mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
901 lines
57 KiB
Swift
901 lines
57 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import Postbox
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import MtProtoKit
|
|
import MessageUI
|
|
import CoreTelephony
|
|
import TelegramPresentationData
|
|
import TextFormat
|
|
import AccountContext
|
|
import CountrySelectionUI
|
|
import SettingsUI
|
|
import PhoneNumberFormat
|
|
import LegacyComponents
|
|
import LegacyMediaPickerUI
|
|
import PasswordSetupUI
|
|
|
|
private enum InnerState: Equatable {
|
|
case state(UnauthorizedAccountStateContents)
|
|
case authorized
|
|
}
|
|
|
|
public final class AuthorizationSequenceController: NavigationController, MFMailComposeViewControllerDelegate {
|
|
static func navigationBarTheme(_ theme: PresentationTheme) -> NavigationBarTheme {
|
|
return NavigationBarTheme(buttonColor: theme.intro.accentTextColor, disabledButtonColor: theme.intro.disabledTextColor, primaryTextColor: theme.intro.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: theme.rootController.navigationBar.badgeBackgroundColor, badgeStrokeColor: theme.rootController.navigationBar.badgeStrokeColor, badgeTextColor: theme.rootController.navigationBar.badgeTextColor)
|
|
}
|
|
|
|
private let sharedContext: SharedAccountContext
|
|
private var account: UnauthorizedAccount
|
|
private let otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)])
|
|
private let apiId: Int32
|
|
private let apiHash: String
|
|
public var presentationData: PresentationData
|
|
private let openUrl: (String) -> Void
|
|
private let authorizationCompleted: () -> Void
|
|
|
|
private var stateDisposable: Disposable?
|
|
private let actionDisposable = MetaDisposable()
|
|
|
|
private var didPlayPresentationAnimation = false
|
|
|
|
private let _ready = Promise<Bool>()
|
|
override public var ready: Promise<Bool> {
|
|
return self._ready
|
|
}
|
|
private var didSetReady = false
|
|
|
|
public init(sharedContext: SharedAccountContext, account: UnauthorizedAccount, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]), presentationData: PresentationData, openUrl: @escaping (String) -> Void, apiId: Int32, apiHash: String, authorizationCompleted: @escaping () -> Void) {
|
|
self.sharedContext = sharedContext
|
|
self.account = account
|
|
self.otherAccountPhoneNumbers = otherAccountPhoneNumbers
|
|
self.apiId = apiId
|
|
self.apiHash = apiHash
|
|
self.presentationData = presentationData
|
|
self.openUrl = openUrl
|
|
self.authorizationCompleted = authorizationCompleted
|
|
|
|
let navigationStatusBar: NavigationStatusBarStyle
|
|
switch presentationData.theme.rootController.statusBarStyle {
|
|
case .black:
|
|
navigationStatusBar = .black
|
|
case .white:
|
|
navigationStatusBar = .white
|
|
}
|
|
|
|
super.init(mode: .single, theme: NavigationControllerTheme(statusBar: navigationStatusBar, navigationBar: AuthorizationSequenceController.navigationBarTheme(presentationData.theme), emptyAreaColor: .black))
|
|
|
|
self.stateDisposable = (account.postbox.stateView()
|
|
|> map { view -> InnerState in
|
|
if let _ = view.state as? AuthorizedAccountState {
|
|
return .authorized
|
|
} else if let state = view.state as? UnauthorizedAccountState {
|
|
return .state(state.contents)
|
|
} else {
|
|
return .state(.empty)
|
|
}
|
|
}
|
|
|> distinctUntilChanged
|
|
|> deliverOnMainQueue).start(next: { [weak self] state in
|
|
self?.updateState(state: state)
|
|
})
|
|
}
|
|
|
|
required public init(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
self.stateDisposable?.dispose()
|
|
self.actionDisposable.dispose()
|
|
}
|
|
|
|
override public func loadView() {
|
|
super.loadView()
|
|
self.view.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
|
}
|
|
|
|
private func splashController() -> AuthorizationSequenceSplashController {
|
|
var currentController: AuthorizationSequenceSplashController?
|
|
for c in self.viewControllers {
|
|
if let c = c as? AuthorizationSequenceSplashController {
|
|
currentController = c
|
|
break
|
|
}
|
|
}
|
|
let controller: AuthorizationSequenceSplashController
|
|
if let currentController = currentController {
|
|
controller = currentController
|
|
} else {
|
|
controller = AuthorizationSequenceSplashController(accountManager: self.sharedContext.accountManager, account: self.account, theme: self.presentationData.theme)
|
|
controller.nextPressed = { [weak self] strings in
|
|
if let strongSelf = self {
|
|
if let strings = strings {
|
|
strongSelf.presentationData = strongSelf.presentationData.withStrings(strings)
|
|
}
|
|
let masterDatacenterId = strongSelf.account.masterDatacenterId
|
|
let isTestingEnvironment = strongSelf.account.testingEnvironment
|
|
|
|
let countryCode = defaultCountryCode()
|
|
|
|
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
|
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: isTestingEnvironment, masterDatacenterId: masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: "")))
|
|
}).start()
|
|
}
|
|
}
|
|
}
|
|
return controller
|
|
}
|
|
|
|
private func phoneEntryController(countryCode: Int32, number: String) -> AuthorizationSequencePhoneEntryController {
|
|
var currentController: AuthorizationSequencePhoneEntryController?
|
|
for c in self.viewControllers {
|
|
if let c = c as? AuthorizationSequencePhoneEntryController {
|
|
currentController = c
|
|
break
|
|
}
|
|
}
|
|
let controller: AuthorizationSequencePhoneEntryController
|
|
if let currentController = currentController {
|
|
controller = currentController
|
|
} else {
|
|
controller = AuthorizationSequencePhoneEntryController(sharedContext: self.sharedContext, account: self.account, isTestingEnvironment: self.account.testingEnvironment, otherAccountPhoneNumbers: self.otherAccountPhoneNumbers, network: self.account.network, presentationData: self.presentationData, openUrl: { [weak self] url in
|
|
self?.openUrl(url)
|
|
}, back: { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
if !strongSelf.otherAccountPhoneNumbers.1.isEmpty {
|
|
let _ = (strongSelf.sharedContext.accountManager.transaction { transaction -> Void in
|
|
transaction.removeAuth()
|
|
}).start()
|
|
} else {
|
|
let _ = strongSelf.account.postbox.transaction({ transaction -> Void in
|
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .empty))
|
|
}).start()
|
|
}
|
|
})
|
|
controller.accountUpdated = { [weak self] updatedAccount in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.account = updatedAccount
|
|
}
|
|
controller.loginWithNumber = { [weak self, weak controller] number, syncContacts in
|
|
if let strongSelf = self {
|
|
controller?.inProgress = true
|
|
strongSelf.actionDisposable.set((sendAuthorizationCode(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, phoneNumber: number, apiId: strongSelf.apiId, apiHash: strongSelf.apiHash, syncContacts: syncContacts) |> deliverOnMainQueue).start(next: { [weak self] account in
|
|
if let strongSelf = self {
|
|
controller?.inProgress = false
|
|
strongSelf.account = account
|
|
}
|
|
}, error: { error in
|
|
if let strongSelf = self, let controller = controller {
|
|
controller.inProgress = false
|
|
|
|
let text: String
|
|
var actions: [TextAlertAction] = []
|
|
switch error {
|
|
case .limitExceeded:
|
|
text = strongSelf.presentationData.strings.Login_CodeFloodError
|
|
actions.append(TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {}))
|
|
case .invalidPhoneNumber:
|
|
text = strongSelf.presentationData.strings.Login_InvalidPhoneError
|
|
actions.append(TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {}))
|
|
actions.append(TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Login_PhoneNumberHelp, action: { [weak controller] in
|
|
guard let strongSelf = self, let controller = controller else {
|
|
return
|
|
}
|
|
let formattedNumber = formatPhoneNumber(number)
|
|
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
|
let systemVersion = UIDevice.current.systemVersion
|
|
let locale = Locale.current.identifier
|
|
let carrier = CTCarrier()
|
|
let mnc = carrier.mobileNetworkCode ?? "none"
|
|
|
|
strongSelf.presentEmailComposeController(address: "login@stel.com", subject: strongSelf.presentationData.strings.Login_InvalidPhoneEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_InvalidPhoneEmailBody(formattedNumber, appVersion, systemVersion, locale, mnc).string, from: controller)
|
|
}))
|
|
case .phoneLimitExceeded:
|
|
text = strongSelf.presentationData.strings.Login_PhoneFloodError
|
|
actions.append(TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {}))
|
|
case .phoneBanned:
|
|
text = strongSelf.presentationData.strings.Login_PhoneBannedError
|
|
actions.append(TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {}))
|
|
actions.append(TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Login_PhoneNumberHelp, action: { [weak controller] in
|
|
guard let strongSelf = self, let controller = controller else {
|
|
return
|
|
}
|
|
let formattedNumber = formatPhoneNumber(number)
|
|
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
|
let systemVersion = UIDevice.current.systemVersion
|
|
let locale = Locale.current.identifier
|
|
let carrier = CTCarrier()
|
|
let mnc = carrier.mobileNetworkCode ?? "none"
|
|
|
|
strongSelf.presentEmailComposeController(address: "login@stel.com", subject: strongSelf.presentationData.strings.Login_PhoneBannedEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_PhoneBannedEmailBody(formattedNumber, appVersion, systemVersion, locale, mnc).string, from: controller)
|
|
}))
|
|
case let .generic(info):
|
|
text = strongSelf.presentationData.strings.Login_UnknownError
|
|
actions.append(TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {}))
|
|
actions.append(TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Login_PhoneNumberHelp, action: { [weak controller] in
|
|
guard let strongSelf = self, let controller = controller else {
|
|
return
|
|
}
|
|
let formattedNumber = formatPhoneNumber(number)
|
|
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
|
let systemVersion = UIDevice.current.systemVersion
|
|
let locale = Locale.current.identifier
|
|
let carrier = CTCarrier()
|
|
let mnc = carrier.mobileNetworkCode ?? "none"
|
|
let errorString: String
|
|
if let (code, description) = info {
|
|
errorString = "\(code): \(description)"
|
|
} else {
|
|
errorString = "unknown"
|
|
}
|
|
|
|
strongSelf.presentEmailComposeController(address: "login@stel.com", subject: strongSelf.presentationData.strings.Login_PhoneGenericEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_PhoneGenericEmailBody(formattedNumber, errorString, appVersion, systemVersion, locale, mnc).string, from: controller)
|
|
}))
|
|
case .timeout:
|
|
text = strongSelf.presentationData.strings.Login_NetworkError
|
|
actions.append(TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {}))
|
|
actions.append(TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.ChatSettings_ConnectionType_UseProxy, action: { [weak controller] in
|
|
guard let strongSelf = self, let controller = controller else {
|
|
return
|
|
}
|
|
controller.present(proxySettingsController(accountManager: strongSelf.sharedContext.accountManager, postbox: strongSelf.account.postbox, network: strongSelf.account.network, mode: .modal, presentationData: strongSelf.sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: strongSelf.sharedContext.presentationData), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
}))
|
|
}
|
|
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: actions), in: .window(.root))
|
|
}
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
controller.updateData(countryCode: countryCode, countryName: nil, number: number)
|
|
return controller
|
|
}
|
|
|
|
private func codeEntryController(number: String, type: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) -> AuthorizationSequenceCodeEntryController {
|
|
var currentController: AuthorizationSequenceCodeEntryController?
|
|
for c in self.viewControllers {
|
|
if let c = c as? AuthorizationSequenceCodeEntryController {
|
|
if c.data?.1 == type {
|
|
currentController = c
|
|
}
|
|
break
|
|
}
|
|
}
|
|
let controller: AuthorizationSequenceCodeEntryController
|
|
if let currentController = currentController {
|
|
controller = currentController
|
|
} else {
|
|
controller = AuthorizationSequenceCodeEntryController(presentationData: self.presentationData, openUrl: { [weak self] url in
|
|
self?.openUrl(url)
|
|
}, back: { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
let countryCode = defaultCountryCode()
|
|
|
|
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
|
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: "")))
|
|
}).start()
|
|
})
|
|
controller.loginWithCode = { [weak self, weak controller] code in
|
|
if let strongSelf = self {
|
|
controller?.inProgress = true
|
|
|
|
strongSelf.actionDisposable.set((authorizeWithCode(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, code: code, termsOfService: termsOfService?.0)
|
|
|> deliverOnMainQueue).start(next: { result in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
controller?.inProgress = false
|
|
switch result {
|
|
case let .signUp(data):
|
|
if let (termsOfService, explicit) = termsOfService, explicit {
|
|
var presentAlertAgainImpl: (() -> Void)?
|
|
let presentAlertImpl: () -> Void = {
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
var dismissImpl: (() -> Void)?
|
|
let alertTheme = AlertControllerTheme(presentationData: strongSelf.presentationData)
|
|
let attributedText = stringWithAppliedEntities(termsOfService.text, entities: termsOfService.entities, baseColor: alertTheme.primaryColor, linkColor: alertTheme.accentColor, baseFont: Font.regular(13.0), linkFont: Font.regular(13.0), boldFont: Font.semibold(13.0), italicFont: Font.italic(13.0), boldItalicFont: Font.semiboldItalic(13.0), fixedFont: Font.regular(13.0), blockQuoteFont: Font.regular(13.0))
|
|
let contentNode = TextAlertContentNode(theme: alertTheme, title: NSAttributedString(string: strongSelf.presentationData.strings.Login_TermsOfServiceHeader, font: Font.medium(17.0), textColor: alertTheme.primaryColor, paragraphAlignment: .center), text: attributedText, actions: [
|
|
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Login_TermsOfServiceAgree, action: {
|
|
dismissImpl?()
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
let _ = beginSignUp(account: strongSelf.account, data: data).start()
|
|
}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Login_TermsOfServiceDecline, action: {
|
|
dismissImpl?()
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.currentWindow?.present(standardTextAlertController(theme: alertTheme, title: strongSelf.presentationData.strings.Login_TermsOfServiceDecline, text: strongSelf.presentationData.strings.Login_TermsOfServiceSignupDecline, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
|
|
presentAlertAgainImpl?()
|
|
}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Login_TermsOfServiceDecline, action: {
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
let account = strongSelf.account
|
|
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
|
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty))
|
|
}).start()
|
|
})]), on: .root, blockInteraction: false, completion: {})
|
|
})
|
|
], actionLayout: .vertical, dismissOnOutsideTap: true)
|
|
contentNode.textAttributeAction = (NSAttributedString.Key(rawValue: TelegramTextAttributes.URL), { value in
|
|
if let value = value as? String {
|
|
strongSelf.openUrl(value)
|
|
}
|
|
})
|
|
let controller = AlertController(theme: alertTheme, contentNode: contentNode)
|
|
dismissImpl = { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
}
|
|
strongSelf.view.endEditing(true)
|
|
strongSelf.currentWindow?.present(controller, on: .root, blockInteraction: false, completion: {})
|
|
}
|
|
presentAlertAgainImpl = {
|
|
presentAlertImpl()
|
|
}
|
|
presentAlertImpl()
|
|
} else {
|
|
let _ = beginSignUp(account: strongSelf.account, data: data).start()
|
|
}
|
|
case .loggedIn:
|
|
break
|
|
}
|
|
}, error: { error in
|
|
Queue.mainQueue().async {
|
|
if let strongSelf = self, let controller = controller {
|
|
controller.inProgress = false
|
|
|
|
let text: String
|
|
switch error {
|
|
case .limitExceeded:
|
|
text = strongSelf.presentationData.strings.Login_CodeFloodError
|
|
case .invalidCode:
|
|
text = strongSelf.presentationData.strings.Login_InvalidCodeError
|
|
case .generic:
|
|
text = strongSelf.presentationData.strings.Login_UnknownError
|
|
case .codeExpired:
|
|
text = strongSelf.presentationData.strings.Login_CodeExpired
|
|
let account = strongSelf.account
|
|
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
|
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty))
|
|
}).start()
|
|
}
|
|
|
|
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
|
}
|
|
}
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
controller.requestNextOption = { [weak self, weak controller] in
|
|
if let strongSelf = self {
|
|
if nextType == nil {
|
|
if MFMailComposeViewController.canSendMail(), let controller = controller {
|
|
let formattedNumber = formatPhoneNumber(number)
|
|
strongSelf.presentEmailComposeController(address: "sms@stel.com", subject: strongSelf.presentationData.strings.Login_EmailCodeSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_EmailCodeBody(formattedNumber).string, from: controller)
|
|
} else {
|
|
controller?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Login_EmailNotConfiguredError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
|
}
|
|
} else {
|
|
controller?.inProgress = true
|
|
strongSelf.actionDisposable.set((resendAuthorizationCode(account: strongSelf.account)
|
|
|> deliverOnMainQueue).start(next: { result in
|
|
controller?.inProgress = false
|
|
}, error: { error in
|
|
if let strongSelf = self, let controller = controller {
|
|
controller.inProgress = false
|
|
|
|
let text: String
|
|
switch error {
|
|
case .limitExceeded:
|
|
text = strongSelf.presentationData.strings.Login_CodeFloodError
|
|
case .invalidPhoneNumber:
|
|
text = strongSelf.presentationData.strings.Login_InvalidPhoneError
|
|
case .phoneLimitExceeded:
|
|
text = strongSelf.presentationData.strings.Login_PhoneFloodError
|
|
case .phoneBanned:
|
|
text = strongSelf.presentationData.strings.Login_PhoneBannedError
|
|
case .generic:
|
|
text = strongSelf.presentationData.strings.Login_UnknownError
|
|
case .timeout:
|
|
text = strongSelf.presentationData.strings.Login_NetworkError
|
|
}
|
|
|
|
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
|
}
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
controller.reset = { [weak self] in
|
|
if let strongSelf = self {
|
|
let account = strongSelf.account
|
|
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
|
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty))
|
|
}).start()
|
|
}
|
|
}
|
|
controller.updateData(number: formatPhoneNumber(number), codeType: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService)
|
|
return controller
|
|
}
|
|
|
|
private func passwordEntryController(hint: String, suggestReset: Bool, syncContacts: Bool) -> AuthorizationSequencePasswordEntryController {
|
|
var currentController: AuthorizationSequencePasswordEntryController?
|
|
for c in self.viewControllers {
|
|
if let c = c as? AuthorizationSequencePasswordEntryController {
|
|
currentController = c
|
|
break
|
|
}
|
|
}
|
|
let controller: AuthorizationSequencePasswordEntryController
|
|
if let currentController = currentController {
|
|
controller = currentController
|
|
} else {
|
|
controller = AuthorizationSequencePasswordEntryController(presentationData: self.presentationData, back: { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
let countryCode = defaultCountryCode()
|
|
|
|
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
|
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: "")))
|
|
}).start()
|
|
})
|
|
controller.loginWithPassword = { [weak self, weak controller] password in
|
|
if let strongSelf = self {
|
|
controller?.inProgress = true
|
|
|
|
strongSelf.actionDisposable.set((authorizeWithPassword(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, password: password, syncContacts: syncContacts) |> deliverOnMainQueue).start(error: { error in
|
|
Queue.mainQueue().async {
|
|
if let strongSelf = self, let controller = controller {
|
|
controller.inProgress = false
|
|
|
|
let text: String
|
|
switch error {
|
|
case .limitExceeded:
|
|
text = strongSelf.presentationData.strings.LoginPassword_FloodError
|
|
case .invalidPassword:
|
|
text = strongSelf.presentationData.strings.LoginPassword_InvalidPasswordError
|
|
case .generic:
|
|
text = strongSelf.presentationData.strings.Login_UnknownError
|
|
}
|
|
|
|
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
|
controller.passwordIsInvalid()
|
|
}
|
|
}
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
controller.forgot = { [weak self, weak controller] in
|
|
if let strongSelf = self, let strongController = controller {
|
|
strongController.inProgress = true
|
|
strongSelf.actionDisposable.set((TelegramEngineUnauthorized(account: strongSelf.account).auth.requestTwoStepVerificationPasswordRecoveryCode()
|
|
|> deliverOnMainQueue).start(next: { pattern in
|
|
if let strongSelf = self, let strongController = controller {
|
|
strongController.inProgress = false
|
|
|
|
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
|
|
if let state = transaction.getState() as? UnauthorizedAccountState, case let .passwordEntry(hint, number, code, _, syncContacts) = state.contents {
|
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .passwordRecovery(hint: hint, number: number, code: code, emailPattern: pattern, syncContacts: syncContacts)))
|
|
}
|
|
}).start()
|
|
}
|
|
}, error: { error in
|
|
guard let strongController = controller else {
|
|
return
|
|
}
|
|
|
|
strongController.inProgress = false
|
|
|
|
strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
|
strongController.didForgotWithNoRecovery = true
|
|
}))
|
|
}
|
|
}
|
|
controller.reset = { [weak self, weak controller] in
|
|
if let strongSelf = self, let strongController = controller {
|
|
strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: suggestReset ? strongSelf.presentationData.strings.TwoStepAuth_RecoveryFailed : strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [
|
|
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}),
|
|
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Login_ResetAccountProtected_Reset, action: {
|
|
if let strongSelf = self, let strongController = controller {
|
|
strongController.inProgress = true
|
|
strongSelf.actionDisposable.set((performAccountReset(account: strongSelf.account)
|
|
|> deliverOnMainQueue).start(next: {
|
|
if let strongController = controller {
|
|
strongController.inProgress = false
|
|
}
|
|
}, error: { error in
|
|
if let strongSelf = self, let strongController = controller {
|
|
strongController.inProgress = false
|
|
let text: String
|
|
switch error {
|
|
case .generic:
|
|
text = strongSelf.presentationData.strings.Login_UnknownError
|
|
case .limitExceeded:
|
|
text = strongSelf.presentationData.strings.Login_ResetAccountProtected_LimitExceeded
|
|
}
|
|
strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
|
}
|
|
}))
|
|
}
|
|
})]), in: .window(.root))
|
|
}
|
|
}
|
|
controller.updateData(hint: hint, suggestReset: suggestReset)
|
|
return controller
|
|
}
|
|
|
|
private func passwordRecoveryController(emailPattern: String, syncContacts: Bool) -> TwoFactorDataInputScreen {
|
|
var currentController: TwoFactorDataInputScreen?
|
|
for c in self.viewControllers {
|
|
if let c = c as? TwoFactorDataInputScreen {
|
|
currentController = c
|
|
break
|
|
}
|
|
}
|
|
let controller: TwoFactorDataInputScreen
|
|
if let currentController = currentController {
|
|
controller = currentController
|
|
} else {
|
|
controller = TwoFactorDataInputScreen(sharedContext: self.sharedContext, engine: .unauthorized(TelegramEngineUnauthorized(account: self.account)), mode: .passwordRecoveryEmail(emailPattern: emailPattern, mode: .notAuthorized(syncContacts: syncContacts)), stateUpdated: { _ in
|
|
}, presentation: .default)
|
|
}
|
|
controller.passwordRecoveryFailed = { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
|
|
if let state = transaction.getState() as? UnauthorizedAccountState, case let .passwordRecovery(hint, number, code, _, syncContacts) = state.contents {
|
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .passwordEntry(hint: hint, number: number, code: code, suggestReset: true, syncContacts: syncContacts)))
|
|
}
|
|
}).start()
|
|
}
|
|
return controller
|
|
}
|
|
|
|
private func awaitingAccountResetController(protectedUntil: Int32, number: String?) -> AuthorizationSequenceAwaitingAccountResetController {
|
|
var currentController: AuthorizationSequenceAwaitingAccountResetController?
|
|
for c in self.viewControllers {
|
|
if let c = c as? AuthorizationSequenceAwaitingAccountResetController {
|
|
currentController = c
|
|
break
|
|
}
|
|
}
|
|
let controller: AuthorizationSequenceAwaitingAccountResetController
|
|
if let currentController = currentController {
|
|
controller = currentController
|
|
} else {
|
|
controller = AuthorizationSequenceAwaitingAccountResetController(strings: self.presentationData.strings, theme: self.presentationData.theme, back: { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
let countryCode = defaultCountryCode()
|
|
|
|
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
|
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: "")))
|
|
}).start()
|
|
})
|
|
controller.reset = { [weak self, weak controller] in
|
|
if let strongSelf = self, let strongController = controller {
|
|
strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_ResetAccountConfirmation, actions: [
|
|
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}),
|
|
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Login_ResetAccountProtected_Reset, action: {
|
|
if let strongSelf = self, let strongController = controller {
|
|
strongController.inProgress = true
|
|
strongSelf.actionDisposable.set((performAccountReset(account: strongSelf.account)
|
|
|> deliverOnMainQueue).start(next: {
|
|
if let strongController = controller {
|
|
strongController.inProgress = false
|
|
}
|
|
}, error: { error in
|
|
if let strongSelf = self, let strongController = controller {
|
|
strongController.inProgress = false
|
|
let text: String
|
|
switch error {
|
|
case .generic:
|
|
text = strongSelf.presentationData.strings.Login_UnknownError
|
|
case .limitExceeded:
|
|
text = strongSelf.presentationData.strings.Login_ResetAccountProtected_LimitExceeded
|
|
}
|
|
strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
|
}
|
|
}))
|
|
}
|
|
})]), in: .window(.root))
|
|
}
|
|
}
|
|
controller.logout = { [weak self] in
|
|
if let strongSelf = self {
|
|
let account = strongSelf.account
|
|
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
|
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty))
|
|
}).start()
|
|
}
|
|
}
|
|
}
|
|
controller.updateData(protectedUntil: protectedUntil, number: number ?? "")
|
|
return controller
|
|
}
|
|
|
|
private func signUpController(firstName: String, lastName: String, termsOfService: UnauthorizedAccountTermsOfService?, displayCancel: Bool) -> AuthorizationSequenceSignUpController {
|
|
var currentController: AuthorizationSequenceSignUpController?
|
|
for c in self.viewControllers {
|
|
if let c = c as? AuthorizationSequenceSignUpController {
|
|
currentController = c
|
|
break
|
|
}
|
|
}
|
|
let controller: AuthorizationSequenceSignUpController
|
|
if let currentController = currentController {
|
|
controller = currentController
|
|
} else {
|
|
controller = AuthorizationSequenceSignUpController(presentationData: self.presentationData, back: { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
let countryCode = defaultCountryCode()
|
|
|
|
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
|
|
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: "")))
|
|
}).start()
|
|
}, displayCancel: displayCancel)
|
|
controller.signUpWithName = { [weak self, weak controller] firstName, lastName, avatarData, avatarAsset, avatarAdjustments in
|
|
if let strongSelf = self {
|
|
controller?.inProgress = true
|
|
|
|
var videoStartTimestamp: Double? = nil
|
|
if let adjustments = avatarAdjustments, adjustments.videoStartValue > 0.0 {
|
|
videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue
|
|
}
|
|
|
|
let avatarVideo: Signal<UploadedPeerPhotoData?, NoError>?
|
|
if let avatarAsset = avatarAsset as? AVAsset {
|
|
let account = strongSelf.account
|
|
avatarVideo = Signal<TelegramMediaResource?, NoError> { subscriber in
|
|
let entityRenderer: LegacyPaintEntityRenderer? = avatarAdjustments.flatMap { adjustments in
|
|
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
|
|
return LegacyPaintEntityRenderer(account: nil, adjustments: adjustments)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
let signal = TGMediaVideoConverter.convert(avatarAsset, adjustments: avatarAdjustments, watcher: nil, entityRenderer: entityRenderer)!
|
|
|
|
let signalDisposable = signal.start(next: { next in
|
|
if let result = next as? TGMediaVideoConversionResult {
|
|
var value = stat()
|
|
if stat(result.fileURL.path, &value) == 0 {
|
|
if let data = try? Data(contentsOf: result.fileURL) {
|
|
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
|
|
account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
|
|
subscriber.putNext(resource)
|
|
}
|
|
}
|
|
subscriber.putCompletion()
|
|
}
|
|
}, error: { _ in
|
|
}, completed: nil)
|
|
|
|
let disposable = ActionDisposable {
|
|
signalDisposable?.dispose()
|
|
}
|
|
|
|
return ActionDisposable {
|
|
disposable.dispose()
|
|
}
|
|
}
|
|
|> mapToSignal { resource -> Signal<UploadedPeerPhotoData?, NoError> in
|
|
if let resource = resource {
|
|
return TelegramEngineUnauthorized(account: account).auth.uploadedPeerVideo(resource: resource) |> map(Optional.init)
|
|
} else {
|
|
return .single(nil)
|
|
}
|
|
}
|
|
} else {
|
|
avatarVideo = nil
|
|
}
|
|
|
|
strongSelf.actionDisposable.set((signUpWithName(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, firstName: firstName, lastName: lastName, avatarData: avatarData, avatarVideo: avatarVideo, videoStartTimestamp: videoStartTimestamp)
|
|
|> deliverOnMainQueue).start(error: { error in
|
|
Queue.mainQueue().async {
|
|
if let strongSelf = self, let controller = controller {
|
|
controller.inProgress = false
|
|
|
|
let text: String
|
|
switch error {
|
|
case .limitExceeded:
|
|
text = strongSelf.presentationData.strings.Login_CodeFloodError
|
|
case .codeExpired:
|
|
text = strongSelf.presentationData.strings.Login_CodeExpiredError
|
|
case .invalidFirstName:
|
|
text = strongSelf.presentationData.strings.Login_InvalidFirstNameError
|
|
case .invalidLastName:
|
|
text = strongSelf.presentationData.strings.Login_InvalidLastNameError
|
|
case .generic:
|
|
text = strongSelf.presentationData.strings.Login_UnknownError
|
|
}
|
|
|
|
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
|
}
|
|
}
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
controller.updateData(firstName: firstName, lastName: lastName, termsOfService: termsOfService)
|
|
return controller
|
|
}
|
|
|
|
private func updateState(state: InnerState) {
|
|
switch state {
|
|
case .authorized:
|
|
self.authorizationCompleted()
|
|
case let .state(state):
|
|
switch state {
|
|
case .empty:
|
|
if let _ = self.viewControllers.last as? AuthorizationSequenceSplashController {
|
|
} else {
|
|
var controllers: [ViewController] = []
|
|
if self.otherAccountPhoneNumbers.1.isEmpty {
|
|
controllers.append(self.splashController())
|
|
} else {
|
|
controllers.append(self.phoneEntryController(countryCode: defaultCountryCode(), number: ""))
|
|
}
|
|
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
|
|
}
|
|
case let .phoneEntry(countryCode, number):
|
|
var controllers: [ViewController] = []
|
|
if !self.otherAccountPhoneNumbers.1.isEmpty {
|
|
controllers.append(self.splashController())
|
|
}
|
|
controllers.append(self.phoneEntryController(countryCode: countryCode, number: number))
|
|
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
|
|
case let .confirmationCodeEntry(number, type, _, timeout, nextType, _):
|
|
var controllers: [ViewController] = []
|
|
if !self.otherAccountPhoneNumbers.1.isEmpty {
|
|
controllers.append(self.splashController())
|
|
}
|
|
controllers.append(self.phoneEntryController(countryCode: defaultCountryCode(), number: ""))
|
|
controllers.append(self.codeEntryController(number: number, type: type, nextType: nextType, timeout: timeout, termsOfService: nil))
|
|
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
|
|
case let .passwordEntry(hint, _, _, suggestReset, syncContacts):
|
|
var controllers: [ViewController] = []
|
|
if !self.otherAccountPhoneNumbers.1.isEmpty {
|
|
controllers.append(self.splashController())
|
|
}
|
|
controllers.append(self.passwordEntryController(hint: hint, suggestReset: suggestReset, syncContacts: syncContacts))
|
|
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
|
|
case let .passwordRecovery(_, _, _, emailPattern, syncContacts):
|
|
var controllers: [ViewController] = []
|
|
if !self.otherAccountPhoneNumbers.1.isEmpty {
|
|
controllers.append(self.splashController())
|
|
}
|
|
controllers.append(self.passwordRecoveryController(emailPattern: emailPattern, syncContacts: syncContacts))
|
|
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
|
|
case let .awaitingAccountReset(protectedUntil, number, _):
|
|
var controllers: [ViewController] = []
|
|
if !self.otherAccountPhoneNumbers.1.isEmpty {
|
|
controllers.append(self.splashController())
|
|
}
|
|
controllers.append(self.awaitingAccountResetController(protectedUntil: protectedUntil, number: number))
|
|
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
|
|
case let .signUp(_, _, firstName, lastName, termsOfService, _):
|
|
var controllers: [ViewController] = []
|
|
var displayCancel = false
|
|
if !self.otherAccountPhoneNumbers.1.isEmpty {
|
|
controllers.append(self.splashController())
|
|
} else {
|
|
displayCancel = true
|
|
}
|
|
controllers.append(self.signUpController(firstName: firstName, lastName: lastName, termsOfService: termsOfService, displayCancel: displayCancel))
|
|
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
|
|
}
|
|
}
|
|
}
|
|
|
|
override public func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) {
|
|
let wasEmpty = self.viewControllers.isEmpty
|
|
super.setViewControllers(viewControllers, animated: animated)
|
|
if wasEmpty {
|
|
self.topViewController?.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
|
}
|
|
if !self.didSetReady {
|
|
self.didSetReady = true
|
|
self._ready.set(.single(true))
|
|
}
|
|
}
|
|
|
|
public func applyConfirmationCode(_ code: Int) {
|
|
if let controller = self.viewControllers.last as? AuthorizationSequenceCodeEntryController {
|
|
controller.applyConfirmationCode(code)
|
|
}
|
|
}
|
|
|
|
private func presentEmailComposeController(address: String, subject: String, body: String, from controller: ViewController) {
|
|
if MFMailComposeViewController.canSendMail() {
|
|
let composeController = MFMailComposeViewController()
|
|
composeController.setToRecipients([address])
|
|
composeController.setSubject(subject)
|
|
composeController.setMessageBody(body, isHTML: false)
|
|
composeController.mailComposeDelegate = self
|
|
|
|
controller.view.window?.rootViewController?.present(composeController, animated: true, completion: nil)
|
|
} else {
|
|
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_EmailNotConfiguredError, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
|
}
|
|
}
|
|
|
|
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
|
controller.dismiss(animated: true, completion: nil)
|
|
}
|
|
|
|
private func animateIn() {
|
|
self.view.layer.animatePosition(from: CGPoint(x: self.view.layer.position.x, y: self.view.layer.position.y + self.view.layer.bounds.size.height), to: self.view.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
|
}
|
|
|
|
private func animateOut(completion: (() -> Void)? = nil) {
|
|
self.view.layer.animatePosition(from: self.view.layer.position, to: CGPoint(x: self.view.layer.position.x, y: self.view.layer.position.y + self.view.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in
|
|
completion?()
|
|
})
|
|
}
|
|
|
|
override public func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
|
|
if !self.didPlayPresentationAnimation {
|
|
self.didPlayPresentationAnimation = true
|
|
self.animateIn()
|
|
}
|
|
}
|
|
|
|
public func dismiss() {
|
|
self.animateOut(completion: { [weak self] in
|
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
|
})
|
|
}
|
|
}
|
|
|
|
private func defaultCountryCode() -> Int32 {
|
|
var countryId: String? = nil
|
|
let networkInfo = CTTelephonyNetworkInfo()
|
|
if let carrier = networkInfo.subscriberCellularProvider {
|
|
countryId = carrier.isoCountryCode
|
|
}
|
|
|
|
if countryId == nil {
|
|
countryId = (Locale.current as NSLocale).object(forKey: .countryCode) as? String
|
|
}
|
|
|
|
var countryCode: Int32 = 1
|
|
|
|
if let countryId = countryId {
|
|
let normalizedId = countryId.uppercased()
|
|
for (code, idAndName) in countryCodeToIdAndName {
|
|
if idAndName.0 == normalizedId {
|
|
countryCode = Int32(code)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return countryCode
|
|
}
|