import Foundation import UIKit import Display import AsyncDisplayKit import Postbox import TelegramCore import TelegramPresentationData import LocalizedPeerData import TelegramStringFormatting import TextFormat import Markdown import ChatPresentationInterfaceState import AccountContext import MoreButtonNode import ContextUI import TranslateUI final class ChatTranslationPanelNode: ASDisplayNode { private let context: AccountContext private let separatorNode: ASDisplayNode private let button: HighlightableButtonNode private let buttonIconNode: ASImageNode private let buttonTextNode: ImmediateTextNode private let moreButton: MoreButtonNode private var theme: PresentationTheme? private var chatInterfaceState: ChatPresentationInterfaceState? var interfaceInteraction: ChatPanelInterfaceInteraction? init(context: AccountContext) { self.context = context self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true self.button = HighlightableButtonNode() self.buttonIconNode = ASImageNode() self.buttonIconNode.displaysAsynchronously = false self.buttonTextNode = ImmediateTextNode() self.buttonTextNode.displaysAsynchronously = false self.moreButton = MoreButtonNode(theme: context.sharedContext.currentPresentationData.with { $0 }.theme) self.moreButton.iconNode.enqueueState(.more, animated: false) super.init() self.addSubnode(self.separatorNode) self.addSubnode(self.button) self.addSubnode(self.moreButton) self.button.addSubnode(self.buttonIconNode) self.button.addSubnode(self.buttonTextNode) self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: [.touchUpInside]) self.moreButton.action = { [weak self] _, gesture in if let strongSelf = self { strongSelf.morePressed(node: strongSelf.moreButton.contextSourceNode, gesture: gesture) } } } func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { let previousIsEnabled = self.chatInterfaceState?.translationState?.isEnabled self.chatInterfaceState = interfaceState var themeUpdated = false if interfaceState.theme !== self.theme { themeUpdated = true self.theme = interfaceState.theme } var isEnabledUpdated = false if previousIsEnabled != interfaceState.translationState?.isEnabled { isEnabledUpdated = true } if themeUpdated { self.buttonIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/Translate"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor) self.moreButton.theme = interfaceState.theme self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor } if themeUpdated || isEnabledUpdated { var languageCode = interfaceState.strings.baseLanguageCode let rawSuffix = "-raw" if languageCode.hasSuffix(rawSuffix) { languageCode = String(languageCode.dropLast(rawSuffix.count)) } let locale = Locale(identifier: languageCode) let toLang = interfaceState.translationState?.toLang ?? languageCode let toLanguage: String = locale.localizedString(forLanguageCode: toLang) ?? "" let buttonText = interfaceState.translationState?.isEnabled == true ? interfaceState.strings.Conversation_Translation_ShowOriginal : interfaceState.strings.Conversation_Translation_TranslateTo(toLanguage).string self.buttonTextNode.attributedText = NSAttributedString(string: buttonText, font: Font.regular(17.0), textColor: interfaceState.theme.rootController.navigationBar.accentTextColor) } let panelHeight: CGFloat = 44.0 let contentRightInset: CGFloat = 14.0 + rightInset let moreButtonSize = self.moreButton.measure(CGSize(width: 100.0, height: panelHeight)) self.moreButton.frame = CGRect(origin: CGPoint(x: width - contentRightInset - moreButtonSize.width, y: floorToScreenPixels((panelHeight - moreButtonSize.height) / 2.0)), size: moreButtonSize) let buttonSpacing: CGFloat = 10.0 let buttonTextSize = self.buttonTextNode.updateLayout(CGSize(width: width - contentRightInset - moreButtonSize.width, height: panelHeight)) if let icon = self.buttonIconNode.image { let buttonSize = CGSize(width: buttonTextSize.width + icon.size.width + buttonSpacing, height: panelHeight) self.button.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - buttonSize.width) / 2.0), y: 0.0), size: buttonSize) self.buttonIconNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((buttonSize.height - icon.size.height) / 2.0)), size: icon.size) self.buttonTextNode.frame = CGRect(origin: CGPoint(x: icon.size.width + buttonSpacing, y: floorToScreenPixels((buttonSize.height - buttonTextSize.height) / 2.0)), size: buttonTextSize) } transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) return panelHeight } @objc private func buttonPressed() { guard let translationState = self.chatInterfaceState?.translationState else { return } self.interfaceInteraction?.toggleTranslation(translationState.isEnabled ? .original : .translated) } @objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) { guard let translationState = self.chatInterfaceState?.translationState else { return } let context = self.context let presentationData = context.sharedContext.currentPresentationData.with { $0 } var languageCode = presentationData.strings.baseLanguageCode let rawSuffix = "-raw" if languageCode.hasSuffix(rawSuffix) { languageCode = String(languageCode.dropLast(rawSuffix.count)) } let locale = Locale(identifier: languageCode) let fromLanguage: String = locale.localizedString(forLanguageCode: translationState.fromLang) ?? "" var items: [ContextMenuItem] = [] items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_Translation_ChooseLanguage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor) }, action: { c, _ in var subItems: [ContextMenuItem] = [] subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, action: { c, _ in c.popItems() }))) subItems.append(.separator) let enLocale = Locale(identifier: "en") var languages: [(String, String, String)] = [] var addedLanguages = Set() for code in popularTranslationLanguages { if let title = enLocale.localizedString(forLanguageCode: code) { let languageLocale = Locale(identifier: code) let subtitle = languageLocale.localizedString(forLanguageCode: code) ?? title let value = (code, title.capitalized, subtitle.capitalized) if code == languageCode { languages.insert(value, at: 0) } else { languages.append(value) } addedLanguages.insert(code) } } // for code in supportedTranslationLanguages { // if !addedLanguages.contains(code), let title = enLocale.localizedString(forLanguageCode: code) { // let languageLocale = Locale(identifier: code) // let subtitle = languageLocale.localizedString(forLanguageCode: code) ?? title // let value = (code, title.capitalized, subtitle.capitalized) // if code == languageCode { // languages.insert(value, at: 0) // } else { // languages.append(value) // } // } // } for (langCode, title, _) in languages { subItems.append(.action(ContextMenuActionItem(text: title , icon: { theme in if translationState.toLang == langCode { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) } else { return nil } }, action: { [weak self] _, f in f(.default) self?.interfaceInteraction?.changeTranslationLanguage(langCode) }))) } c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) }))) items.append(.separator) items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_Translation_DoNotTranslate(fromLanguage).string, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in c.dismiss(completion: nil) self?.interfaceInteraction?.addDoNotTranslateLanguage(translationState.fromLang) }))) items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_Translation_Hide, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in c.dismiss(completion: nil) self?.interfaceInteraction?.hideTranslationPanel() }))) if let controller = self.interfaceInteraction?.chatController() { let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(TranslationContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) self.interfaceInteraction?.presentGlobalOverlayController(contextController, nil) } } } private final class TranslationContextReferenceContentSource: ContextReferenceContentSource { private let controller: ViewController private let sourceNode: ContextReferenceContentNode init(controller: ViewController, sourceNode: ContextReferenceContentNode) { self.controller = controller self.sourceNode = sourceNode } func transitionInfo() -> ContextControllerReferenceViewInfo? { return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) } }