Swiftgram/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift
2021-07-06 03:27:53 +04:00

253 lines
11 KiB
Swift

import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import TelegramPresentationData
import ItemListUI
import PresentationDataUtils
import AccountContext
import AlertUI
import PresentationDataUtils
private final class ResetPasswordControllerArguments {
let updateCodeText: (String) -> Void
let openHelp: () -> Void
init(updateCodeText: @escaping (String) -> Void, openHelp: @escaping () -> Void) {
self.updateCodeText = updateCodeText
self.openHelp = openHelp
}
}
private enum ResetPasswordSection: Int32 {
case code
case help
}
private enum ResetPasswordEntryTag: ItemListItemTag {
case code
func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? ResetPasswordEntryTag {
return self == other
} else {
return false
}
}
}
private enum ResetPasswordEntry: ItemListNodeEntry, Equatable {
case code(PresentationTheme, PresentationStrings, String, String)
case codeInfo(PresentationTheme, String)
case helpInfo(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
case .code, .codeInfo:
return ResetPasswordSection.code.rawValue
case .helpInfo:
return ResetPasswordSection.help.rawValue
}
}
var stableId: Int32 {
switch self {
case .code:
return 0
case .codeInfo:
return 1
case .helpInfo:
return 2
}
}
static func <(lhs: ResetPasswordEntry, rhs: ResetPasswordEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! ResetPasswordControllerArguments
switch self {
case let .code(theme, strings, text, value):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: text, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: "", type: .number, spacing: 10.0, tag: ResetPasswordEntryTag.code, sectionId: self.section, textUpdated: { updatedText in
arguments.updateCodeText(updatedText)
}, action: {
})
case let .codeInfo(theme, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .helpInfo(theme, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { action in
if case .tap = action {
arguments.openHelp()
}
})
}
}
}
private struct ResetPasswordControllerState: Equatable {
var code: String = ""
var checking: Bool = false
}
private func resetPasswordControllerEntries(presentationData: PresentationData, state: ResetPasswordControllerState, pattern: String) -> [ResetPasswordEntry] {
var entries: [ResetPasswordEntry] = []
entries.append(.code(presentationData.theme, presentationData.strings, presentationData.strings.TwoStepAuth_RecoveryCode, state.code))
entries.append(.codeInfo(presentationData.theme, presentationData.strings.TwoStepAuth_RecoveryCodeHelp))
let stringData = presentationData.strings.TwoStepAuth_RecoveryEmailUnavailable(pattern)
var string = stringData.0
if let (_, range) = stringData.1.first {
string.insert(contentsOf: "]()", at: string.index(string.startIndex, offsetBy: range.upperBound))
string.insert(contentsOf: "[", at: string.index(string.startIndex, offsetBy: range.lowerBound))
}
entries.append(.helpInfo(presentationData.theme, string))
return entries
}
public enum ResetPasswordState: Equatable {
case setup(currentPassword: String?)
case pendingVerification(emailPattern: String)
}
public func resetPasswordController(context: AccountContext, emailPattern: String, completion: @escaping (Bool) -> Void) -> ViewController {
let statePromise = ValuePromise(ResetPasswordControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: ResetPasswordControllerState())
let updateState: ((ResetPasswordControllerState) -> ResetPasswordControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
var dismissImpl: (() -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
let actionsDisposable = DisposableSet()
let saveDisposable = MetaDisposable()
actionsDisposable.add(saveDisposable)
let arguments = ResetPasswordControllerArguments(updateCodeText: { updatedText in
updateState { state in
var state = state
state.code = updatedText
return state
}
}, openHelp: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.TwoStepAuth_RecoveryUnavailableResetTitle, text: presentationData.strings.TwoStepAuth_RecoveryEmailResetText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.TwoStepAuth_RecoveryUnavailableResetAction, action: {
let _ = (context.engine.auth.requestTwoStepPasswordReset()
|> deliverOnMainQueue).start(next: { result in
switch result {
case .done, .waitingForReset:
completion(false)
case .declined:
break
case let .error(reason):
break
}
})
})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
})
var initialFocusImpl: (() -> Void)?
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get())
|> deliverOnMainQueue
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
})
var rightNavigationButton: ItemListNavigationButton?
if state.checking {
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
} else {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.code.isEmpty, action: {
var state: ResetPasswordControllerState?
updateState { s in
state = s
return s
}
if let state = state, !state.checking, !state.code.isEmpty {
updateState { state in
var state = state
state.checking = true
return state
}
saveDisposable.set((context.engine.auth.performPasswordRecovery(code: state.code, updatedPassword: .none)
|> deliverOnMainQueue).start(error: { error in
updateState { state in
var state = state
state.checking = false
return state
}
let text: String
switch error {
case .invalidCode:
text = presentationData.strings.TwoStepAuth_RecoveryCodeInvalid
case .expired:
text = presentationData.strings.TwoStepAuth_RecoveryCodeExpired
case .limitExceeded:
text = presentationData.strings.TwoStepAuth_FloodError
case .generic:
text = presentationData.strings.Login_UnknownError
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}, completed: {
completion(true)
}))
}
})
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.TwoStepAuth_RecoveryTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: resetPasswordControllerEntries(presentationData: presentationData, state: state, pattern: emailPattern), style: .blocks, focusItemTag: ResetPasswordEntryTag.code, emptyStateItem: nil, animateChanges: false)
return (controllerState, (listState, arguments))
}
|> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(context: context, state: signal)
dismissImpl = { [weak controller] in
controller?.view.endEditing(true)
controller?.dismiss()
}
presentControllerImpl = { [weak controller] c, p in
if let controller = controller {
controller.present(c, in: .window(.root), with: p)
}
}
initialFocusImpl = { [weak controller] in
guard let controller = controller, controller.didAppearOnce else {
return
}
var resultItemNode: ItemListSingleLineInputItemNode?
let _ = controller.frameForItemNode({ itemNode in
if let itemNode = itemNode as? ItemListSingleLineInputItemNode, let tag = itemNode.tag, tag.isEqual(to: ResetPasswordEntryTag.code) {
resultItemNode = itemNode
return true
}
return false
})
if let resultItemNode = resultItemNode {
resultItemNode.focus()
}
}
controller.didAppear = { firstTime in
if !firstTime {
return
}
initialFocusImpl?()
}
return controller
}