import Foundation import UIKit import SwiftSignalKit import AsyncDisplayKit import Display import TelegramCore import SyncCore import TelegramPresentationData import ActivityIndicator import AccountContext import AppBundle public struct LanguageSuggestionControllerStrings { let ChooseLanguage: String let Other: String let English: String public init(localization: SuggestedLocalizationInfo) { var chooseLanguage = "Choose Your Language" var other = "Other" var english = "English" for entry in localization.extractedEntries { switch entry { case let .string(key, value): switch key { case "Localization.ChooseLanguage": chooseLanguage = value case "Localization.LanguageOther": other = value case "Localization.EnglishLanguageName": english = value default: break } default: break } } self.ChooseLanguage = chooseLanguage self.Other = other self.English = english } public init(bundle: Bundle?) { var chooseLanguage = "Choose Your Language" var other = "Other" var english = "English" if let bundle = bundle { for key in LanguageSuggestionControllerStrings.keys { let value = bundle.localizedString(forKey: key, value: nil, table: nil) if value != key { switch key { case "Localization.ChooseLanguage": chooseLanguage = value case "Localization.LanguageOther": other = value case "Localization.EnglishLanguageName": english = value default: break } } } } self.ChooseLanguage = chooseLanguage self.Other = other self.English = english } public static let keys: [String] = [ "Localization.ChooseLanguage", "Localization.LanguageOther", "Localization.EnglishLanguageName" ] } private enum LanguageSuggestionItemType { case localization(String) case disclosure case action } private struct LanguageSuggestionItem { public let type: LanguageSuggestionItemType public let title: String public let subtitle: String? public let action: () -> Void public init(type: LanguageSuggestionItemType, title: String, subtitle: String?, action: @escaping () -> Void) { self.type = type self.title = title self.subtitle = subtitle self.action = action } } private final class LanguageSuggestionItemNode: HighlightableButtonNode { private let backgroundNode: ASDisplayNode private let separatorNode: ASDisplayNode private let subtitleNode: ASTextNode private let iconNode: ASImageNode let item: LanguageSuggestionItem override var isSelected: Bool { didSet { if case .localization = self.item.type { self.iconNode.isHidden = !self.isSelected } } } init(theme: PresentationTheme, item: LanguageSuggestionItem) { self.item = item self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true self.backgroundNode.backgroundColor = theme.actionSheet.opaqueItemHighlightedBackgroundColor self.backgroundNode.alpha = 0.0 self.separatorNode = ASDisplayNode() self.separatorNode.backgroundColor = theme.actionSheet.opaqueItemSeparatorColor self.subtitleNode = ASTextNode() self.iconNode = ASImageNode() super.init() self.addSubnode(self.subtitleNode) self.addSubnode(self.separatorNode) self.addSubnode(self.iconNode) var color: UIColor = theme.actionSheet.primaryTextColor var alignment: ASHorizontalAlignment = .left var inset: CGFloat = 19.0 var icon: UIImage? switch item.type { case .action: alignment = .middle color = theme.actionSheet.controlAccentColor inset = 0.0 case .disclosure: icon = PresentationResourcesItemList.disclosureArrowImage(theme) case .localization: icon = PresentationResourcesItemList.checkIconImage(theme) } self.iconNode.image = icon self.contentHorizontalAlignment = alignment self.setTitle(item.title, with: Font.regular(17.0), with: color, for: []) var titleVerticalOffset: CGFloat = 0.0 if let subtitle = item.subtitle { self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(14.0), textColor: theme.actionSheet.secondaryTextColor) titleVerticalOffset = 20.0 } self.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: inset, bottom: titleVerticalOffset, right: 0.0) self.highligthedChanged = { [weak self] value in if let strongSelf = self { if value { if strongSelf.backgroundNode.supernode == nil { strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) } strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity") strongSelf.backgroundNode.alpha = 1.0 } else if !strongSelf.backgroundNode.alpha.isZero { strongSelf.backgroundNode.alpha = 0.0 strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) } } } } override func didLoad() { super.didLoad() self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) } @objc func pressed() { self.item.action() } public func updateLayout(_ constrainedSize: CGSize) -> CGSize { let bounds = CGRect(origin: CGPoint(), size: CGSize(width: constrainedSize.width, height: self.item.subtitle != nil ? 58.0 : 44.0)) self.backgroundNode.frame = bounds let subtitleSize = self.subtitleNode.measure(bounds.size) self.subtitleNode.frame = CGRect(origin: CGPoint(x: 19.0, y: 31.0), size: subtitleSize) self.separatorNode.frame = CGRect(x: 0.0, y: bounds.height - UIScreenPixel, width: bounds.width, height: UIScreenPixel) if let icon = self.iconNode.image { self.iconNode.frame = CGRect(origin: CGPoint(x: bounds.width - icon.size.width - 19.0, y: floorToScreenPixels((bounds.height - icon.size.height) / 2.0)), size: icon.size) } return bounds.size } } private final class LanguageSuggestionAlertContentNode: AlertContentNode { private var validLayout: CGSize? private let titleNode: ASTextNode private let subtitleNode: ASTextNode private let titleSeparatorNode: ASDisplayNode private let activityIndicator: ActivityIndicator private var nodes: [LanguageSuggestionItemNode] private let disposable = MetaDisposable() override var dismissOnOutsideTap: Bool { return self.isUserInteractionEnabled } init(presentationData: PresentationData, strings: LanguageSuggestionControllerStrings, englishStrings: LanguageSuggestionControllerStrings, suggestedLocalization: LocalizationInfo, openSelection: @escaping () -> Void, applyLocalization: @escaping (String, () -> Void) -> Void, dismiss: @escaping () -> Void) { let selectedLocalization = ValuePromise(suggestedLocalization.languageCode, ignoreRepeated: true) self.titleNode = ASTextNode() self.titleNode.attributedText = NSAttributedString(string: strings.ChooseLanguage, font: Font.bold(presentationData.fontSize.baseDisplaySize), textColor: presentationData.theme.actionSheet.primaryTextColor, paragraphAlignment: .center) self.titleNode.maximumNumberOfLines = 2 self.subtitleNode = ASTextNode() self.subtitleNode.attributedText = NSAttributedString(string: englishStrings.ChooseLanguage, font: Font.regular(floor(presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)), textColor: presentationData.theme.actionSheet.secondaryTextColor, paragraphAlignment: .center) self.subtitleNode.maximumNumberOfLines = 2 self.titleSeparatorNode = ASDisplayNode() self.titleSeparatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor self.activityIndicator = ActivityIndicator(type: .custom(presentationData.theme.actionSheet.controlAccentColor, 22.0, 1.0, false)) self.activityIndicator.isHidden = true var items: [LanguageSuggestionItem] = [] items.append(LanguageSuggestionItem(type: .localization(suggestedLocalization.languageCode), title: suggestedLocalization.localizedTitle, subtitle: suggestedLocalization.title, action: { selectedLocalization.set(suggestedLocalization.languageCode) })) items.append(LanguageSuggestionItem(type: .localization("en"), title: strings.English, subtitle: englishStrings.English, action: { selectedLocalization.set("en") })) items.append(LanguageSuggestionItem(type: .disclosure, title: strings.Other, subtitle: englishStrings.Other != strings.Other ? englishStrings.Other : nil, action: { openSelection() })) var applyImpl: (() -> Void)? items.append(LanguageSuggestionItem(type: .action, title: "OK", subtitle: nil, action: { applyImpl?() })) self.nodes = items.map { LanguageSuggestionItemNode(theme: presentationData.theme, item: $0) } super.init() self.addSubnode(self.titleNode) self.addSubnode(self.subtitleNode) self.addSubnode(self.titleSeparatorNode) self.addSubnode(self.activityIndicator) for node in self.nodes { self.addSubnode(node) } self.disposable.set(selectedLocalization.get().start(next: { [weak self] selectedCode in if let strongSelf = self { for node in strongSelf.nodes { if case let .localization(code) = node.item.type { node.isSelected = code == selectedCode } } } })) applyImpl = { [weak self] in if let strongSelf = self { strongSelf.isUserInteractionEnabled = false _ = (selectedLocalization.get() |> take(1)).start(next: { selectedCode in applyLocalization(selectedCode, { [weak self] in if let strongSelf = self { strongSelf.activityIndicator.isHidden = false if let lastNode = strongSelf.nodes.last { lastNode.isHidden = true } } }) }) } } } deinit { self.disposable.dispose() } override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { var size = size size.width = min(size.width, 270.0) self.validLayout = size var origin: CGPoint = CGPoint(x: 0.0, y: 17.0) let titleSize = self.titleNode.measure(size) transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) origin.y += titleSize.height + 3.0 let subtitleSize = self.subtitleNode.measure(size) transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - subtitleSize.width) / 2.0), y: origin.y), size: subtitleSize)) origin.y += subtitleSize.height + 17.0 transition.updateFrame(node: self.titleSeparatorNode, frame: CGRect(x: 0.0, y: origin.y - UIScreenPixel, width: size.width, height: UIScreenPixel)) var lastNodeSize: CGSize? for node in self.nodes { let size = node.updateLayout(size) transition.updateFrame(node: node, frame: CGRect(origin: origin, size: size)) origin.y += size.height lastNodeSize = size } if let lastSize = lastNodeSize { let indicatorSize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0)) transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - indicatorSize.width) / 2.0), y: origin.y - lastSize.height + floorToScreenPixels((lastSize.height - indicatorSize.height) / 2.0)), size: indicatorSize)) } return CGSize(width: size.width, height: origin.y - UIScreenPixel) } } public func languageSuggestionController(context: AccountContext, suggestedLocalization: SuggestedLocalizationInfo, currentLanguageCode: String, openSelection: @escaping () -> Void) -> AlertController? { guard let localization = suggestedLocalization.availableLocalizations.filter({ $0.languageCode == suggestedLocalization.languageCode }).first else { return nil } let presentationData = context.sharedContext.currentPresentationData.with { $0 } let theme = context.sharedContext.presentationData let strings = LanguageSuggestionControllerStrings(localization: suggestedLocalization) guard let mainPath = getAppBundle().path(forResource: "en", ofType: "lproj") else { return nil } let englishStrings = LanguageSuggestionControllerStrings(bundle: Bundle(path: mainPath)) let disposable = MetaDisposable() var dismissImpl: ((Bool) -> Void)? let contentNode = LanguageSuggestionAlertContentNode(presentationData: presentationData, strings: strings, englishStrings: englishStrings, suggestedLocalization: localization, openSelection: { dismissImpl?(true) openSelection() }, applyLocalization: { languageCode, startActivity in if languageCode == currentLanguageCode { dismissImpl?(true) } else { startActivity() disposable.set((downloadAndApplyLocalization(accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, languageCode: languageCode) |> deliverOnMainQueue).start(completed: { dismissImpl?(true) })) } }, dismiss: { dismissImpl?(true) }) let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) dismissImpl = { [weak controller] animated in if animated { controller?.dismissAnimated() } else { controller?.dismiss() } } return controller }