mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Various improvements
This commit is contained in:
parent
67d43248da
commit
a2d5c530a5
49 changed files with 4592 additions and 1028 deletions
|
|
@ -1419,6 +1419,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
}, displayUndo: { _ in
|
||||
}, presentInputTextTranslation: { _, _ in
|
||||
}, sendEmoji: { _, _, _ in
|
||||
}, openAICompose: {
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": [],
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ public final class ChatRecentActionsController: TelegramBaseController {
|
|||
}, displayUndo: { _ in
|
||||
}, presentInputTextTranslation: { _, _ in
|
||||
}, sendEmoji: { _, _, _ in
|
||||
}, openAICompose: {
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
|
|
|
|||
|
|
@ -760,7 +760,9 @@ public final class ChatTextInputPanelComponent: Component {
|
|||
},
|
||||
sendEmoji: { _, _, _ in
|
||||
},
|
||||
updateHistoryFilter: { _ in
|
||||
openAICompose: {
|
||||
}
|
||||
,updateHistoryFilter: { _ in
|
||||
},
|
||||
updateChatLocationThread: { _, _ in
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
|||
}, displayUndo: { _ in
|
||||
}, presentInputTextTranslation: { _, _ in
|
||||
}, sendEmoji: { _, _, _ in
|
||||
}, openAICompose: {
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
|
|
|
|||
|
|
@ -835,6 +835,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||
}, displayUndo: { _ in
|
||||
}, presentInputTextTranslation: { _, _ in
|
||||
}, sendEmoji: { _, _, _ in
|
||||
}, openAICompose: {
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
40
submodules/TelegramUI/Components/TextProcessingScreen/BUILD
Normal file
40
submodules/TelegramUI/Components/TextProcessingScreen/BUILD
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "TextProcessingScreen",
|
||||
module_name = "TextProcessingScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/Components/ResizableSheetComponent",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/TabBarComponent",
|
||||
"//submodules/TranslateUI",
|
||||
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/TelegramUI/Components/CheckComponent",
|
||||
"//submodules/ShimmerEffect",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,805 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import GlassBackgroundComponent
|
||||
import MultilineTextComponent
|
||||
import BundleIconComponent
|
||||
import TelegramCore
|
||||
import TranslateUI
|
||||
|
||||
final class TextProcessingLanguageSelectionComponent: Component {
|
||||
public struct Language: Equatable {
|
||||
public let id: String
|
||||
public let languageCode: String
|
||||
public let name: String
|
||||
|
||||
public init(id: String, languageCode: String, name: String) {
|
||||
self.id = id
|
||||
self.languageCode = languageCode
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let sourceView: UIView
|
||||
let topLanguages: [Language]
|
||||
let selectedLanguageCode: String
|
||||
let currentStyle: TelegramComposeAIMessageMode.Style
|
||||
let displayStyles: Bool
|
||||
let completion: (String, TelegramComposeAIMessageMode.Style) -> Void
|
||||
let dismissed: () -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
sourceView: UIView,
|
||||
topLanguages: [Language],
|
||||
selectedLanguageCode: String,
|
||||
currentStyle: TelegramComposeAIMessageMode.Style,
|
||||
displayStyles: Bool,
|
||||
completion: @escaping (String, TelegramComposeAIMessageMode.Style) -> Void,
|
||||
dismissed: @escaping () -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.sourceView = sourceView
|
||||
self.topLanguages = topLanguages
|
||||
self.selectedLanguageCode = selectedLanguageCode
|
||||
self.currentStyle = currentStyle
|
||||
self.displayStyles = displayStyles
|
||||
self.completion = completion
|
||||
self.dismissed = dismissed
|
||||
}
|
||||
|
||||
static func ==(lhs: TextProcessingLanguageSelectionComponent, rhs: TextProcessingLanguageSelectionComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.topLanguages != rhs.topLanguages {
|
||||
return false
|
||||
}
|
||||
if lhs.selectedLanguageCode != rhs.selectedLanguageCode {
|
||||
return false
|
||||
}
|
||||
if lhs.currentStyle != rhs.currentStyle {
|
||||
return false
|
||||
}
|
||||
if lhs.displayStyles != rhs.displayStyles {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private final class ScrollView: UIScrollView {
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private struct ItemLayout: Equatable {
|
||||
let size: CGSize
|
||||
let itemHeight: CGFloat
|
||||
let itemCount: Int
|
||||
let topSeparatedItemCount: Int
|
||||
let topSeparatorHeight: CGFloat
|
||||
let verticalInset: CGFloat
|
||||
let contentHeight: CGFloat
|
||||
|
||||
init(size: CGSize, itemHeight: CGFloat, itemCount: Int, topSeparatedItemCount: Int, verticalInset: CGFloat) {
|
||||
self.size = size
|
||||
self.itemHeight = itemHeight
|
||||
self.itemCount = itemCount
|
||||
self.topSeparatedItemCount = topSeparatedItemCount
|
||||
self.topSeparatorHeight = 20.0
|
||||
self.verticalInset = verticalInset
|
||||
var contentHeight = verticalInset * 2.0 + CGFloat(itemCount) * itemHeight
|
||||
self.contentHeight = contentHeight
|
||||
if self.topSeparatedItemCount != 0 {
|
||||
contentHeight += self.topSeparatorHeight
|
||||
}
|
||||
}
|
||||
|
||||
func indexRange(minY: CGFloat, maxY: CGFloat) -> Range<Int>? {
|
||||
var firstIndex = Int(floor((minY - self.verticalInset - self.topSeparatorHeight) / self.itemHeight))
|
||||
firstIndex = max(0, firstIndex)
|
||||
|
||||
var lastIndex = Int(ceil((maxY - self.verticalInset + self.topSeparatorHeight) / self.itemHeight))
|
||||
lastIndex = min(self.itemCount - 1, lastIndex)
|
||||
|
||||
if firstIndex < lastIndex {
|
||||
return firstIndex ..< (lastIndex + 1)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func frame(forItemAt index: Int) -> CGRect {
|
||||
var rect = CGRect(origin: CGPoint(x: 0.0, y: self.verticalInset + CGFloat(index) * self.itemHeight), size: CGSize(width: self.size.width, height: self.itemHeight))
|
||||
if index >= self.topSeparatedItemCount && self.topSeparatedItemCount != 0 {
|
||||
rect.origin.y += self.topSeparatorHeight
|
||||
}
|
||||
return rect
|
||||
}
|
||||
}
|
||||
|
||||
final class View: UIView, UIScrollViewDelegate {
|
||||
private let dimView: UIView
|
||||
|
||||
private let backgroundContainer: GlassBackgroundContainerView
|
||||
private let mainBackground: GlassBackgroundView
|
||||
private let mainScrollView: ScrollView
|
||||
private var mainItemViews: [String: ComponentView<Empty>] = [:]
|
||||
private let mainMeasureItem = ComponentView<Empty>()
|
||||
|
||||
private let mainTopSeparator: SimpleLayer
|
||||
|
||||
private let stylesBackground: GlassBackgroundView
|
||||
private let stylesScrollView: ScrollView
|
||||
private let stylesSelectionView: UIImageView
|
||||
private var stylesItemViews: [TelegramComposeAIMessageMode.Style: ComponentView<Empty>] = [:]
|
||||
|
||||
private var mainItems: [Language] = []
|
||||
private var mainTopItemCount: Int = 0
|
||||
|
||||
private var mainItemLayout: ItemLayout?
|
||||
|
||||
private var component: TextProcessingLanguageSelectionComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var isUpdating: Bool = false
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
private var updatedLanguage: String?
|
||||
private var updatedStyle: TelegramComposeAIMessageMode.Style?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.dimView = UIView()
|
||||
|
||||
self.backgroundContainer = GlassBackgroundContainerView()
|
||||
|
||||
self.mainBackground = GlassBackgroundView()
|
||||
self.backgroundContainer.contentView.addSubview(self.mainBackground)
|
||||
|
||||
self.stylesBackground = GlassBackgroundView()
|
||||
|
||||
self.mainScrollView = ScrollView()
|
||||
self.stylesScrollView = ScrollView()
|
||||
|
||||
self.mainTopSeparator = SimpleLayer()
|
||||
self.mainScrollView.layer.addSublayer(self.mainTopSeparator)
|
||||
|
||||
self.stylesSelectionView = UIImageView()
|
||||
self.stylesScrollView.addSubview(self.stylesSelectionView)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.dimView)
|
||||
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onDimTapGesture(_:))))
|
||||
|
||||
self.addSubview(self.backgroundContainer)
|
||||
|
||||
self.mainScrollView.delaysContentTouches = false
|
||||
self.mainScrollView.canCancelContentTouches = true
|
||||
self.mainScrollView.contentInsetAdjustmentBehavior = .never
|
||||
self.mainScrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
self.mainScrollView.showsVerticalScrollIndicator = false
|
||||
self.mainScrollView.showsHorizontalScrollIndicator = false
|
||||
self.mainScrollView.alwaysBounceHorizontal = false
|
||||
self.mainScrollView.alwaysBounceVertical = true
|
||||
self.mainScrollView.scrollsToTop = false
|
||||
self.mainScrollView.delegate = self
|
||||
self.mainScrollView.clipsToBounds = true
|
||||
self.mainBackground.contentView.addSubview(self.mainScrollView)
|
||||
|
||||
self.stylesScrollView.delaysContentTouches = false
|
||||
self.stylesScrollView.canCancelContentTouches = true
|
||||
self.stylesScrollView.contentInsetAdjustmentBehavior = .never
|
||||
self.stylesScrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
self.stylesScrollView.showsVerticalScrollIndicator = false
|
||||
self.stylesScrollView.showsHorizontalScrollIndicator = false
|
||||
self.stylesScrollView.alwaysBounceHorizontal = false
|
||||
self.stylesScrollView.alwaysBounceVertical = false
|
||||
self.stylesScrollView.scrollsToTop = false
|
||||
self.stylesScrollView.delegate = self
|
||||
self.stylesScrollView.clipsToBounds = true
|
||||
self.stylesBackground.contentView.addSubview(self.stylesScrollView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
@objc private func onDimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case .ended = recognizer.state {
|
||||
if component.displayStyles, let updatedStyle = self.updatedStyle {
|
||||
component.completion(component.selectedLanguageCode, updatedStyle)
|
||||
}
|
||||
self.animateOut()
|
||||
}
|
||||
}
|
||||
|
||||
private func animateIn() {
|
||||
self.mainBackground.layer.animateSpring(from: 0.001, to: 1.0, keyPath: "transform.scale", duration: 0.5)
|
||||
self.stylesBackground.layer.animateSpring(from: 0.001, to: 1.0, keyPath: "transform.scale", duration: 0.5)
|
||||
self.backgroundContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
private func animateOut() {
|
||||
self.mainBackground.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
||||
self.stylesBackground.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
||||
self.backgroundContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.dismissed()
|
||||
})
|
||||
}
|
||||
|
||||
private func completeIfPossible() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if self.updatedLanguage != nil || self.updatedStyle != nil {
|
||||
component.completion(self.updatedLanguage ?? component.selectedLanguageCode, self.updatedStyle ?? component.currentStyle)
|
||||
}
|
||||
self.animateOut()
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if !self.ignoreScrolling {
|
||||
self.updateScrolling(scrollView: scrollView, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateScrolling(scrollView: UIScrollView, transition: ComponentTransition) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if scrollView == self.mainScrollView {
|
||||
guard let itemLayout = self.mainItemLayout else {
|
||||
return
|
||||
}
|
||||
let visibleBounds = scrollView.bounds
|
||||
|
||||
var validIds: [String] = []
|
||||
if let indexRange = itemLayout.indexRange(minY: visibleBounds.minY, maxY: visibleBounds.maxY) {
|
||||
for index in indexRange.lowerBound ..< indexRange.upperBound {
|
||||
if index >= self.mainItems.count {
|
||||
break
|
||||
}
|
||||
let item = self.mainItems[index]
|
||||
validIds.append(item.id)
|
||||
|
||||
let itemView: ComponentView<Empty>
|
||||
var itemTransition = transition
|
||||
if let current = self.mainItemViews[item.id] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemTransition = itemTransition.withAnimation(.none)
|
||||
itemView = ComponentView()
|
||||
self.mainItemViews[item.id] = itemView
|
||||
}
|
||||
|
||||
let itemFrame = itemLayout.frame(forItemAt: index)
|
||||
let _ = itemView.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(LanguageItemComponent(
|
||||
theme: component.theme,
|
||||
title: item.name,
|
||||
isSelected: item.languageCode == component.selectedLanguageCode,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updatedLanguage = item.languageCode
|
||||
self.completeIfPossible()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: itemFrame.size
|
||||
)
|
||||
if let itemComponentView = itemView.view {
|
||||
if itemComponentView.superview == nil {
|
||||
self.mainScrollView.addSubview(itemComponentView)
|
||||
}
|
||||
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var removedIds: [String] = []
|
||||
for (id, itemView) in self.mainItemViews {
|
||||
if !validIds.contains(id) {
|
||||
removedIds.append(id)
|
||||
itemView.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
for id in removedIds {
|
||||
self.mainItemViews.removeValue(forKey: id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: TextProcessingLanguageSelectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
let containerSideInset: CGFloat = 16.0
|
||||
|
||||
var shouldAnimateIn = false
|
||||
if self.component == nil {
|
||||
shouldAnimateIn = true
|
||||
}
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
if self.mainItems.isEmpty {
|
||||
self.mainItems = supportedTranslationLanguages.compactMap { item in
|
||||
return Language(id: item, languageCode: item, name: localizedLanguageName(strings: component.strings, language: item))
|
||||
}
|
||||
var topIds: [String] = []
|
||||
if !topIds.contains(component.selectedLanguageCode), let item = self.mainItems.first(where: { $0.languageCode == component.selectedLanguageCode }) {
|
||||
self.mainItems.insert(TextProcessingLanguageSelectionComponent.Language(
|
||||
id: "top-" + item.id,
|
||||
languageCode: item.languageCode,
|
||||
name: item.name
|
||||
), at: 0)
|
||||
topIds.append(item.languageCode)
|
||||
}
|
||||
if !topIds.contains("en"), let item = self.mainItems.first(where: { $0.languageCode == "en" }) {
|
||||
self.mainItems.insert(TextProcessingLanguageSelectionComponent.Language(
|
||||
id: "top-" + item.id,
|
||||
languageCode: item.languageCode,
|
||||
name: item.name
|
||||
), at: 0)
|
||||
topIds.append(item.languageCode)
|
||||
}
|
||||
|
||||
var languageCode = component.strings.baseLanguageCode
|
||||
let rawSuffix = "-raw"
|
||||
if languageCode.hasSuffix(rawSuffix) {
|
||||
languageCode = String(languageCode.dropLast(rawSuffix.count))
|
||||
}
|
||||
|
||||
if !topIds.contains(languageCode), let item = self.mainItems.first(where: { $0.languageCode == languageCode }) {
|
||||
self.mainItems.insert(TextProcessingLanguageSelectionComponent.Language(
|
||||
id: "top-" + item.id,
|
||||
languageCode: item.languageCode,
|
||||
name: item.name
|
||||
), at: 0)
|
||||
topIds.append(item.languageCode)
|
||||
}
|
||||
self.mainTopItemCount = topIds.count
|
||||
}
|
||||
|
||||
let mainWidth: CGFloat = 220.0
|
||||
let mainContainerInset: CGFloat = 11.0
|
||||
let mainItemSize = self.mainMeasureItem.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(LanguageItemComponent(
|
||||
theme: component.theme,
|
||||
title: "A",
|
||||
isSelected: false,
|
||||
action: {
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: mainWidth, height: 1000.0)
|
||||
)
|
||||
let mainContentHeight = mainContainerInset * 2.0 + CGFloat(self.mainItems.count) * mainItemSize.height
|
||||
|
||||
var mainSize = CGSize(width: mainWidth, height: min(370.0, mainContentHeight))
|
||||
|
||||
var stylesSize: CGSize?
|
||||
let stylesSpacing: CGFloat = 8.0
|
||||
if component.displayStyles {
|
||||
var styleData: [(id: TelegramComposeAIMessageMode.Style, icon: String, title: String)] = []
|
||||
styleData.append((.neutral, "🏳️", "Neutral"))
|
||||
styleData.append((.formal, "🤝", "Formal"))
|
||||
styleData.append((.short, "🎯", "Short"))
|
||||
styleData.append((.savage, "🍖", "Savage"))
|
||||
styleData.append((.biblical, "🕯", "Biblical"))
|
||||
styleData.append((.posh, "🍷", "Posh"))
|
||||
|
||||
let stylesItemSize = CGSize(width: 82.0, height: 60.0)
|
||||
var selectedItemFrame: CGRect?
|
||||
stylesSize = CGSize(width: stylesItemSize.width, height: CGFloat(styleData.count) * stylesItemSize.height)
|
||||
for index in 0 ..< styleData.count {
|
||||
let item = styleData[index]
|
||||
let itemView: ComponentView<Empty>
|
||||
var itemViewTransition = transition
|
||||
if let current = self.stylesItemViews[item.id] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemViewTransition = itemViewTransition.withAnimation(.none)
|
||||
itemView = ComponentView()
|
||||
self.stylesItemViews[item.id] = itemView
|
||||
}
|
||||
let _ = itemView.update(
|
||||
transition: itemViewTransition,
|
||||
component: AnyComponent(StyleItemComponent(
|
||||
theme: component.theme,
|
||||
icon: item.icon,
|
||||
title: item.title,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updatedStyle = item.id
|
||||
self.completeIfPossible()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: stylesItemSize
|
||||
)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: CGFloat(index) * stylesItemSize.height), size: stylesItemSize)
|
||||
if let itemComponentView = itemView.view {
|
||||
if itemComponentView.superview == nil {
|
||||
self.stylesScrollView.addSubview(itemComponentView)
|
||||
}
|
||||
itemViewTransition.setFrame(view: itemComponentView, frame: itemFrame)
|
||||
}
|
||||
if item.id == self.updatedStyle ?? component.currentStyle {
|
||||
selectedItemFrame = itemFrame
|
||||
}
|
||||
}
|
||||
|
||||
if self.stylesSelectionView.image == nil {
|
||||
self.stylesSelectionView.image = generateStretchableFilledCircleImage(diameter: (30.0 - 4.0) * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
self.stylesSelectionView.tintColor = component.theme.list.itemHighlightedBackgroundColor.withMultipliedAlpha(0.6)
|
||||
|
||||
if let selectedItemFrame {
|
||||
var selectedBackgroundTransition = transition
|
||||
if self.stylesSelectionView.isHidden {
|
||||
self.stylesSelectionView.isHidden = false
|
||||
selectedBackgroundTransition = selectedBackgroundTransition.withAnimation(.none)
|
||||
}
|
||||
selectedBackgroundTransition.setFrame(view: self.stylesSelectionView, frame: selectedItemFrame.insetBy(dx: 4.0, dy: 4.0))
|
||||
transition.setAlpha(view: self.stylesSelectionView, alpha: 1.0)
|
||||
} else {
|
||||
if !self.stylesSelectionView.isHidden {
|
||||
transition.setAlpha(view: self.stylesSelectionView, alpha: 0.0, completion: { [weak self] flag in
|
||||
guard let self, flag else {
|
||||
return
|
||||
}
|
||||
self.stylesSelectionView.isHidden = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if let stylesSize {
|
||||
mainSize.height = stylesSize.height
|
||||
}
|
||||
|
||||
let mainItemLayout = ItemLayout(size: mainSize, itemHeight: mainItemSize.height, itemCount: self.mainItems.count, topSeparatedItemCount: self.mainTopItemCount, verticalInset: mainContainerInset)
|
||||
self.mainItemLayout = mainItemLayout
|
||||
|
||||
if mainItemLayout.topSeparatedItemCount != 0 {
|
||||
self.mainTopSeparator.backgroundColor = component.theme.contextMenu.itemSeparatorColor.cgColor
|
||||
self.mainTopSeparator.isHidden = false
|
||||
var topSeparatorFrame = CGRect(origin: CGPoint(x: 18.0, y: mainItemLayout.verticalInset + CGFloat(mainItemLayout.topSeparatedItemCount) * mainItemLayout.itemHeight), size: CGSize(width: mainItemLayout.size.width - 18.0 - 18.0, height: UIScreenPixel))
|
||||
topSeparatorFrame.origin.y += floorToScreenPixels((mainItemLayout.topSeparatorHeight - topSeparatorFrame.height) * 0.5)
|
||||
transition.setFrame(layer: self.mainTopSeparator, frame: topSeparatorFrame)
|
||||
} else {
|
||||
self.mainTopSeparator.isHidden = true
|
||||
}
|
||||
|
||||
self.ignoreScrolling = true
|
||||
if self.mainScrollView.bounds.size != mainItemLayout.size || self.mainScrollView.contentSize.height != mainItemLayout.contentHeight {
|
||||
self.mainScrollView.frame = CGRect(origin: CGPoint(), size: mainItemLayout.size)
|
||||
self.mainScrollView.contentSize = CGSize(width: mainItemLayout.size.width, height: mainItemLayout.contentHeight)
|
||||
}
|
||||
self.ignoreScrolling = false
|
||||
self.updateScrolling(scrollView: self.mainScrollView, transition: transition)
|
||||
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
self.backgroundContainer.update(size: availableSize, isDark: component.theme.overallDarkAppearance, transition: transition)
|
||||
|
||||
let sourceLocation = component.sourceView.convert(component.sourceView.bounds.center, to: self)
|
||||
var mainFrame = CGRect(origin: CGPoint(x: floor(sourceLocation.x - mainItemLayout.size.width * 0.5), y: floor(sourceLocation.y - mainItemLayout.size.height * 0.5)), size: mainItemLayout.size)
|
||||
if mainFrame.origin.x + mainFrame.size.width > availableSize.width - containerSideInset {
|
||||
mainFrame.origin.x = availableSize.width - containerSideInset - mainFrame.size.width
|
||||
}
|
||||
if mainFrame.origin.y + mainFrame.size.height > availableSize.height - containerSideInset {
|
||||
mainFrame.origin.y = availableSize.height - containerSideInset - mainFrame.size.height
|
||||
}
|
||||
if mainFrame.origin.x < containerSideInset {
|
||||
mainFrame.origin.x = containerSideInset
|
||||
}
|
||||
if mainFrame.origin.y < containerSideInset {
|
||||
mainFrame.origin.y = containerSideInset
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.mainBackground, frame: mainFrame)
|
||||
self.mainBackground.update(size: mainFrame.size, cornerRadius: 30.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)
|
||||
|
||||
if let stylesSize {
|
||||
let stylesFrame = CGRect(origin: CGPoint(x: mainFrame.maxX + stylesSpacing, y: mainFrame.minY), size: stylesSize)
|
||||
if self.stylesBackground.superview == nil {
|
||||
self.backgroundContainer.contentView.addSubview(self.stylesBackground)
|
||||
}
|
||||
transition.setFrame(view: self.stylesBackground, frame: stylesFrame)
|
||||
self.stylesBackground.update(size: stylesFrame.size, cornerRadius: 30.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)
|
||||
|
||||
transition.setFrame(view: self.stylesScrollView, frame: CGRect(origin: CGPoint(), size: stylesFrame.size))
|
||||
self.stylesScrollView.contentSize = stylesFrame.size
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
if shouldAnimateIn {
|
||||
self.animateIn()
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private final class LanguageItemComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let isSelected: Bool
|
||||
let action: () -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
title: String,
|
||||
isSelected: Bool,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.isSelected = isSelected
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: LanguageItemComponent, rhs: LanguageItemComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.isSelected != rhs.isSelected {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var imageIcon: ComponentView<Empty>?
|
||||
private let title = ComponentView<Empty>()
|
||||
|
||||
private var component: LanguageItemComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case .ended = recognizer.state {
|
||||
component.action()
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: LanguageItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let size = CGSize(width: availableSize.width, height: 42.0)
|
||||
|
||||
let leftTitleInset: CGFloat = 60.0
|
||||
let rightTitleInset: CGFloat = 8.0
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.regular(17.0), textColor: component.theme.contextMenu.primaryColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - leftTitleInset - rightTitleInset, height: 100.0)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftTitleInset, y: floorToScreenPixels((size.height - titleSize.height) * 0.5)), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.isUserInteractionEnabled = false
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
}
|
||||
|
||||
if component.isSelected {
|
||||
let imageIcon: ComponentView<Empty>
|
||||
var imageIconTransition = transition
|
||||
if let current = self.imageIcon {
|
||||
imageIcon = current
|
||||
} else {
|
||||
imageIconTransition = imageIconTransition.withAnimation(.none)
|
||||
imageIcon = ComponentView()
|
||||
self.imageIcon = imageIcon
|
||||
}
|
||||
let imageIconSize = imageIcon.update(
|
||||
transition: imageIconTransition,
|
||||
component: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Context Menu/Check",
|
||||
tintColor: component.theme.contextMenu.primaryColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let imageIconFrame = CGRect(origin: CGPoint(x: 23.0, y: floorToScreenPixels((size.height - imageIconSize.height) * 0.5)), size: imageIconSize)
|
||||
if let imageIconView = imageIcon.view {
|
||||
if imageIconView.superview == nil {
|
||||
imageIconView.isUserInteractionEnabled = false
|
||||
self.addSubview(imageIconView)
|
||||
}
|
||||
imageIconTransition.setFrame(view: imageIconView, frame: imageIconFrame)
|
||||
}
|
||||
} else {
|
||||
if let imageIcon = self.imageIcon {
|
||||
self.imageIcon = nil
|
||||
imageIcon.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private final class StyleItemComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let icon: String
|
||||
let title: String
|
||||
let action: () -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
icon: String,
|
||||
title: String,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.icon = icon
|
||||
self.title = title
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: StyleItemComponent, rhs: StyleItemComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.icon != rhs.icon {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var imageIcon: ComponentView<Empty>?
|
||||
private let title = ComponentView<Empty>()
|
||||
|
||||
private var component: StyleItemComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case .ended = recognizer.state {
|
||||
component.action()
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: StyleItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let iconTintColor = component.theme.list.itemPrimaryTextColor
|
||||
|
||||
let imageIcon: ComponentView<Empty>
|
||||
var iconTransition = transition
|
||||
if let current = self.imageIcon {
|
||||
imageIcon = current
|
||||
} else {
|
||||
iconTransition = iconTransition.withAnimation(.none)
|
||||
imageIcon = ComponentView()
|
||||
self.imageIcon = imageIcon
|
||||
}
|
||||
|
||||
let iconSize = imageIcon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.icon, font: Font.regular(25.0), textColor: .black))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: 8.0), size: iconSize)
|
||||
if let imageIconView = imageIcon.view {
|
||||
if imageIconView.superview == nil {
|
||||
self.addSubview(imageIconView)
|
||||
}
|
||||
iconTransition.setFrame(view: imageIconView, frame: iconFrame)
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(10.0), textColor: iconTintColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: availableSize.height - 9.0 - titleSize.height), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,722 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
import AccountContext
|
||||
import ViewControllerComponent
|
||||
import MultilineTextComponent
|
||||
import ButtonComponent
|
||||
import BundleIconComponent
|
||||
import TelegramCore
|
||||
import PresentationDataUtils
|
||||
import ResizableSheetComponent
|
||||
import GlassBarButtonComponent
|
||||
import TabBarComponent
|
||||
import TranslateUI
|
||||
import LottieComponent
|
||||
|
||||
final class TextProcessingContentComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
final class ExternalState {
|
||||
fileprivate(set) var isProcessing: Bool = false
|
||||
fileprivate(set) var result: TextWithEntities?
|
||||
|
||||
init() {
|
||||
}
|
||||
}
|
||||
|
||||
let externalState: ExternalState
|
||||
let context: AccountContext
|
||||
let inputText: TextWithEntities
|
||||
let copyCurrentResult: () -> Void
|
||||
let displayLanguageSelectionMenu: (UIView, String, TelegramComposeAIMessageMode.Style, Bool, @escaping (String, TelegramComposeAIMessageMode.Style) -> Void) -> Void
|
||||
|
||||
init(
|
||||
externalState: ExternalState,
|
||||
context: AccountContext,
|
||||
inputText: TextWithEntities,
|
||||
copyCurrentResult: @escaping () -> Void,
|
||||
displayLanguageSelectionMenu: @escaping (UIView, String, TelegramComposeAIMessageMode.Style, Bool, @escaping (String, TelegramComposeAIMessageMode.Style) -> Void) -> Void
|
||||
) {
|
||||
self.externalState = externalState
|
||||
self.context = context
|
||||
self.inputText = inputText
|
||||
self.copyCurrentResult = copyCurrentResult
|
||||
self.displayLanguageSelectionMenu = displayLanguageSelectionMenu
|
||||
}
|
||||
|
||||
static func ==(lhs: TextProcessingContentComponent, rhs: TextProcessingContentComponent) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
private enum Mode {
|
||||
case translate
|
||||
case stylize
|
||||
case fix
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var component: TextProcessingContentComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var isUpdating: Bool = false
|
||||
|
||||
private let modeTabs = ComponentView<Empty>()
|
||||
|
||||
private let currentContentBackground: UIImageView
|
||||
private let currentContentContainer: UIView
|
||||
|
||||
private let translateState = TextProcessingTranslateContentComponent.ExternalState()
|
||||
private let stylizeState = TextProcessingTranslateContentComponent.ExternalState()
|
||||
private let fixState = TextProcessingTranslateContentComponent.ExternalState()
|
||||
|
||||
private var currentContent: (mode: Mode, view: ComponentView<Empty>)?
|
||||
|
||||
private var currentMode: Mode = .translate
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.currentContentBackground = UIImageView()
|
||||
self.currentContentContainer = UIView()
|
||||
self.currentContentContainer.clipsToBounds = true
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.currentContentBackground)
|
||||
self.addSubview(self.currentContentContainer)
|
||||
|
||||
self.translateState.resultUpdated = { [weak self] result in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case .translate = self.currentMode {
|
||||
component.externalState.result = result?.text
|
||||
}
|
||||
}
|
||||
self.translateState.isProcessingUpdated = { [weak self] isProcessing in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case .translate = self.currentMode {
|
||||
component.externalState.isProcessing = isProcessing
|
||||
}
|
||||
}
|
||||
self.stylizeState.resultUpdated = { [weak self] result in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case .stylize = self.currentMode {
|
||||
component.externalState.result = result?.text
|
||||
}
|
||||
}
|
||||
self.stylizeState.isProcessingUpdated = { [weak self] isProcessing in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case .stylize = self.currentMode {
|
||||
component.externalState.isProcessing = isProcessing
|
||||
}
|
||||
}
|
||||
self.fixState.resultUpdated = { [weak self] result in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case .fix = self.currentMode {
|
||||
component.externalState.result = result?.text
|
||||
}
|
||||
}
|
||||
self.fixState.isProcessingUpdated = { [weak self] isProcessing in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case .fix = self.currentMode {
|
||||
component.externalState.isProcessing = isProcessing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(component: TextProcessingContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
|
||||
|
||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
contentHeight += 85.0
|
||||
|
||||
var tabs: [TabBarComponent.Item] = []
|
||||
tabs.append(TabBarComponent.Item(
|
||||
content: .customItem(TabBarComponent.Item.Content.CustomItem(
|
||||
id: "translate",
|
||||
title: "Translate",
|
||||
icon: .bundleIcon(name: "TextProcessing/TabTranslate")
|
||||
)),
|
||||
action: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.currentMode != .translate {
|
||||
self.currentMode = .translate
|
||||
}
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
},
|
||||
doubleTapAction: nil,
|
||||
contextAction: nil
|
||||
))
|
||||
tabs.append(TabBarComponent.Item(
|
||||
content: .customItem(TabBarComponent.Item.Content.CustomItem(
|
||||
id: "stylize",
|
||||
title: "Style",
|
||||
icon: .bundleIcon(name: "TextProcessing/TabStylize")
|
||||
)),
|
||||
action: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.currentMode != .stylize {
|
||||
self.currentMode = .stylize
|
||||
}
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
},
|
||||
doubleTapAction: nil,
|
||||
contextAction: nil
|
||||
))
|
||||
tabs.append(TabBarComponent.Item(
|
||||
content: .customItem(TabBarComponent.Item.Content.CustomItem(
|
||||
id: "fix",
|
||||
title: "Fix",
|
||||
icon: .bundleIcon(name: "TextProcessing/TabFix")
|
||||
)),
|
||||
action: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.currentMode != .fix {
|
||||
self.currentMode = .fix
|
||||
}
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
},
|
||||
doubleTapAction: nil,
|
||||
contextAction: nil
|
||||
))
|
||||
|
||||
let currentModeId: String
|
||||
switch self.currentMode {
|
||||
case .translate:
|
||||
currentModeId = "translate"
|
||||
case .stylize:
|
||||
currentModeId = "stylize"
|
||||
case .fix:
|
||||
currentModeId = "fix"
|
||||
}
|
||||
let modeTabsSize = self.modeTabs.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(TabBarComponent(
|
||||
theme: environment.theme,
|
||||
tintSelectedItem: false,
|
||||
strings: environment.strings,
|
||||
items: tabs,
|
||||
search: nil,
|
||||
selectedId: currentModeId,
|
||||
outerInsets: UIEdgeInsets()
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 62.0)
|
||||
)
|
||||
let modeTabsFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: modeTabsSize)
|
||||
if let modeTabsView = self.modeTabs.view {
|
||||
if modeTabsView.superview == nil {
|
||||
self.modeTabs.parentState = state
|
||||
self.addSubview(modeTabsView)
|
||||
}
|
||||
transition.setFrame(view: modeTabsView, frame: modeTabsFrame)
|
||||
}
|
||||
contentHeight += modeTabsSize.height
|
||||
contentHeight += 24.0
|
||||
|
||||
if let currentContent = self.currentContent, currentContent.mode != self.currentMode {
|
||||
if let currentContentView = currentContent.view.view {
|
||||
transition.setAlpha(view: currentContentView, alpha: 0.0, completion: { [weak currentContentView] _ in
|
||||
currentContentView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
self.currentContent = nil
|
||||
}
|
||||
|
||||
let contentComponent: AnyComponent<Empty>
|
||||
switch self.currentMode {
|
||||
case .translate:
|
||||
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
externalState: self.translateState,
|
||||
inputText: component.inputText,
|
||||
mode: .translate,
|
||||
copyAction: component.copyCurrentResult,
|
||||
displayLanguageSelectionMenu: component.displayLanguageSelectionMenu
|
||||
))
|
||||
case .stylize:
|
||||
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
externalState: self.stylizeState,
|
||||
inputText: component.inputText,
|
||||
mode: .stylize,
|
||||
copyAction: component.copyCurrentResult,
|
||||
displayLanguageSelectionMenu: component.displayLanguageSelectionMenu
|
||||
))
|
||||
case .fix:
|
||||
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
externalState: self.fixState,
|
||||
inputText: component.inputText,
|
||||
mode: .fix,
|
||||
copyAction: component.copyCurrentResult,
|
||||
displayLanguageSelectionMenu: component.displayLanguageSelectionMenu
|
||||
))
|
||||
}
|
||||
|
||||
let content: ComponentView<Empty>
|
||||
var contentTransition = transition
|
||||
if let current = self.currentContent {
|
||||
content = current.view
|
||||
} else {
|
||||
content = ComponentView()
|
||||
self.currentContent = (self.currentMode, content)
|
||||
contentTransition = contentTransition.withAnimation(.none)
|
||||
}
|
||||
let contentSize = content.update(
|
||||
transition: contentTransition,
|
||||
component: contentComponent,
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000000.0)
|
||||
)
|
||||
if let contentView = content.view {
|
||||
if contentView.superview == nil {
|
||||
content.parentState = state
|
||||
self.currentContentContainer.addSubview(contentView)
|
||||
contentView.layer.allowsGroupOpacity = true
|
||||
contentView.alpha = 0.0
|
||||
}
|
||||
alphaTransition.setAlpha(view: contentView, alpha: 1.0)
|
||||
contentTransition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(), size: contentSize))
|
||||
}
|
||||
let contentFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: contentSize)
|
||||
transition.setFrame(view: self.currentContentContainer, frame: contentFrame)
|
||||
|
||||
if self.currentContentBackground.image == nil {
|
||||
self.currentContentBackground.image = generateStretchableFilledCircleImage(diameter: 60.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
self.currentContentBackground.tintColor = environment.theme.list.itemBlocksBackgroundColor
|
||||
transition.setFrame(view: self.currentContentBackground, frame: contentFrame)
|
||||
|
||||
contentHeight += contentSize.height
|
||||
|
||||
contentHeight += 106.0
|
||||
|
||||
return CGSize(width: availableSize.width, height: contentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private final class TextProcessingSheetComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let inputText: TextWithEntities
|
||||
let copyCurrentResult: (TextWithEntities) -> Void
|
||||
let completion: (TextWithEntities) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
inputText: TextWithEntities,
|
||||
copyCurrentResult: @escaping (TextWithEntities) -> Void,
|
||||
completion: @escaping (TextWithEntities) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.inputText = inputText
|
||||
self.copyCurrentResult = copyCurrentResult
|
||||
self.completion = completion
|
||||
}
|
||||
|
||||
static func ==(lhs: TextProcessingSheetComponent, rhs: TextProcessingSheetComponent) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let sheet = Child(ResizableSheetComponent<EnvironmentType>.self)
|
||||
let languageSelectionMenu = Child(TextProcessingLanguageSelectionComponent.self)
|
||||
let animateOut = StoredActionSlot(Action<Void>.self)
|
||||
let contentExternalState = TextProcessingContentComponent.ExternalState()
|
||||
|
||||
class LanguageSelectionMenuData {
|
||||
let sourceView: UIView
|
||||
let currentLanguage: String
|
||||
let currentStyle: TelegramComposeAIMessageMode.Style
|
||||
let displayStyle: Bool
|
||||
let completion: (String, TelegramComposeAIMessageMode.Style) -> Void
|
||||
|
||||
init(sourceView: UIView, currentLanguage: String, currentStyle: TelegramComposeAIMessageMode.Style, displayStyle: Bool, completion: @escaping (String, TelegramComposeAIMessageMode.Style) -> Void) {
|
||||
self.sourceView = sourceView
|
||||
self.currentLanguage = currentLanguage
|
||||
self.currentStyle = currentStyle
|
||||
self.displayStyle = displayStyle
|
||||
self.completion = completion
|
||||
}
|
||||
}
|
||||
var languageSelectionMenuData: LanguageSelectionMenuData?
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
let controller = environment.controller
|
||||
let theme = environment.theme
|
||||
let state = context.state
|
||||
|
||||
let dismiss: (Bool) -> Void = { animated in
|
||||
if animated {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let completion = context.component.completion
|
||||
let performMainAction: () -> Void = {
|
||||
if let result = contentExternalState.result {
|
||||
completion(result)
|
||||
}
|
||||
dismiss(true)
|
||||
}
|
||||
let copyCurrentResult = context.component.copyCurrentResult
|
||||
let copyCurrentResultImpl: () -> Void = {
|
||||
if let result = contentExternalState.result {
|
||||
copyCurrentResult(result)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:localize (for AI: don't remove this line)
|
||||
let titleString: String = "AI Editor"
|
||||
//TODO:localize (for AI: don't remove this line)
|
||||
let actionButtonTitle: String = "Apply"
|
||||
|
||||
let sheet = sheet.update(
|
||||
component: ResizableSheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(TextProcessingContentComponent(
|
||||
externalState: contentExternalState,
|
||||
context: context.component.context,
|
||||
inputText: context.component.inputText,
|
||||
copyCurrentResult: {
|
||||
copyCurrentResultImpl()
|
||||
},
|
||||
displayLanguageSelectionMenu: { [weak state] sourceView, currentLanguage, currentStyle, displayStyle, completion in
|
||||
languageSelectionMenuData = LanguageSelectionMenuData(sourceView: sourceView, currentLanguage: currentLanguage, currentStyle: currentStyle, displayStyle: displayStyle, completion: completion)
|
||||
state?.updated(transition: .immediate)
|
||||
}
|
||||
)),
|
||||
titleItem: AnyComponent(TitleComponent(
|
||||
theme: theme,
|
||||
title: titleString,
|
||||
isProcessing: contentExternalState.isProcessing
|
||||
)),
|
||||
leftItem: AnyComponent(
|
||||
GlassBarButtonComponent(
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: nil,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { _ in
|
||||
dismiss(true)
|
||||
}
|
||||
)
|
||||
),
|
||||
bottomItem: AnyComponent(
|
||||
ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: theme.list.itemCheckColors.fillColor,
|
||||
foreground: theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(ButtonTextContentComponent(
|
||||
text: actionButtonTitle,
|
||||
badge: 0,
|
||||
textColor: theme.list.itemCheckColors.foregroundColor,
|
||||
badgeBackground: theme.list.itemCheckColors.foregroundColor,
|
||||
badgeForeground: theme.list.itemCheckColors.fillColor
|
||||
))
|
||||
),
|
||||
isEnabled: !contentExternalState.isProcessing,
|
||||
displaysProgress: false,
|
||||
action: {
|
||||
performMainAction()
|
||||
}
|
||||
)
|
||||
),
|
||||
backgroundColor: .color(theme.list.blocksBackgroundColor),
|
||||
animateOut: animateOut
|
||||
),
|
||||
environment: {
|
||||
environment
|
||||
ResizableSheetComponentEnvironment(
|
||||
theme: theme,
|
||||
statusBarHeight: environment.statusBarHeight,
|
||||
safeInsets: environment.safeInsets,
|
||||
metrics: environment.metrics,
|
||||
deviceMetrics: environment.deviceMetrics,
|
||||
isDisplaying: environment.value.isVisible,
|
||||
isCentered: environment.metrics.widthClass == .regular,
|
||||
screenSize: context.availableSize,
|
||||
regularMetricsSize: nil,
|
||||
dismiss: { animated in
|
||||
dismiss(animated)
|
||||
}
|
||||
)
|
||||
},
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
context.add(sheet
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
|
||||
if let languageSelectionMenuDataValue = languageSelectionMenuData {
|
||||
let languageSelectionMenu = languageSelectionMenu.update(
|
||||
component: TextProcessingLanguageSelectionComponent(
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
sourceView: languageSelectionMenuDataValue.sourceView,
|
||||
topLanguages: [],
|
||||
selectedLanguageCode: languageSelectionMenuDataValue.currentLanguage,
|
||||
currentStyle: languageSelectionMenuDataValue.currentStyle,
|
||||
displayStyles: languageSelectionMenuDataValue.displayStyle,
|
||||
completion: languageSelectionMenuDataValue.completion,
|
||||
dismissed: { [weak state] in
|
||||
languageSelectionMenuData = nil
|
||||
state?.updated(transition: .immediate)
|
||||
}
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(languageSelectionMenu
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
}
|
||||
|
||||
return context.availableSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TextProcessingScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
inputText: TextWithEntities,
|
||||
copyResult: @escaping (TextWithEntities) -> Void,
|
||||
completion: @escaping (TextWithEntities) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
super.init(
|
||||
context: context,
|
||||
component: TextProcessingSheetComponent(
|
||||
context: context,
|
||||
inputText: inputText,
|
||||
copyCurrentResult: copyResult,
|
||||
completion: completion
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
theme: .default
|
||||
)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
self.navigationPresentation = .flatModal
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
public func dismissAnimated() {
|
||||
if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? ResizableSheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||
view.dismissAnimated()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class TitleComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let isProcessing: Bool
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
title: String,
|
||||
isProcessing: Bool
|
||||
) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.isProcessing = isProcessing
|
||||
}
|
||||
|
||||
static func ==(lhs: TitleComponent, rhs: TitleComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.isProcessing != rhs.isProcessing {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var animationIcon: ComponentView<Empty>?
|
||||
private let title = ComponentView<Empty>()
|
||||
|
||||
private var component: TitleComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: TitleComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.isUserInteractionEnabled = false
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
}
|
||||
|
||||
if component.isProcessing {
|
||||
let animationIcon: ComponentView<Empty>
|
||||
var animationIconTransition = transition
|
||||
if let current = self.animationIcon {
|
||||
animationIcon = current
|
||||
} else {
|
||||
animationIconTransition = animationIconTransition.withAnimation(.none)
|
||||
animationIcon = ComponentView()
|
||||
self.animationIcon = animationIcon
|
||||
}
|
||||
|
||||
let animationIconSize = animationIcon.update(
|
||||
transition: animationIconTransition,
|
||||
component: AnyComponent(LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(
|
||||
name: "SparklesEmoji"
|
||||
),
|
||||
placeholderColor: nil,
|
||||
startingPosition: .begin,
|
||||
size: CGSize(width: 30.0, height: 30.0),
|
||||
loop: true
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 30.0, height: 30.0)
|
||||
)
|
||||
let animationIconFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - animationIconSize.height) * 0.5) - 2.0), size: animationIconSize)
|
||||
if let animationIconView = animationIcon.view {
|
||||
if animationIconView.superview == nil {
|
||||
self.addSubview(animationIconView)
|
||||
animationIconView.alpha = 0.0
|
||||
}
|
||||
animationIconTransition.setFrame(view: animationIconView, frame: animationIconFrame)
|
||||
transition.setAlpha(view: animationIconView, alpha: 1.0)
|
||||
}
|
||||
} else {
|
||||
if let animationIcon = self.animationIcon {
|
||||
self.animationIcon = nil
|
||||
if let animationIconView = animationIcon.view {
|
||||
transition.setAlpha(view: animationIconView, alpha: 0.0, completion: { [weak animationIconView] _ in
|
||||
animationIconView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return titleSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
import TelegramCore
|
||||
|
||||
final class TextProcessingStyleSelectionComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let selectedStyle: TelegramComposeAIMessageMode.Style
|
||||
let updateStyle: (TelegramComposeAIMessageMode.Style) -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
selectedStyle: TelegramComposeAIMessageMode.Style,
|
||||
updateStyle: @escaping (TelegramComposeAIMessageMode.Style) -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.selectedStyle = selectedStyle
|
||||
self.updateStyle = updateStyle
|
||||
}
|
||||
|
||||
static func ==(lhs: TextProcessingStyleSelectionComponent, rhs: TextProcessingStyleSelectionComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.selectedStyle != rhs.selectedStyle {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var component: TextProcessingStyleSelectionComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var isUpdating: Bool = false
|
||||
|
||||
private var itemViews: [TelegramComposeAIMessageMode.Style: ComponentView<Empty>] = [:]
|
||||
private let selectedBackgroundView: UIImageView
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.selectedBackgroundView = UIImageView()
|
||||
self.selectedBackgroundView.isHidden = true
|
||||
self.selectedBackgroundView.alpha = 0.0
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.selectedBackgroundView)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case .ended = recognizer.state {
|
||||
for (id, itemView) in self.itemViews {
|
||||
if let itemComponentView = itemView.view {
|
||||
if itemComponentView.bounds.contains(self.convert(recognizer.location(in: self), to: itemComponentView)) {
|
||||
component.updateStyle(id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: TextProcessingStyleSelectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
var styleData: [(id: TelegramComposeAIMessageMode.Style, icon: String, title: String)] = []
|
||||
styleData.append((.neutral, "🏳️", "Neutral"))
|
||||
styleData.append((.formal, "🤝", "Formal"))
|
||||
styleData.append((.short, "🎯", "Short"))
|
||||
styleData.append((.savage, "🍖", "Savage"))
|
||||
styleData.append((.biblical, "🕯", "Biblical"))
|
||||
styleData.append((.posh, "🍷", "Posh"))
|
||||
|
||||
let itemSize = CGSize(width: floor(availableSize.width / CGFloat(styleData.count)), height: availableSize.height)
|
||||
var selectedItemFrame: CGRect?
|
||||
for i in 0 ..< styleData.count {
|
||||
let style = styleData[i]
|
||||
let itemView: ComponentView<Empty>
|
||||
var itemTransition = transition
|
||||
if let current = self.itemViews[style.id] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemTransition = itemTransition.withAnimation(.none)
|
||||
itemView = ComponentView()
|
||||
self.itemViews[style.id] = itemView
|
||||
}
|
||||
let itemFrame = CGRect(origin: CGPoint(x: CGFloat(i) * itemSize.width, y: 0.0), size: itemSize)
|
||||
let _ = itemView.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(ItemComponent(
|
||||
theme: component.theme,
|
||||
icon: style.icon,
|
||||
title: style.title
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: itemSize
|
||||
)
|
||||
if let itemComponentView = itemView.view {
|
||||
if itemComponentView.superview == nil {
|
||||
self.addSubview(itemComponentView)
|
||||
}
|
||||
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
|
||||
}
|
||||
if style.id == component.selectedStyle {
|
||||
selectedItemFrame = CGRect(origin: CGPoint(x: itemFrame.minX, y: itemFrame.minY - 5.0), size: CGSize(width: itemFrame.width, height: itemFrame.height + 5.0 + 3.0))
|
||||
}
|
||||
}
|
||||
|
||||
if self.selectedBackgroundView.image == nil {
|
||||
self.selectedBackgroundView.image = generateStretchableFilledCircleImage(diameter: 16.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
self.selectedBackgroundView.tintColor = component.theme.list.itemHighlightedBackgroundColor.withMultipliedAlpha(0.6)
|
||||
|
||||
if let selectedItemFrame {
|
||||
var selectedBackgroundTransition = transition
|
||||
if self.selectedBackgroundView.isHidden {
|
||||
self.selectedBackgroundView.isHidden = false
|
||||
selectedBackgroundTransition = selectedBackgroundTransition.withAnimation(.none)
|
||||
}
|
||||
selectedBackgroundTransition.setFrame(view: self.selectedBackgroundView, frame: selectedItemFrame)
|
||||
alphaTransition.setAlpha(view: self.selectedBackgroundView, alpha: 1.0)
|
||||
} else {
|
||||
if !self.selectedBackgroundView.isHidden {
|
||||
alphaTransition.setAlpha(view: self.selectedBackgroundView, alpha: 0.0, completion: { [weak self] flag in
|
||||
guard let self, flag else {
|
||||
return
|
||||
}
|
||||
self.selectedBackgroundView.isHidden = true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: availableSize.height)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private final class ItemComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let icon: String
|
||||
let title: String
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
icon: String,
|
||||
title: String
|
||||
) {
|
||||
self.theme = theme
|
||||
self.icon = icon
|
||||
self.title = title
|
||||
}
|
||||
|
||||
static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.icon != rhs.icon {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var imageIcon: ComponentView<Empty>?
|
||||
private let title = ComponentView<Empty>()
|
||||
|
||||
private var component: ItemComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let iconTintColor = component.theme.list.itemPrimaryTextColor
|
||||
|
||||
let imageIcon: ComponentView<Empty>
|
||||
var iconTransition = transition
|
||||
if let current = self.imageIcon {
|
||||
imageIcon = current
|
||||
} else {
|
||||
iconTransition = iconTransition.withAnimation(.none)
|
||||
imageIcon = ComponentView()
|
||||
self.imageIcon = imageIcon
|
||||
}
|
||||
|
||||
let iconSize = imageIcon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.icon, font: Font.regular(25.0), textColor: .black))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: -1.0), size: iconSize)
|
||||
if let imageIconView = imageIcon.view {
|
||||
if imageIconView.superview == nil {
|
||||
self.addSubview(imageIconView)
|
||||
}
|
||||
iconTransition.setFrame(view: imageIconView, frame: iconFrame)
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(10.0), textColor: iconTintColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: availableSize.height - 5.0 - titleSize.height), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,532 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import AccountContext
|
||||
import MultilineTextComponent
|
||||
import BundleIconComponent
|
||||
import TelegramCore
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import TextFormat
|
||||
import PlainButtonComponent
|
||||
import CheckComponent
|
||||
import ShimmerEffect
|
||||
|
||||
final class TextProcessingTextAreaComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let titlePrefix: String
|
||||
let title: String
|
||||
let titleAction: ((UIView) -> Void)?
|
||||
let isExpanded: (value: Bool, toggle: () -> Void)?
|
||||
let copyAction: (() -> Void)?
|
||||
let emojify: (value: Bool, toggle: () -> Void)?
|
||||
let text: TextWithEntities?
|
||||
let textCorrectionRanges: [Range<Int>]
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
titlePrefix: String,
|
||||
title: String,
|
||||
titleAction: ((UIView) -> Void)?,
|
||||
isExpanded: (value: Bool, toggle: () -> Void)?,
|
||||
copyAction: (() -> Void)?,
|
||||
emojify: (value: Bool, toggle: () -> Void)?,
|
||||
text: TextWithEntities?,
|
||||
textCorrectionRanges: [Range<Int>]
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.titlePrefix = titlePrefix
|
||||
self.isExpanded = isExpanded
|
||||
self.copyAction = copyAction
|
||||
self.title = title
|
||||
self.titleAction = titleAction
|
||||
self.emojify = emojify
|
||||
self.text = text
|
||||
self.textCorrectionRanges = textCorrectionRanges
|
||||
}
|
||||
|
||||
static func ==(lhs: TextProcessingTextAreaComponent, rhs: TextProcessingTextAreaComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.titlePrefix != rhs.titlePrefix {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if (lhs.titleAction == nil) != (rhs.titleAction == nil) {
|
||||
return false
|
||||
}
|
||||
if lhs.isExpanded?.value != rhs.isExpanded?.value {
|
||||
return false
|
||||
}
|
||||
if (lhs.copyAction == nil) != (rhs.copyAction == nil) {
|
||||
return false
|
||||
}
|
||||
if lhs.emojify?.value != rhs.emojify?.value {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.textCorrectionRanges != rhs.textCorrectionRanges {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var component: TextProcessingTextAreaComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var isUpdating: Bool = false
|
||||
|
||||
private let titlePrefix = ComponentView<Empty>()
|
||||
private let title = ComponentView<Empty>()
|
||||
private var titleArrow: ComponentView<Empty>?
|
||||
private var emojify: ComponentView<Empty>?
|
||||
private let titleButton: HighlightTrackingButton
|
||||
|
||||
private let textState = MultilineTextWithEntitiesComponent.External()
|
||||
private let textContainer: UIView
|
||||
private let text = ComponentView<Empty>()
|
||||
private var expandShadow: UIImageView?
|
||||
private var expandButton: ComponentView<Empty>?
|
||||
|
||||
private let copyButton = ComponentView<Empty>()
|
||||
|
||||
private let measureLoadingTextState = MultilineTextWithEntitiesComponent.External()
|
||||
private let measureLoadingText = ComponentView<Empty>()
|
||||
private var shimmerEffectNode: ShimmerEffectNode?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.textContainer = UIView()
|
||||
self.textContainer.clipsToBounds = true
|
||||
|
||||
self.titleButton = HighlightTrackingButton()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.textContainer)
|
||||
self.addSubview(self.titleButton)
|
||||
|
||||
self.titleButton.highligthedChanged = { [weak self] highighed in
|
||||
guard let self, let titleView = self.title.view, let titleArrowView = self.titleArrow?.view else {
|
||||
return
|
||||
}
|
||||
if highighed {
|
||||
titleView.alpha = 0.6
|
||||
titleArrowView.alpha = 0.6
|
||||
} else {
|
||||
let transition: ComponentTransition = .easeInOut(duration: 0.25)
|
||||
transition.setAlpha(view: titleView, alpha: 1.0)
|
||||
transition.setAlpha(view: titleArrowView, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
self.titleButton.addTarget(self, action: #selector(self.titleButtonPressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
@objc private func titleButtonPressed() {
|
||||
guard let component = self.component, let titleView = self.title.view else {
|
||||
return
|
||||
}
|
||||
component.titleAction?(titleView)
|
||||
}
|
||||
|
||||
func update(component: TextProcessingTextAreaComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
self.titleButton.isUserInteractionEnabled = component.titleAction != nil
|
||||
|
||||
let topInset: CGFloat = 0.0
|
||||
let bottomInset: CGFloat = 0.0
|
||||
let sideInset: CGFloat = 0.0
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
contentHeight += topInset
|
||||
|
||||
let titlePrefixSize = self.titlePrefix.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.titlePrefix, font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 10.0, height: 100.0)
|
||||
)
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(13.0), textColor: component.theme.list.itemAccentColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 10.0, height: 100.0)
|
||||
)
|
||||
|
||||
let titlePrefixFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: titlePrefixSize)
|
||||
var titleFrame = CGRect(origin: CGPoint(x: titlePrefixFrame.maxX, y: titlePrefixFrame.minY), size: titleSize)
|
||||
if !component.titlePrefix.isEmpty {
|
||||
titleFrame.origin.x += 3.0
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.titleButton, frame: titleFrame.insetBy(dx: -10.0, dy: -10.0))
|
||||
|
||||
if let titlePrefixView = self.titlePrefix.view {
|
||||
if titlePrefixView.superview == nil {
|
||||
titlePrefixView.layer.anchorPoint = CGPoint()
|
||||
titlePrefixView.isUserInteractionEnabled = false
|
||||
self.addSubview(titlePrefixView)
|
||||
}
|
||||
titlePrefixView.bounds = CGRect(origin: CGPoint(), size: titlePrefixFrame.size)
|
||||
transition.setPosition(view: titlePrefixView, position: titlePrefixFrame.origin)
|
||||
}
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.layer.anchorPoint = CGPoint()
|
||||
titleView.isUserInteractionEnabled = false
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
transition.setPosition(view: titleView, position: titleFrame.origin)
|
||||
}
|
||||
|
||||
if component.titleAction != nil {
|
||||
let titleArrow: ComponentView<Empty>
|
||||
var titleArrowTransition = transition
|
||||
if let current = self.titleArrow {
|
||||
titleArrow = current
|
||||
} else {
|
||||
titleArrowTransition = titleArrowTransition.withAnimation(.none)
|
||||
titleArrow = ComponentView()
|
||||
self.titleArrow = titleArrow
|
||||
}
|
||||
let titleArrowSize = titleArrow.update(
|
||||
transition: titleArrowTransition,
|
||||
component: AnyComponent(BundleIconComponent(
|
||||
name: "Item List/ExpandableSelectorArrows", tintColor: component.theme.list.itemAccentColor.withMultipliedAlpha(0.8))),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let titleArrowFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 2.0, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - titleArrowSize.height) * 0.5)), size: titleArrowSize)
|
||||
if let titleArrowView = titleArrow.view {
|
||||
if titleArrowView.superview == nil {
|
||||
titleArrowView.isUserInteractionEnabled = false
|
||||
self.addSubview(titleArrowView)
|
||||
}
|
||||
transition.setFrame(view: titleArrowView, frame: titleArrowFrame)
|
||||
}
|
||||
} else {
|
||||
if let titleArrow = self.titleArrow {
|
||||
self.titleArrow = nil
|
||||
titleArrow.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if let emojifyValue = component.emojify {
|
||||
let emojify: ComponentView<Empty>
|
||||
var emojifyTransition = transition
|
||||
if let current = self.emojify {
|
||||
emojify = current
|
||||
} else {
|
||||
emojify = ComponentView()
|
||||
self.emojify = emojify
|
||||
emojifyTransition = emojifyTransition.withAnimation(.none)
|
||||
}
|
||||
let checkTheme = CheckComponent.Theme(
|
||||
backgroundColor: component.theme.list.itemCheckColors.fillColor,
|
||||
strokeColor: component.theme.list.itemCheckColors.foregroundColor,
|
||||
borderColor: component.theme.list.itemCheckColors.strokeColor,
|
||||
overlayBorder: false,
|
||||
hasInset: false,
|
||||
hasShadow: false
|
||||
)
|
||||
let emojifySize = emojify.update(
|
||||
transition: emojifyTransition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(HStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent(
|
||||
theme: checkTheme,
|
||||
size: CGSize(width: 16.0, height: 16.0),
|
||||
selected: emojifyValue.value
|
||||
))),
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "Emojify", font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor))
|
||||
)))
|
||||
], spacing: 7.0)),
|
||||
effectAlignment: .center,
|
||||
action: {
|
||||
emojifyValue.toggle()
|
||||
},
|
||||
animateAlpha: false,
|
||||
animateScale: false
|
||||
)),
|
||||
environment: {
|
||||
},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
|
||||
)
|
||||
let emojifyFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - emojifySize.width, y: contentHeight), size: emojifySize)
|
||||
if let emojifyView = emojify.view {
|
||||
if emojifyView.superview == nil {
|
||||
self.addSubview(emojifyView)
|
||||
}
|
||||
emojifyTransition.setFrame(view: emojifyView, frame: emojifyFrame)
|
||||
}
|
||||
} else {
|
||||
if let emojify = self.emojify {
|
||||
self.emojify = nil
|
||||
emojify.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
contentHeight += 25.0
|
||||
|
||||
let fontSize: CGFloat = 17.0
|
||||
let textValue = NSMutableAttributedString(attributedString: stringWithAppliedEntities(
|
||||
component.text?.text ?? "",
|
||||
entities: component.text?.entities ?? [],
|
||||
baseColor: component.theme.list.itemPrimaryTextColor,
|
||||
linkColor: component.theme.list.itemAccentColor,
|
||||
baseFont: Font.regular(fontSize),
|
||||
linkFont: Font.regular(fontSize),
|
||||
boldFont: Font.semibold(fontSize),
|
||||
italicFont: Font.italic(fontSize),
|
||||
boldItalicFont: Font.semiboldItalic(fontSize),
|
||||
fixedFont: Font.monospace(fontSize),
|
||||
blockQuoteFont: Font.monospace(fontSize),
|
||||
message: nil
|
||||
))
|
||||
for range in component.textCorrectionRanges {
|
||||
if range.lowerBound >= 0 && range.upperBound < textValue.length {
|
||||
textValue.addAttributes([
|
||||
.underlineColor: component.theme.list.itemAccentColor,
|
||||
.underlineStyle: NSUnderlineStyle.patternDot.rawValue
|
||||
], range: NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound))
|
||||
}
|
||||
}
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextWithEntitiesComponent(
|
||||
external: self.textState,
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: component.theme.list.mediaPlaceholderColor,
|
||||
text: .plain(textValue),
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.12,
|
||||
cutout: nil,
|
||||
insets: UIEdgeInsets(),
|
||||
spoilerColor: component.theme.list.itemPrimaryTextColor,
|
||||
enableLooping: true,
|
||||
displaysAsynchronously: false,
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: textSize)
|
||||
if let textView = self.text.view {
|
||||
if textView.superview == nil {
|
||||
textView.layer.anchorPoint = CGPoint()
|
||||
self.textContainer.addSubview(textView)
|
||||
}
|
||||
textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||
}
|
||||
|
||||
var textContainerFrame = textFrame
|
||||
if let isExpanded = component.isExpanded, let textLayout = self.textState.layout, textLayout.numberOfLines > 1 {
|
||||
if !isExpanded.value, let firstLineRect = textLayout.linesRects().first {
|
||||
textContainerFrame.size.height = firstLineRect.maxY - 14.0
|
||||
}
|
||||
|
||||
let expandButton: ComponentView<Empty>
|
||||
var expandButtonTransition = transition
|
||||
if let current = self.expandButton {
|
||||
expandButton = current
|
||||
} else {
|
||||
expandButtonTransition = expandButtonTransition.withAnimation(.none)
|
||||
expandButton = ComponentView()
|
||||
self.expandButton = expandButton
|
||||
}
|
||||
let expandShadow: UIImageView
|
||||
if let current = self.expandShadow {
|
||||
expandShadow = current
|
||||
} else {
|
||||
expandShadow = UIImageView()
|
||||
self.expandShadow = expandShadow
|
||||
self.addSubview(expandShadow)
|
||||
}
|
||||
let expandShadowExtent: CGFloat = 20.0
|
||||
if expandShadow.image == nil {
|
||||
let baseSize: CGFloat = 20.0
|
||||
expandShadow.image = generateImage(CGSize(width: baseSize + expandShadowExtent * 2.0, height: baseSize + expandShadowExtent * 2.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let colors: [CGColor] = [UIColor.white.cgColor, UIColor.white.withAlphaComponent(0.0).cgColor]
|
||||
let locations: [CGFloat] = [0.0, 1.0]
|
||||
if let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as CFArray, locations: locations) {
|
||||
let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.drawRadialGradient(gradient, startCenter: center, startRadius: baseSize / 2.0, endCenter: center, endRadius: size.width / 2.0, options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
|
||||
}
|
||||
})?.withRenderingMode(.alwaysTemplate).stretchableImage(withLeftCapWidth: Int(baseSize / 2.0 + expandShadowExtent), topCapHeight: Int(baseSize / 2.0 + expandShadowExtent))
|
||||
}
|
||||
expandShadow.tintColor = component.theme.list.itemBlocksBackgroundColor
|
||||
|
||||
let expandButtonSize = expandButton.update(
|
||||
transition: expandButtonTransition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: isExpanded.value ? "less" : "more", font: Font.regular(17.0), textColor: component.theme.list.itemAccentColor))
|
||||
)),
|
||||
effectAlignment: .right,
|
||||
action: {
|
||||
isExpanded.toggle()
|
||||
},
|
||||
animateAlpha: false,
|
||||
animateScale: false,
|
||||
animateContents: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 100.0)
|
||||
)
|
||||
let expandButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - expandButtonSize.width, y: textContainerFrame.maxY - expandButtonSize.height - 2.0), size: expandButtonSize)
|
||||
if let expandButtonView = expandButton.view {
|
||||
if expandButtonView.superview == nil {
|
||||
expandButtonView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
|
||||
self.addSubview(expandButtonView)
|
||||
}
|
||||
expandButtonTransition.setPosition(view: expandButtonView, position: CGPoint(x: expandButtonFrame.maxX, y: expandButtonFrame.minY))
|
||||
expandButtonTransition.setBounds(view: expandButtonView, bounds: CGRect(origin: CGPoint(), size: expandButtonFrame.size))
|
||||
|
||||
expandButtonTransition.setFrame(view: expandShadow, frame: expandButtonFrame.insetBy(dx: -expandShadowExtent, dy: -expandShadowExtent))
|
||||
}
|
||||
} else {
|
||||
if let expandButton = self.expandButton {
|
||||
self.expandButton = nil
|
||||
expandButton.view?.removeFromSuperview()
|
||||
}
|
||||
if let expandShadow = self.expandShadow {
|
||||
self.expandShadow = nil
|
||||
expandShadow.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if component.text == nil {
|
||||
let shimmerEffectNode: ShimmerEffectNode
|
||||
if let current = self.shimmerEffectNode {
|
||||
shimmerEffectNode = current
|
||||
} else {
|
||||
shimmerEffectNode = ShimmerEffectNode()
|
||||
self.shimmerEffectNode = shimmerEffectNode
|
||||
self.addSubview(shimmerEffectNode.view)
|
||||
}
|
||||
|
||||
let measureLoadingTextSize = self.measureLoadingText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextWithEntitiesComponent(
|
||||
external: self.measureLoadingTextState,
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: component.theme.list.mediaPlaceholderColor,
|
||||
text: .plain(NSAttributedString(string: "a\na\na\na", font: Font.regular(fontSize), textColor: .black)),
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.12,
|
||||
cutout: nil,
|
||||
insets: UIEdgeInsets(),
|
||||
spoilerColor: component.theme.list.itemPrimaryTextColor,
|
||||
enableLooping: true,
|
||||
displaysAsynchronously: false,
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
textContainerFrame.size = CGSize(width: availableSize.width, height: measureLoadingTextSize.height)
|
||||
|
||||
shimmerEffectNode.frame = textContainerFrame
|
||||
|
||||
var shapes: [ShimmerEffectNode.Shape] = []
|
||||
if let textLayout = self.measureLoadingTextState.layout {
|
||||
let lineWidths: [CGFloat] = [1.0, 0.9, 1.0, 0.8]
|
||||
var index = 0
|
||||
for lineRect in textLayout.linesRects() {
|
||||
shapes.append(.roundedRectLine(startPoint: CGPoint(x: 0.0, y: lineRect.midY - 18.0), width: floor(textContainerFrame.width * lineWidths[index % lineWidths.count]), diameter: 6.0))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
shimmerEffectNode.updateAbsoluteRect(shimmerEffectNode.bounds, within: shimmerEffectNode.bounds.size)
|
||||
shimmerEffectNode.update(backgroundColor: component.theme.list.plainBackgroundColor, foregroundColor: component.theme.list.mediaPlaceholderColor, shimmeringColor: component.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: shimmerEffectNode.bounds.size)
|
||||
} else {
|
||||
if let shimmerEffectNode = self.shimmerEffectNode {
|
||||
self.shimmerEffectNode = nil
|
||||
shimmerEffectNode.view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if let copyAction = component.copyAction {
|
||||
let copyButtonSize = self.copyButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Context Menu/Copy",
|
||||
tintColor: component.theme.list.itemAccentColor
|
||||
)),
|
||||
effectAlignment: .right,
|
||||
action: {
|
||||
copyAction()
|
||||
},
|
||||
animateAlpha: true,
|
||||
animateScale: false,
|
||||
animateContents: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 100.0)
|
||||
)
|
||||
let copyButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - copyButtonSize.width, y: textContainerFrame.maxY - copyButtonSize.height - 2.0), size: copyButtonSize)
|
||||
if let copyButtonView = self.copyButton.view {
|
||||
if copyButtonView.superview == nil {
|
||||
copyButtonView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
|
||||
self.addSubview(copyButtonView)
|
||||
}
|
||||
transition.setPosition(view: copyButtonView, position: CGPoint(x: copyButtonFrame.maxX, y: copyButtonFrame.minY))
|
||||
transition.setBounds(view: copyButtonView, bounds: CGRect(origin: CGPoint(), size: copyButtonFrame.size))
|
||||
alphaTransition.setAlpha(view: copyButtonView, alpha: component.text != nil ? 1.0 : 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.textContainer, frame: textContainerFrame)
|
||||
contentHeight += textContainerFrame.height
|
||||
|
||||
contentHeight += bottomInset
|
||||
|
||||
return CGSize(width: availableSize.width, height: contentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,460 @@
|
|||
import Foundation
|
||||
import NaturalLanguage
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import AccountContext
|
||||
import MultilineTextComponent
|
||||
import BundleIconComponent
|
||||
import TelegramCore
|
||||
import TranslateUI
|
||||
|
||||
private let languageRecognizer = NLLanguageRecognizer()
|
||||
|
||||
func localizedLanguageName(strings: PresentationStrings, language: String) -> String {
|
||||
let toLang = language
|
||||
let key = "Translation.Language.\(toLang)"
|
||||
let translateTitle: String
|
||||
if let string = strings.primaryComponent.dict[key] {
|
||||
translateTitle = string
|
||||
} else {
|
||||
let languageLocale = Locale(identifier: language)
|
||||
let toLanguage = languageLocale.localizedString(forLanguageCode: toLang) ?? ""
|
||||
return toLanguage
|
||||
}
|
||||
return translateTitle
|
||||
}
|
||||
|
||||
final class TextProcessingTranslateContentComponent: Component {
|
||||
enum Mode {
|
||||
case translate
|
||||
case stylize
|
||||
case fix
|
||||
}
|
||||
|
||||
final class ExternalState {
|
||||
fileprivate(set) var sourceLanguage: String?
|
||||
|
||||
fileprivate(set) var result: (language: String, text: TextWithEntities?, textCorrectionRanges: [Range<Int>])? = nil {
|
||||
didSet {
|
||||
if self.result?.language != oldValue?.language || self.result?.text != oldValue?.text {
|
||||
self.resultUpdated?(self.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
var resultUpdated: (((language: String, text: TextWithEntities?, textCorrectionRanges: [Range<Int>])?) -> Void)?
|
||||
|
||||
fileprivate(set) var emojify: Bool = false
|
||||
fileprivate(set) var isSourceTextExpanded: Bool = false
|
||||
fileprivate(set) var style: TelegramComposeAIMessageMode.Style = .neutral
|
||||
|
||||
fileprivate(set) var isProcessing: Bool = false {
|
||||
didSet {
|
||||
if self.isProcessing != oldValue {
|
||||
self.isProcessingUpdated?(self.isProcessing)
|
||||
}
|
||||
}
|
||||
}
|
||||
var isProcessingUpdated: ((Bool) -> Void)?
|
||||
|
||||
init() {
|
||||
}
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let inputText: TextWithEntities
|
||||
let externalState: ExternalState
|
||||
let mode: Mode
|
||||
let copyAction: () -> Void
|
||||
let displayLanguageSelectionMenu: (UIView, String, TelegramComposeAIMessageMode.Style, Bool, @escaping (String, TelegramComposeAIMessageMode.Style) -> Void) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
externalState: ExternalState,
|
||||
inputText: TextWithEntities,
|
||||
mode: Mode,
|
||||
copyAction: @escaping () -> Void,
|
||||
displayLanguageSelectionMenu: @escaping (UIView, String, TelegramComposeAIMessageMode.Style, Bool, @escaping (String, TelegramComposeAIMessageMode.Style) -> Void) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.externalState = externalState
|
||||
self.inputText = inputText
|
||||
self.mode = mode
|
||||
self.copyAction = copyAction
|
||||
self.displayLanguageSelectionMenu = displayLanguageSelectionMenu
|
||||
}
|
||||
|
||||
static func ==(lhs: TextProcessingTranslateContentComponent, rhs: TextProcessingTranslateContentComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.externalState !== rhs.externalState {
|
||||
return false
|
||||
}
|
||||
if lhs.inputText != rhs.inputText {
|
||||
return false
|
||||
}
|
||||
if lhs.mode != rhs.mode {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let sourceText = ComponentView<Empty>()
|
||||
private let targetText = ComponentView<Empty>()
|
||||
private let separatorLayer: SimpleLayer
|
||||
|
||||
private var component: TextProcessingTranslateContentComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var isUpdating: Bool = false
|
||||
|
||||
private var processDisposable: Disposable?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.separatorLayer = SimpleLayer()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.addSublayer(self.separatorLayer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.processDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func beginTranslationIfNecessary(reset: Bool) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
if reset {
|
||||
self.processDisposable?.dispose()
|
||||
self.processDisposable = nil
|
||||
if let result = component.externalState.result {
|
||||
component.externalState.result = (result.language, nil, [])
|
||||
}
|
||||
}
|
||||
|
||||
if let result = component.externalState.result, result.text == nil, self.processDisposable == nil {
|
||||
switch component.mode {
|
||||
case .translate:
|
||||
component.externalState.isProcessing = true
|
||||
self.processDisposable = (component.context.engine.messages.composeAIMessage(
|
||||
text: component.inputText,
|
||||
mode: .translate(toLanguage: result.language, emojify: component.externalState.emojify, style: component.externalState.style)
|
||||
) |> deliverOnMainQueue).startStrict(next: { [weak self] processedText in
|
||||
guard let self, let component = self.component, let processedText else {
|
||||
return
|
||||
}
|
||||
component.externalState.isProcessing = false
|
||||
component.externalState.result = (result.language, processedText.text, processedText.diffRanges)
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
})
|
||||
case .stylize:
|
||||
if !component.externalState.emojify && component.externalState.style == .neutral {
|
||||
component.externalState.isProcessing = false
|
||||
component.externalState.result = (result.language, component.inputText, [])
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
} else {
|
||||
component.externalState.isProcessing = true
|
||||
self.processDisposable = (component.context.engine.messages.composeAIMessage(
|
||||
text: component.inputText,
|
||||
mode: .stylize(emojify: component.externalState.emojify, style: component.externalState.style)
|
||||
) |> deliverOnMainQueue).startStrict(next: { [weak self] processedText in
|
||||
guard let self, let component = self.component, let processedText else {
|
||||
return
|
||||
}
|
||||
component.externalState.isProcessing = false
|
||||
component.externalState.result = (result.language, processedText.text, processedText.diffRanges)
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
})
|
||||
}
|
||||
case .fix:
|
||||
component.externalState.isProcessing = true
|
||||
self.processDisposable = (component.context.engine.messages.composeAIMessage(
|
||||
text: component.inputText,
|
||||
mode: .proofread
|
||||
) |> deliverOnMainQueue).startStrict(next: { [weak self] processedText in
|
||||
guard let self, let component = self.component, let processedText else {
|
||||
return
|
||||
}
|
||||
component.externalState.isProcessing = false
|
||||
component.externalState.result = (result.language, processedText.text, processedText.diffRanges)
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: TextProcessingTranslateContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
if component.externalState.sourceLanguage == nil {
|
||||
languageRecognizer.processString(component.inputText.text)
|
||||
let hypotheses = languageRecognizer.languageHypotheses(withMaximum: 3)
|
||||
languageRecognizer.reset()
|
||||
|
||||
let filteredLanguages = hypotheses.sorted(by: { $0.value > $1.value })
|
||||
if let first = filteredLanguages.first {
|
||||
component.externalState.sourceLanguage = normalizeTranslationLanguage(first.key.rawValue)
|
||||
} else {
|
||||
component.externalState.sourceLanguage = "en"
|
||||
}
|
||||
}
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
if component.externalState.result == nil {
|
||||
switch component.mode {
|
||||
case .translate:
|
||||
var languageCode = component.strings.baseLanguageCode
|
||||
let rawSuffix = "-raw"
|
||||
if languageCode.hasSuffix(rawSuffix) {
|
||||
languageCode = String(languageCode.dropLast(rawSuffix.count))
|
||||
}
|
||||
component.externalState.result = (languageCode, nil, [])
|
||||
self.beginTranslationIfNecessary(reset: false)
|
||||
case .stylize:
|
||||
component.externalState.result = ("", component.inputText, [])
|
||||
case .fix:
|
||||
component.externalState.result = ("", nil, [])
|
||||
self.beginTranslationIfNecessary(reset: false)
|
||||
}
|
||||
}
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
let topInset: CGFloat = 17.0
|
||||
let bottomInset: CGFloat = 14.0
|
||||
let blockSpacing: CGFloat = 30.0
|
||||
|
||||
let fromPrefix: String
|
||||
let toPrefix: String
|
||||
var toTitle: String
|
||||
switch component.mode {
|
||||
case .translate:
|
||||
fromPrefix = "From"
|
||||
toPrefix = "To"
|
||||
toTitle = localizedLanguageName(strings: component.strings, language: component.externalState.result?.language ?? "")
|
||||
if component.externalState.style != .neutral {
|
||||
toTitle.append(" (")
|
||||
let styleName: String
|
||||
switch component.externalState.style {
|
||||
case .neutral:
|
||||
styleName = ""
|
||||
case .formal:
|
||||
styleName = "Formal"
|
||||
case .short:
|
||||
styleName = "Short"
|
||||
case .savage:
|
||||
styleName = "Savage"
|
||||
case .biblical:
|
||||
styleName = "Biblical"
|
||||
case .posh:
|
||||
styleName = "Posh"
|
||||
}
|
||||
toTitle.append(styleName)
|
||||
toTitle.append(")")
|
||||
}
|
||||
case .stylize, .fix:
|
||||
fromPrefix = "Original:"
|
||||
toPrefix = "Result"
|
||||
toTitle = ""
|
||||
}
|
||||
|
||||
contentHeight += topInset
|
||||
if case .stylize = component.mode {
|
||||
let sourceTextSize = self.sourceText.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(TextProcessingStyleSelectionComponent(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
selectedStyle: component.externalState.style,
|
||||
updateStyle: { [weak self] style in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if component.externalState.style != style {
|
||||
component.externalState.style = style
|
||||
|
||||
if let result = component.externalState.result {
|
||||
component.externalState.result = (result.language, nil, [])
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
self.beginTranslationIfNecessary(reset: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 46.0)
|
||||
)
|
||||
let sourceTextFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: sourceTextSize)
|
||||
contentHeight += sourceTextSize.height
|
||||
|
||||
if let sourceTextView = self.sourceText.view {
|
||||
if sourceTextView.superview == nil {
|
||||
self.sourceText.parentState = state
|
||||
self.addSubview(sourceTextView)
|
||||
}
|
||||
transition.setFrame(view: sourceTextView, frame: sourceTextFrame)
|
||||
}
|
||||
} else {
|
||||
let sourceTextSize = self.sourceText.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(TextProcessingTextAreaComponent(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
titlePrefix: fromPrefix,
|
||||
title: localizedLanguageName(strings: component.strings, language: component.externalState.sourceLanguage ?? ""),
|
||||
titleAction: nil,
|
||||
isExpanded: (
|
||||
component.externalState.isSourceTextExpanded,
|
||||
{ [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.externalState.isSourceTextExpanded = !component.externalState.isSourceTextExpanded
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
}
|
||||
),
|
||||
copyAction: nil,
|
||||
emojify: nil,
|
||||
text: component.inputText,
|
||||
textCorrectionRanges: []
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
|
||||
let sourceTextFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: sourceTextSize)
|
||||
contentHeight += sourceTextSize.height
|
||||
|
||||
if let sourceTextView = self.sourceText.view {
|
||||
if sourceTextView.superview == nil {
|
||||
self.sourceText.parentState = state
|
||||
self.addSubview(sourceTextView)
|
||||
}
|
||||
transition.setFrame(view: sourceTextView, frame: sourceTextFrame)
|
||||
}
|
||||
}
|
||||
|
||||
let targetTextSize = self.targetText.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(TextProcessingTextAreaComponent(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
titlePrefix: toPrefix,
|
||||
title: toTitle,
|
||||
titleAction: component.mode == .translate ? { [weak self] sourceView in
|
||||
guard let self, let component = self.component, let result = component.externalState.result else {
|
||||
return
|
||||
}
|
||||
component.displayLanguageSelectionMenu(sourceView, result.language, component.externalState.style, true, { [weak self] language, style in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
if component.externalState.result?.language != language || component.externalState.style != style {
|
||||
component.externalState.result = (language, nil, [])
|
||||
component.externalState.style = style
|
||||
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
self.beginTranslationIfNecessary(reset: true)
|
||||
}
|
||||
})
|
||||
} : nil,
|
||||
isExpanded: nil,
|
||||
copyAction: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.copyAction()
|
||||
},
|
||||
emojify: (component.mode == .translate || component.mode == .stylize) ? (
|
||||
component.externalState.emojify,
|
||||
{ [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.externalState.emojify = !component.externalState.emojify
|
||||
|
||||
self.beginTranslationIfNecessary(reset: true)
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
}
|
||||
) : nil,
|
||||
text: component.externalState.result?.text,
|
||||
textCorrectionRanges: component.externalState.result?.textCorrectionRanges ?? []
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
|
||||
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight + floorToScreenPixels((blockSpacing - UIScreenPixel) * 0.5) - 1.0), size: CGSize(width: availableSize.width - sideInset * 2.0, height: UIScreenPixel)))
|
||||
self.separatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
|
||||
|
||||
contentHeight += blockSpacing
|
||||
let targetTextFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: targetTextSize)
|
||||
contentHeight += targetTextSize.height
|
||||
|
||||
if let targetTextView = self.targetText.view {
|
||||
if targetTextView.superview == nil {
|
||||
self.targetText.parentState = state
|
||||
self.addSubview(targetTextView)
|
||||
}
|
||||
transition.setFrame(view: targetTextView, frame: targetTextFrame)
|
||||
}
|
||||
|
||||
contentHeight += bottomInset
|
||||
|
||||
return CGSize(width: availableSize.width, height: contentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
12
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/InputAIIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/InputAIIcon.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ai.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/InputAIIcon.imageset/ai.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/InputAIIcon.imageset/ai.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
12
submodules/TelegramUI/Images.xcassets/TextProcessing/TabFix.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/TextProcessing/TabFix.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "fix.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/TextProcessing/TabFix.imageset/fix.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/TextProcessing/TabFix.imageset/fix.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/TextProcessing/TabStylize.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/TextProcessing/TabStylize.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "style.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/TextProcessing/TabStylize.imageset/style.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/TextProcessing/TabStylize.imageset/style.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/TextProcessing/TabTranslate.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/TextProcessing/TabTranslate.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "translate.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/TextProcessing/TabTranslate.imageset/translate.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/TextProcessing/TabTranslate.imageset/translate.pdf
vendored
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/Animations/SparklesEmoji.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/SparklesEmoji.tgs
Normal file
Binary file not shown.
|
|
@ -4501,6 +4501,11 @@ extension ChatControllerImpl {
|
|||
return
|
||||
}
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue