Swiftgram/submodules/LanguageSuggestionUI/Sources/LanguageSuggestionController.swift

374 lines
15 KiB
Swift

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.listsFontSize.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.listsFontSize.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
}