Cocoon info

This commit is contained in:
Ilya Laktyushin 2025-12-23 23:54:51 +04:00
parent f7f3b92fa7
commit 57cdc60862
4 changed files with 148 additions and 2 deletions

Binary file not shown.

View file

@ -15648,3 +15648,5 @@ Error: %8$@";
"CocoonInfo.ForEveryone.Text" = "Any developer can use Cocoon for AI features. Learn more at [@cocoon](telegram) or [cocoon.org](web).";
"CocoonInfo.IntergrateInfo" = "Want to integrate Cocoon into your projects?\nReach out at [t.me/cocoon?direct]()";
"CocoonInfo.Understood" = "Understood";
"Conversation.Translation.CocoonInfo" = "Translations are powered by\n**#Cocoon**. [How does it work?]()";

View file

@ -767,7 +767,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
self.panelButton = panelButton
}
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: params.bottomInset, innerDiameter: 52.0 * 0.5, sideInset: sideInset + 16.0)
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: params.bottomInset, innerDiameter: 52.0 * 0.5, sideInset: sideInset + 30.0)
let buttonTitle: String
var buttonIconName: String?

View file

@ -17,6 +17,8 @@ import TranslateUI
import TelegramUIPreferences
import TelegramNotices
import PremiumUI
import LocalMediaResources
import AppBundle
final class ChatTranslationPanelNode: ASDisplayNode {
private let context: AccountContext
@ -362,11 +364,40 @@ final class ChatTranslationPanelNode: ASDisplayNode {
self?.close()
})))
items.append(.separator)
let cocoonPath = getAppBundle().url(forResource: "Cocoon", withExtension: "tgs")?.path ?? ""
let cocoonFile = TelegramMediaFile(
fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: -123456789),
partialReference: nil,
resource: BundleResource(name: "Cocoon", path: cocoonPath),
previewRepresentations: [],
videoThumbnails: [],
immediateThumbnailData: nil,
mimeType: "application/x-tgsticker",
size: nil,
attributes: [
.FileName(fileName: "sticker.tgs"),
.CustomEmoji(isPremium: false, isSingleColor: true, alt: "", packReference: .animatedEmojiAnimations)
],
alternativeRepresentations: []
)
let (cocoonText, entities) = parseCocoonMenuTextEntities(presentationData.strings.Conversation_Translation_CocoonInfo, emojiFileId: cocoonFile.fileId.id)
items.append(.action(ContextMenuActionItem(text: cocoonText, entities: entities, entityFiles: [cocoonFile.fileId.id: cocoonFile], enableEntityAnimations: true, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: { [weak self] c, _ in
c?.dismiss(completion: nil)
if let controller = self?.controller() {
let infoController = context.sharedContext.makeCocoonInfoScreen(context: context)
controller.push(infoController)
}
})))
return ContextController.Items(content: .list(items))
}
if let controller = self.controller() {
let contextController = ContextController(presentationData: presentationData, source: .reference(TranslationContextReferenceContentSource(controller: controller, sourceNode: node)), items: items, gesture: gesture)
let contextController = ContextController(context: context, presentationData: presentationData, source: .reference(TranslationContextReferenceContentSource(controller: controller, sourceNode: node)), items: items, gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
}
@ -941,3 +972,116 @@ private final class TranslationLanguagesContextMenuContent: ContextControllerIte
)
}
}
private func parseCocoonMenuTextEntities(_ input: String, emojiFileId: Int64) -> (String, [MessageTextEntity]) {
var output = ""
var entities: [MessageTextEntity] = []
var i = input.startIndex
var outputCount = 0
func utf16Len(_ s: String) -> Int {
s.utf16.count
}
func peek(_ offset: Int) -> Character? {
var idx = i
for _ in 0..<offset {
if idx == input.endIndex { return nil }
idx = input.index(after: idx)
}
return idx < input.endIndex ? input[idx] : nil
}
var boldStartOut: Int? = nil
while i < input.endIndex {
let c = input[i]
if c == "*", peek(1) == "*" {
if let start = boldStartOut {
let end = outputCount
if end > start {
entities.append(MessageTextEntity(range: start..<end, type: .Bold))
}
boldStartOut = nil
} else {
boldStartOut = outputCount
}
i = input.index(i, offsetBy: 2)
continue
}
if c == "[" {
let labelStart = input.index(after: i)
guard let closeBracket = input[labelStart...].firstIndex(of: "]") else {
let s = String(c)
output += s
outputCount += utf16Len(s)
i = input.index(after: i)
continue
}
let afterBracket = input.index(after: closeBracket)
guard afterBracket < input.endIndex, input[afterBracket] == "(" else {
let s = String(c)
output += s
outputCount += utf16Len(s)
i = input.index(after: i)
continue
}
let urlStart = input.index(after: afterBracket)
guard let closeParen = input[urlStart...].firstIndex(of: ")") else {
let s = String(c)
output += s
outputCount += utf16Len(s)
i = input.index(after: i)
continue
}
let label = String(input[labelStart..<closeBracket])
let labelOutStart = outputCount
output += label
let labelLen = utf16Len(label)
outputCount += labelLen
if !label.isEmpty {
entities.append(MessageTextEntity(
range: labelOutStart ..< (labelOutStart + labelLen),
type: .Url
))
}
i = input.index(after: closeParen)
continue
}
if c == "#" {
let s = "#"
output += s
let len = utf16Len(s)
entities.append(MessageTextEntity(
range: outputCount ..< (outputCount + len),
type: .CustomEmoji(stickerPack: nil, fileId: emojiFileId)
))
outputCount += len
i = input.index(after: i)
continue
}
let s = String(c)
output += s
outputCount += utf16Len(s)
i = input.index(after: i)
}
if let start = boldStartOut {
let end = outputCount
if end > start {
entities.append(MessageTextEntity(range: start..<end, type: .Bold))
}
}
return (output, entities)
}