From 9922543d01a3c99805251d8aae1033ab52a573fb Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 1 Feb 2023 00:45:22 +0400 Subject: [PATCH 1/2] Translation improvements --- .../ReactionListContextMenuContent.swift | 9 +- .../ContextUI/Sources/ContextController.swift | 6 + ...tControllerExtractedPresentationNode.swift | 7 +- .../TranslatonSettingsController.swift | 8 +- .../Sources/ChatTranslationPanelNode.swift | 749 +++++++++++++++--- .../Sources/PeerInfo/PeerInfoScreen.swift | 4 +- .../PhotoUpdateConfirmationController.swift | 7 +- 7 files changed, 687 insertions(+), 103 deletions(-) diff --git a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift index 88fbc0a261..ab2398ab1c 100644 --- a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift +++ b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift @@ -95,11 +95,10 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent self.titleLabelNode.attributedText = NSAttributedString(string: presentationData.strings.Common_Back, font: Font.regular(17.0), textColor: presentationData.theme.contextMenu.primaryColor) let titleSize = self.titleLabelNode.updateLayout(CGSize(width: size.width - sideInset - standardIconWidth, height: 100.0)) - self.titleLabelNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) + self.titleLabelNode.frame = CGRect(origin: CGPoint(x: sideInset + 36.0, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) if let iconImage = self.iconNode.image { - let iconWidth = max(standardIconWidth, iconImage.size.width) - let iconFrame = CGRect(origin: CGPoint(x: size.width - iconSideInset - iconWidth + floor((iconWidth - iconImage.size.width) / 2.0), y: floor((size.height - iconImage.size.height) / 2.0)), size: iconImage.size) + let iconFrame = CGRect(origin: CGPoint(x: iconSideInset, y: floor((size.height - iconImage.size.height) / 2.0)), size: iconImage.size) self.iconNode.frame = iconFrame } @@ -1314,10 +1313,10 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent var apparentHeight = topContentHeight if let interactiveTransitionState = self.interactiveTransitionState, let fromTabLayout = tabLayouts[self.currentTabIndex], let toTabLayout = tabLayouts[interactiveTransitionState.toIndex] { - let megedTabLayoutHeight = fromTabLayout.height * (1.0 - interactiveTransitionState.progress) + toTabLayout.height * interactiveTransitionState.progress + let mergedTabLayoutHeight = fromTabLayout.height * (1.0 - interactiveTransitionState.progress) + toTabLayout.height * interactiveTransitionState.progress let megedTabLayoutApparentHeight = fromTabLayout.apparentHeight * (1.0 - interactiveTransitionState.progress) + toTabLayout.apparentHeight * interactiveTransitionState.progress - contentSize.height += megedTabLayoutHeight + contentSize.height += mergedTabLayoutHeight apparentHeight += megedTabLayoutApparentHeight } else if let tabLayout = tabLayouts[self.currentTabIndex] { contentSize.height += tabLayout.height diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index a9ca3c6e41..625e8a1724 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -2215,12 +2215,18 @@ public final class ContextControllerReferenceViewInfo { } public protocol ContextReferenceContentSource: AnyObject { + var keepInPlace: Bool { get } + var shouldBeDismissed: Signal { get } func transitionInfo() -> ContextControllerReferenceViewInfo? } public extension ContextReferenceContentSource { + var keepInPlace: Bool { + return false + } + var shouldBeDismissed: Signal { return .single(false) } diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index 9cf4a3e3d1..cde8ddd138 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -671,7 +671,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if let actionsPositionLock = self.actionsStackNode.topPositionLock { actionsConstrainedHeight = layout.size.height - bottomInset - layout.intrinsicInsets.bottom - actionsPositionLock } else { - actionsConstrainedHeight = layout.size.height - contentTopInset - contentRect.height - contentActionsSpacing - bottomInset - layout.intrinsicInsets.bottom + if case let .reference(reference) = self.source, reference.keepInPlace { + actionsConstrainedHeight = layout.size.height - contentRect.maxY - contentActionsSpacing - bottomInset - layout.intrinsicInsets.bottom + } else { + actionsConstrainedHeight = layout.size.height - contentTopInset - contentRect.height - contentActionsSpacing - bottomInset - layout.intrinsicInsets.bottom + } } let actionsStackPresentation: ContextControllerActionsStackNode.Presentation @@ -755,6 +759,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo var actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.maxY + contentActionsSpacing), size: actionsSize) var contentVerticalOffset: CGFloat = 0.0 + if keepInPlace, case .extracted = self.source { actionsFrame.origin.y = contentRect.minY - contentActionsSpacing - actionsFrame.height let statusBarHeight = (layout.statusBarHeight ?? 0.0) diff --git a/submodules/SettingsUI/Sources/Language Selection/TranslatonSettingsController.swift b/submodules/SettingsUI/Sources/Language Selection/TranslatonSettingsController.swift index 3a7af62bca..0f418155d1 100644 --- a/submodules/SettingsUI/Sources/Language Selection/TranslatonSettingsController.swift +++ b/submodules/SettingsUI/Sources/Language Selection/TranslatonSettingsController.swift @@ -77,7 +77,13 @@ private func translationSettingsControllerEntries(theme: PresentationTheme, stri if let ignoredLanguages = settings.ignoredLanguages { selectedLanguages = Set(ignoredLanguages) } else { - selectedLanguages = Set([strings.baseLanguageCode]) + var langCode = strings.baseLanguageCode + if langCode == "nb" { + langCode = "no" + } else if langCode == "pt-br" { + langCode = "pt" + } + selectedLanguages = Set([langCode]) for language in systemLanguageCodes() { selectedLanguages.insert(language) } diff --git a/submodules/TelegramUI/Sources/ChatTranslationPanelNode.swift b/submodules/TelegramUI/Sources/ChatTranslationPanelNode.swift index 03287cc954..766efdf8b4 100644 --- a/submodules/TelegramUI/Sources/ChatTranslationPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTranslationPanelNode.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Display import AsyncDisplayKit +import SwiftSignalKit import Postbox import TelegramCore import TelegramPresentationData @@ -14,6 +15,7 @@ import AccountContext import MoreButtonNode import ContextUI import TranslateUI +import TelegramUIPreferences final class ChatTranslationPanelNode: ASDisplayNode { private let context: AccountContext @@ -116,17 +118,17 @@ final class ChatTranslationPanelNode: ASDisplayNode { } let toLang = interfaceState.translationState?.toLang ?? languageCode - let key = "Translation.Language.\(toLang)" - var toLanguage: String? + let translateTitle: String if let string = interfaceState.strings.primaryComponent.dict[key] { - toLanguage = string + translateTitle = interfaceState.strings.Conversation_Translation_TranslateTo(string).string } else { let languageLocale = Locale(identifier: languageCode) - toLanguage = languageLocale.localizedString(forLanguageCode: toLang) ?? "" + let toLanguage = languageLocale.localizedString(forLanguageCode: toLang) ?? "" + translateTitle = interfaceState.strings.Conversation_Translation_TranslateToOther(toLanguage).string } - let buttonText = interfaceState.translationState?.isEnabled == true ? interfaceState.strings.Conversation_Translation_ShowOriginal : interfaceState.strings.Conversation_Translation_TranslateTo(toLanguage ?? "").string + let buttonText = interfaceState.translationState?.isEnabled == true ? interfaceState.strings.Conversation_Translation_ShowOriginal : translateTitle self.buttonTextNode.attributedText = NSAttributedString(string: buttonText, font: Font.regular(17.0), textColor: interfaceState.theme.rootController.navigationBar.accentTextColor) } @@ -174,107 +176,129 @@ final class ChatTranslationPanelNode: ASDisplayNode { languageCode = String(languageCode.dropLast(rawSuffix.count)) } + let doNotTranslateTitle: String let fromLang = translationState.fromLang let key = "Translation.Language.\(fromLang)" - let fromLanguage: String if let string = presentationData.strings.primaryComponent.dict[key] { - fromLanguage = string + doNotTranslateTitle = presentationData.strings.Conversation_Translation_DoNotTranslate(string).string } else { let languageLocale = Locale(identifier: languageCode) - fromLanguage = languageLocale.localizedString(forLanguageCode: fromLang) ?? "" + let fromLanguage = languageLocale.localizedString(forLanguageCode: fromLang) ?? "" + doNotTranslateTitle = presentationData.strings.Conversation_Translation_DoNotTranslateOther(fromLanguage).string } + + let items: Signal = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) + |> take(1) + |> map { sharedData -> ContextController.Items in + let settings: TranslationSettings + if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { + settings = current + } else { + settings = TranslationSettings.defaultSettings + } + + 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 addedLanguages = Set() - 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.ChatList_Context_Back, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) - }, iconPosition: .left, action: { c, _ in - c.popItems() - }))) - subItems.append(.separator) - - let enLocale = Locale(identifier: "en") - var languages: [(String, String)] = [] - var addedLanguages = Set() - - var topLanguages: [String] = [] - var langCode = languageCode - if langCode == "nb" { - langCode = "no" - } else if langCode == "pt-br" { - langCode = "pt" - } - topLanguages.append(langCode) - topLanguages.append(contentsOf: popularTranslationLanguages) - - for code in topLanguages { - 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, subtitle.capitalized) - if code == languageCode { - languages.insert(value, at: 0) - } else { - languages.append(value) - } - addedLanguages.insert(code) + var topLanguages: [String] = [] + var langCode = languageCode + if langCode == "nb" { + langCode = "no" + } else if langCode == "pt-br" { + langCode = "pt" } - } - -// 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 + + var selectedLanguages: Set + if let ignoredLanguages = settings.ignoredLanguages { + selectedLanguages = Set(ignoredLanguages) + } else { + selectedLanguages = Set([langCode]) + for language in systemLanguageCodes() { + selectedLanguages.insert(language) } - }, action: { [weak self] _, f in - f(.default) - - self?.interfaceInteraction?.changeTranslationLanguage(langCode) - }))) - } + } + for code in supportedTranslationLanguages { + if selectedLanguages.contains(code) { + topLanguages.append(code) + } + } + + topLanguages.append(contentsOf: popularTranslationLanguages) + + var languages: [(String, String)] = [] + let languageLocale = Locale(identifier: langCode) + + for code in topLanguages { + if !addedLanguages.contains(code) { + let displayTitle = languageLocale.localizedString(forLanguageCode: code) ?? "" + let value = (code, displayTitle) + if code == languageCode { + languages.insert(value, at: 0) + } else { + languages.append(value) + } + addedLanguages.insert(code) + } + } + + for code in supportedTranslationLanguages { + if !addedLanguages.contains(code) { + let displayTitle = languageLocale.localizedString(forLanguageCode: code) ?? "" + let value = (code, displayTitle) + if code == languageCode { + languages.insert(value, at: 0) + } else { + languages.append(value) + } + addedLanguages.insert(code) + } + } + + c.pushItems(items: .single(ContextController.Items( + content: .custom( + TranslationLanguagesContextMenuContent( + context: self.context, + languages: languages, back: { [weak c] in + c?.popItems() + }, selectLanguage: { [weak self, weak c] language in + c?.dismiss(completion: { + guard let strongSelf = self else { + return + } + strongSelf.interfaceInteraction?.changeTranslationLanguage(language) + }) + } + ) + ) + ))) + }))) - 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) + items.append(.separator) - 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) + items.append(.action(ContextMenuActionItem(text: doNotTranslateTitle, 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() + }))) + + return ContextController.Items(content: .list(items)) + } - 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) + let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(TranslationContextReferenceContentSource(controller: controller, sourceNode: node)), items: items, gesture: gesture) self.interfaceInteraction?.presentGlobalOverlayController(contextController, nil) } } @@ -284,6 +308,10 @@ private final class TranslationContextReferenceContentSource: ContextReferenceCo private let controller: ViewController private let sourceNode: ContextReferenceContentNode + var keepInPlace: Bool { + return true + } + init(controller: ViewController, sourceNode: ContextReferenceContentNode) { self.controller = controller self.sourceNode = sourceNode @@ -293,3 +321,538 @@ private final class TranslationContextReferenceContentSource: ContextReferenceCo return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) } } + +private final class TranslationLanguagesContextMenuContent: ContextControllerItemsContent { + private final class BackButtonNode: HighlightTrackingButtonNode { + let highlightBackgroundNode: ASDisplayNode + let titleLabelNode: ImmediateTextNode + let separatorNode: ASDisplayNode + let iconNode: ASImageNode + + var action: (() -> Void)? + + private var theme: PresentationTheme? + + init() { + self.highlightBackgroundNode = ASDisplayNode() + self.highlightBackgroundNode.isAccessibilityElement = false + self.highlightBackgroundNode.alpha = 0.0 + + self.titleLabelNode = ImmediateTextNode() + self.titleLabelNode.isAccessibilityElement = false + self.titleLabelNode.maximumNumberOfLines = 1 + self.titleLabelNode.isUserInteractionEnabled = false + + self.iconNode = ASImageNode() + self.iconNode.isAccessibilityElement = false + + self.separatorNode = ASDisplayNode() + self.separatorNode.isAccessibilityElement = false + + super.init() + + self.addSubnode(self.separatorNode) + self.addSubnode(self.highlightBackgroundNode) + self.addSubnode(self.titleLabelNode) + self.addSubnode(self.iconNode) + + self.isAccessibilityElement = true + + self.highligthedChanged = { [weak self] highlighted in + guard let strongSelf = self else { + return + } + if highlighted { + strongSelf.highlightBackgroundNode.alpha = 1.0 + } else { + let previousAlpha = strongSelf.highlightBackgroundNode.alpha + strongSelf.highlightBackgroundNode.alpha = 0.0 + strongSelf.highlightBackgroundNode.layer.animateAlpha(from: previousAlpha, to: 0.0, duration: 0.2) + } + } + + self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + } + + @objc private func pressed() { + self.action?() + } + + func update(size: CGSize, presentationData: PresentationData, isLast: Bool) { + let standardIconWidth: CGFloat = 32.0 + let sideInset: CGFloat = 16.0 + let iconSideInset: CGFloat = 12.0 + + if self.theme !== presentationData.theme { + self.theme = presentationData.theme + self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: presentationData.theme.contextMenu.primaryColor) + + self.accessibilityLabel = presentationData.strings.Common_Back + } + + self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor + self.separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor + + self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size) + + self.titleLabelNode.attributedText = NSAttributedString(string: presentationData.strings.Common_Back, font: Font.regular(17.0), textColor: presentationData.theme.contextMenu.primaryColor) + let titleSize = self.titleLabelNode.updateLayout(CGSize(width: size.width - sideInset - standardIconWidth, height: 100.0)) + self.titleLabelNode.frame = CGRect(origin: CGPoint(x: sideInset + 36.0, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) + + if let iconImage = self.iconNode.image { + let iconFrame = CGRect(origin: CGPoint(x: iconSideInset, y: floor((size.height - iconImage.size.height) / 2.0)), size: iconImage.size) + self.iconNode.frame = iconFrame + } + + self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)) + self.separatorNode.isHidden = isLast + } + } + + private final class LanguagesListNode: ASDisplayNode, UIScrollViewDelegate { + private final class ItemNode: HighlightTrackingButtonNode { + let context: AccountContext + let highlightBackgroundNode: ASDisplayNode + let titleLabelNode: ImmediateTextNode + let separatorNode: ASDisplayNode + + let action: () -> Void + + private var language: String? + + init(context: AccountContext, action: @escaping () -> Void) { + self.action = action + self.context = context + + self.highlightBackgroundNode = ASDisplayNode() + self.highlightBackgroundNode.isAccessibilityElement = false + self.highlightBackgroundNode.alpha = 0.0 + + self.titleLabelNode = ImmediateTextNode() + self.titleLabelNode.isAccessibilityElement = false + self.titleLabelNode.maximumNumberOfLines = 1 + self.titleLabelNode.isUserInteractionEnabled = false + + self.separatorNode = ASDisplayNode() + self.separatorNode.isAccessibilityElement = false + + super.init() + + self.isAccessibilityElement = true + + self.addSubnode(self.separatorNode) + self.addSubnode(self.highlightBackgroundNode) + self.addSubnode(self.titleLabelNode) + + self.highligthedChanged = { [weak self] highlighted in + guard let strongSelf = self else { + return + } + if highlighted { + strongSelf.highlightBackgroundNode.alpha = 1.0 + } else { + let previousAlpha = strongSelf.highlightBackgroundNode.alpha + strongSelf.highlightBackgroundNode.alpha = 0.0 + strongSelf.highlightBackgroundNode.layer.animateAlpha(from: previousAlpha, to: 0.0, duration: 0.2) + } + } + + self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + } + + @objc private func pressed() { + self.action() + } + + private var displayTitle: String? + func update(size: CGSize, presentationData: PresentationData, language: String, displayTitle: String, isLast: Bool, syncronousLoad: Bool) { + let sideInset: CGFloat = 16.0 + + if self.language != language { + self.language = language + self.displayTitle = displayTitle + + self.accessibilityLabel = "\(displayTitle)" + } + + self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor + self.separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor + + self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size) + + self.titleLabelNode.attributedText = NSAttributedString(string: self.displayTitle ?? "", font: Font.regular(17.0), textColor: presentationData.theme.contextMenu.primaryColor) + let maxTextWidth: CGFloat = size.width - sideInset + + let titleSize = self.titleLabelNode.updateLayout(CGSize(width: maxTextWidth, height: 100.0)) + let titleFrame = CGRect(origin: CGPoint(x: sideInset, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) + self.titleLabelNode.frame = titleFrame + + self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: CGSize(width: size.width, height: UIScreenPixel)) + self.separatorNode.isHidden = isLast + } + } + + private let context: AccountContext + private let languages: [(String, String)] + private let requestUpdate: (LanguagesListNode, ContainedViewLayoutTransition) -> Void + private let requestUpdateApparentHeight: (LanguagesListNode, ContainedViewLayoutTransition) -> Void + private let selectLanguage: (String) -> Void + + private let scrollNode: ASScrollNode + private var ignoreScrolling: Bool = false + private var animateIn: Bool = false + private var bottomScrollInset: CGFloat = 0.0 + + private var presentationData: PresentationData? + private var currentSize: CGSize? + private var apparentHeight: CGFloat = 0.0 + + private var itemNodes: [Int: ItemNode] = [:] + + init( + context: AccountContext, + languages: [(String, String)], + requestUpdate: @escaping (LanguagesListNode, ContainedViewLayoutTransition) -> Void, + requestUpdateApparentHeight: @escaping (LanguagesListNode, ContainedViewLayoutTransition) -> Void, + selectLanguage: @escaping (String) -> Void + ) { + self.context = context + self.languages = languages + self.requestUpdate = requestUpdate + self.requestUpdateApparentHeight = requestUpdateApparentHeight + self.selectLanguage = selectLanguage + + self.scrollNode = ASScrollNode() + self.scrollNode.canCancelAllTouchesInViews = true + self.scrollNode.view.delaysContentTouches = false + self.scrollNode.view.showsVerticalScrollIndicator = false + if #available(iOS 11.0, *) { + self.scrollNode.view.contentInsetAdjustmentBehavior = .never + } + self.scrollNode.clipsToBounds = false + + super.init() + + self.addSubnode(self.scrollNode) + self.scrollNode.view.delegate = self + + self.clipsToBounds = true + +// self.stateDisposable = (self.listContext.state +// |> deliverOnMainQueue).start(next: { [weak self] state in +// guard let strongSelf = self else { +// return +// } +// let updatedState = ItemsState(listState: state, readStats: strongSelf.state.readStats) +// var animateIn = false +// if strongSelf.state.item(at: 0) == nil && updatedState.item(at: 0) != nil { +// animateIn = true +// } +// strongSelf.state = updatedState +// strongSelf.animateIn = true +// strongSelf.requestUpdate(strongSelf, animateIn ? .animated(duration: 0.2, curve: .easeInOut) : .immediate) +// if animateIn { +// for (_, itemNode) in strongSelf.itemNodes { +// itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) +// } +// } +// }) + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if self.ignoreScrolling { + return + } + self.updateVisibleItems(animated: false, syncronousLoad: false) + + if let size = self.currentSize { + var apparentHeight = -self.scrollNode.view.contentOffset.y + self.scrollNode.view.contentSize.height + apparentHeight = max(apparentHeight, 44.0) + apparentHeight = min(apparentHeight, size.height) + if self.apparentHeight != apparentHeight { + self.apparentHeight = apparentHeight + + self.requestUpdateApparentHeight(self, .immediate) + } + } + } + + private func updateVisibleItems(animated: Bool, syncronousLoad: Bool) { + guard let size = self.currentSize else { + return + } + guard let presentationData = self.presentationData else { + return + } + let itemHeight: CGFloat = 44.0 + let visibleBounds = self.scrollNode.bounds.insetBy(dx: 0.0, dy: -180.0) + + var validIds = Set() + + let minVisibleIndex = max(0, Int(floor(visibleBounds.minY / itemHeight))) + let maxVisibleIndex = Int(ceil(visibleBounds.maxY / itemHeight)) + + if minVisibleIndex <= maxVisibleIndex { + for index in minVisibleIndex ... maxVisibleIndex { + let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: CGFloat(index) * itemHeight), size: CGSize(width: size.width, height: itemHeight)) + + if index < self.languages.count { + let (languageCode, displayTitle) = self.languages[index] + validIds.insert(index) + + let itemNode: ItemNode + if let current = self.itemNodes[index] { + itemNode = current + } else { + let selectLanguage = self.selectLanguage + itemNode = ItemNode(context: self.context, action: { + selectLanguage(languageCode) + }) + self.itemNodes[index] = itemNode + self.scrollNode.addSubnode(itemNode) + } + + itemNode.update(size: itemFrame.size, presentationData: presentationData, language: languageCode, displayTitle: displayTitle, isLast: index == self.languages.count - 1, syncronousLoad: syncronousLoad) + itemNode.frame = itemFrame + } + } + } + + var removeIds: [Int] = [] + for (id, itemNode) in self.itemNodes { + if !validIds.contains(id) { + removeIds.append(id) + itemNode.removeFromSupernode() + } + } + for id in removeIds { + self.itemNodes.removeValue(forKey: id) + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + var extendedScrollNodeFrame = self.scrollNode.frame + extendedScrollNodeFrame.size.height += self.bottomScrollInset + + if extendedScrollNodeFrame.contains(point) { + return self.scrollNode.view.hitTest(self.view.convert(point, to: self.scrollNode.view), with: event) + } + + return super.hitTest(point, with: event) + } + + func update(presentationData: PresentationData, constrainedSize: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> (height: CGFloat, apparentHeight: CGFloat) { + let itemHeight: CGFloat = 44.0 + + self.presentationData = presentationData + + let size = CGSize(width: constrainedSize.width, height: CGFloat(self.languages.count) * itemHeight) + + let containerSize = CGSize(width: size.width, height: min(constrainedSize.height, size.height)) + self.currentSize = containerSize + + self.ignoreScrolling = true + + if self.scrollNode.frame != CGRect(origin: CGPoint(), size: containerSize) { + self.scrollNode.frame = CGRect(origin: CGPoint(), size: containerSize) + } + if self.scrollNode.view.contentInset.bottom != bottomInset { + self.scrollNode.view.contentInset.bottom = bottomInset + } + self.bottomScrollInset = bottomInset + let scrollContentSize = CGSize(width: size.width, height: size.height) + if self.scrollNode.view.contentSize != scrollContentSize { + self.scrollNode.view.contentSize = scrollContentSize + } + self.ignoreScrolling = false + + self.updateVisibleItems(animated: transition.isAnimated, syncronousLoad: !transition.isAnimated) + + self.animateIn = false + + var apparentHeight = -self.scrollNode.view.contentOffset.y + self.scrollNode.view.contentSize.height + apparentHeight = max(apparentHeight, 44.0) + apparentHeight = min(apparentHeight, containerSize.height) + self.apparentHeight = apparentHeight + + return (containerSize.height, apparentHeight) + } + } + + final class ItemsNode: ASDisplayNode, ContextControllerItemsNode { + private let context: AccountContext + private let languages: [(String, String)] + private let requestUpdate: (ContainedViewLayoutTransition) -> Void + private let requestUpdateApparentHeight: (ContainedViewLayoutTransition) -> Void + + private var presentationData: PresentationData + + private var backButtonNode: BackButtonNode? + private var separatorNode: ASDisplayNode? + + private let currentTabIndex: Int = 0 + private var visibleTabNodes: [Int: LanguagesListNode] = [:] + + private let selectLanguage: (String) -> Void + + private(set) var apparentHeight: CGFloat = 0.0 + + init( + context: AccountContext, + languages: [(String, String)], + requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void, + requestUpdateApparentHeight: @escaping (ContainedViewLayoutTransition) -> Void, + back: (() -> Void)?, + selectLanguage: @escaping (String) -> Void + ) { + self.context = context + self.languages = languages + self.selectLanguage = selectLanguage + self.presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + + self.requestUpdate = requestUpdate + self.requestUpdateApparentHeight = requestUpdateApparentHeight + + if let back = back { + self.backButtonNode = BackButtonNode() + self.backButtonNode?.action = { + back() + } + } + + super.init() + + if self.backButtonNode != nil { + self.separatorNode = ASDisplayNode() + } + + if let backButtonNode = self.backButtonNode { + self.addSubnode(backButtonNode) + } + if let separatorNode = self.separatorNode { + self.addSubnode(separatorNode) + } + } + + func update(presentationData: PresentationData, constrainedWidth: CGFloat, maxHeight: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> (cleanSize: CGSize, apparentHeight: CGFloat) { + let constrainedSize = CGSize(width: min(260.0, constrainedWidth), height: min(604.0, maxHeight)) + + var topContentHeight: CGFloat = 0.0 + if let backButtonNode = self.backButtonNode { + let backButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: topContentHeight), size: CGSize(width: constrainedSize.width, height: 44.0)) + backButtonNode.update(size: backButtonFrame.size, presentationData: self.presentationData, isLast: true) + transition.updateFrame(node: backButtonNode, frame: backButtonFrame) + topContentHeight += backButtonFrame.height + } + if let separatorNode = self.separatorNode { + let separatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topContentHeight), size: CGSize(width: constrainedSize.width, height: 7.0)) + separatorNode.backgroundColor = self.presentationData.theme.contextMenu.sectionSeparatorColor + transition.updateFrame(node: separatorNode, frame: separatorFrame) + topContentHeight += separatorFrame.height + } + + var tabLayouts: [Int: (height: CGFloat, apparentHeight: CGFloat)] = [:] + + var visibleIndices: [Int] = [] + visibleIndices.append(self.currentTabIndex) + + let previousVisibleTabFrames: [(Int, CGRect)] = self.visibleTabNodes.map { key, value -> (Int, CGRect) in + return (key, value.frame) + } + + for index in visibleIndices { + var tabTransition = transition + let tabNode: LanguagesListNode + var initialReferenceFrame: CGRect? + if let current = self.visibleTabNodes[index] { + tabNode = current + } else { + for (previousIndex, previousFrame) in previousVisibleTabFrames { + if index > previousIndex { + initialReferenceFrame = previousFrame.offsetBy(dx: constrainedSize.width, dy: 0.0) + } else { + initialReferenceFrame = previousFrame.offsetBy(dx: -constrainedSize.width, dy: 0.0) + } + break + } + + tabNode = LanguagesListNode( + context: self.context, + languages: self.languages, + requestUpdate: { [weak self] tab, transition in + guard let strongSelf = self else { + return + } + if strongSelf.visibleTabNodes.contains(where: { $0.value === tab }) { + strongSelf.requestUpdate(transition) + } + }, + requestUpdateApparentHeight: { [weak self] tab, transition in + guard let strongSelf = self else { + return + } + if strongSelf.visibleTabNodes.contains(where: { $0.value === tab }) { + strongSelf.requestUpdateApparentHeight(transition) + } + }, + selectLanguage: self.selectLanguage + ) + self.addSubnode(tabNode) + self.visibleTabNodes[index] = tabNode + tabTransition = .immediate + } + + let tabLayout = tabNode.update(presentationData: presentationData, constrainedSize: CGSize(width: constrainedSize.width, height: constrainedSize.height - topContentHeight), bottomInset: bottomInset, transition: tabTransition) + tabLayouts[index] = tabLayout + let currentFractionalTabIndex = CGFloat(self.currentTabIndex) + let xOffset: CGFloat = (CGFloat(index) - currentFractionalTabIndex) * constrainedSize.width + let tabFrame = CGRect(origin: CGPoint(x: xOffset, y: topContentHeight), size: CGSize(width: constrainedSize.width, height: tabLayout.height)) + tabTransition.updateFrame(node: tabNode, frame: tabFrame) + if let initialReferenceFrame = initialReferenceFrame { + transition.animatePositionAdditive(node: tabNode, offset: CGPoint(x: initialReferenceFrame.minX - tabFrame.minX, y: 0.0)) + } + } + + var contentSize = CGSize(width: constrainedSize.width, height: topContentHeight) + var apparentHeight = topContentHeight + + if let tabLayout = tabLayouts[self.currentTabIndex] { + contentSize.height += tabLayout.height + apparentHeight += tabLayout.apparentHeight + } + + return (contentSize, apparentHeight) + } + } + + let context: AccountContext + let languages: [(String, String)] + let back: (() -> Void)? + let selectLanguage: (String) -> Void + + public init( + context: AccountContext, + languages: [(String, String)], + back: (() -> Void)?, + selectLanguage: @escaping (String) -> Void + ) { + self.context = context + self.languages = languages + self.back = back + self.selectLanguage = selectLanguage + } + + func node( + requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void, + requestUpdateApparentHeight: @escaping (ContainedViewLayoutTransition) -> Void + ) -> ContextControllerItemsNode { + return ItemsNode( + context: self.context, + languages: self.languages, + requestUpdate: requestUpdate, + requestUpdateApparentHeight: requestUpdateApparentHeight, + back: self.back, + selectLanguage: self.selectLanguage + ) + } +} diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index a1b192f639..ad580036da 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -7334,6 +7334,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate completion(nil) } } + var isFromEditor = false mixin.requestAvatarEditor = { [weak self] imageCompletion, videoCompletion in guard let strongSelf = self, let imageCompletion, let videoCompletion else { return @@ -7356,6 +7357,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate controller.imageCompletion = imageCompletion controller.videoCompletion = videoCompletion (strongSelf.controller?.navigationController?.topViewController as? ViewController)?.push(controller) + isFromEditor = true } if let confirmationTextPhoto, let confirmationAction { @@ -7371,7 +7373,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate if let confirmationTextVideo, let confirmationAction { mixin.willFinishWithVideo = { [weak self] image, commit in if let strongSelf = self, let image { - let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextVideo, doneTitle: confirmationAction, commit: { + let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextVideo, doneTitle: confirmationAction, isDark: !isFromEditor, commit: { commit?() }) (strongSelf.controller?.navigationController?.topViewController as? ViewController)?.presentInGlobalOverlay(controller) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PhotoUpdateConfirmationController.swift b/submodules/TelegramUI/Sources/PeerInfo/PhotoUpdateConfirmationController.swift index 61046ab4e8..4e25766949 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PhotoUpdateConfirmationController.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PhotoUpdateConfirmationController.swift @@ -216,9 +216,12 @@ private final class PhotoUpdateConfirmationAlertContentNode: AlertContentNode { } } -func photoUpdateConfirmationController(context: AccountContext, peer: EnginePeer, image: UIImage, text: String, doneTitle: String, commit: @escaping () -> Void) -> AlertController { +func photoUpdateConfirmationController(context: AccountContext, peer: EnginePeer, image: UIImage, text: String, doneTitle: String, isDark: Bool = true, commit: @escaping () -> Void) -> AlertController { let theme = defaultDarkColorPresentationTheme - let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: theme) + var presentationData = context.sharedContext.currentPresentationData.with { $0 } + if isDark { + presentationData = presentationData.withUpdated(theme: theme) + } let strings = presentationData.strings var dismissImpl: ((Bool) -> Void)? From 67f34ab53d911eb5fc64d104d7e1608f06374d11 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 1 Feb 2023 02:51:21 +0400 Subject: [PATCH 2/2] Fix allow write setting when adding attach menu bot --- .../TwoStepVerificationUnlockController.swift | 2 +- submodules/WebUI/Sources/WebAppAlertContentNode.swift | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift b/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift index 2d53114931..ac7aa8d89e 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift @@ -226,7 +226,7 @@ private func twoStepVerificationUnlockSettingsControllerEntries(presentationData if remainingSeconds <= 0 { text += "[" + presentationData.strings.TwoStepAuth_ResetAction + "](reset)" } else { - text.append(presentationData.strings.TwoStepAuth_ResetPendingText(timeIntervalString(strings: presentationData.strings, value: remainingSeconds)).string) + text.append(presentationData.strings.TwoStepAuth_ResetPendingText(timeIntervalString(strings: presentationData.strings, value: remainingSeconds, usage: .afterTime)).string) text.append("\n[\(presentationData.strings.TwoStepAuth_CancelResetTitle)](declineReset)") } } else { diff --git a/submodules/WebUI/Sources/WebAppAlertContentNode.swift b/submodules/WebUI/Sources/WebAppAlertContentNode.swift index d408b8f675..a3da723cdd 100644 --- a/submodules/WebUI/Sources/WebAppAlertContentNode.swift +++ b/submodules/WebUI/Sources/WebAppAlertContentNode.swift @@ -108,7 +108,6 @@ private final class WebAppAlertContentNode: AlertContentNode { self.addSubnode(self.allowWriteLabelNode) } - self.addSubnode(self.actionNodesSeparator) for actionNode in self.actionNodes { @@ -316,10 +315,14 @@ public func addWebAppToAttachmentController(context: AccountContext, peerName: S var contentNode: WebAppAlertContentNode? let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { dismissImpl?(true) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_AddToAttachmentAdd, action: { + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_AddToAttachmentAdd, action: { [weak contentNode] in dismissImpl?(true) - completion(true) + if requestWriteAccess, let allowWriteAccess = contentNode?.allowWriteAccess { + completion(allowWriteAccess) + } else { + completion(false) + } })] contentNode = WebAppAlertContentNode(account: context.account, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, peerName: peerName, icons: icons, requestWriteAccess: requestWriteAccess, actions: actions)