import Foundation import UIKit import Display import SwiftSignalKit import TelegramCore import TelegramPresentationData import ItemListUI import PresentationDataUtils import AccountContext import AlertUI import PresentationDataUtils private final class ResetPasswordControllerArguments { let context: AccountContext let updateCodeText: (String) -> Void let openHelp: () -> Void init(context: AccountContext, updateCodeText: @escaping (String) -> Void, openHelp: @escaping () -> Void) { self.context = context 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, _, 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(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .helpInfo(_, 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.string if let range = stringData.ranges.first { string.insert(contentsOf: "]()", at: string.index(string.startIndex, offsetBy: range.range.upperBound)) string.insert(contentsOf: "[", at: string.index(string.startIndex, offsetBy: range.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(context: context, 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 .error: 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 }