no message

This commit is contained in:
Peter 2018-09-07 22:55:14 +03:00
parent 1bde567623
commit b04ddea9c5
18 changed files with 2875 additions and 2302 deletions

View File

@ -308,6 +308,7 @@
D0B37C5C1F8D22AE004252DF /* ThemeSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B37C5B1F8D22AE004252DF /* ThemeSettingsController.swift */; };
D0B37C5E1F8D26A8004252DF /* ThemeSettingsChatPreviewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B37C5D1F8D26A8004252DF /* ThemeSettingsChatPreviewItem.swift */; };
D0B37C601F8D286E004252DF /* ThemeSettingsFontSizeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B37C5F1F8D286E004252DF /* ThemeSettingsFontSizeItem.swift */; };
D0B3AC802142E2E900CD1374 /* ResetPasswordController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3AC7F2142E2E900CD1374 /* ResetPasswordController.swift */; };
D0B4AF861EC111FA00D51FF6 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D0AB0BBA1D6719B5002C78E7 /* Images.xcassets */; };
D0B4AF881EC112EE00D51FF6 /* CallKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0B4AF871EC112ED00D51FF6 /* CallKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
D0B4AF8B1EC1133600D51FF6 /* CallKitIntergation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B4AF8A1EC1133600D51FF6 /* CallKitIntergation.swift */; };
@ -1625,6 +1626,7 @@
D0B37C5B1F8D22AE004252DF /* ThemeSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsController.swift; sourceTree = "<group>"; };
D0B37C5D1F8D26A8004252DF /* ThemeSettingsChatPreviewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsChatPreviewItem.swift; sourceTree = "<group>"; };
D0B37C5F1F8D286E004252DF /* ThemeSettingsFontSizeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsFontSizeItem.swift; sourceTree = "<group>"; };
D0B3AC7F2142E2E900CD1374 /* ResetPasswordController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPasswordController.swift; sourceTree = "<group>"; };
D0B417C21D7DE54E004562A4 /* ChatPresentationInterfaceState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatPresentationInterfaceState.swift; sourceTree = "<group>"; };
D0B4AF871EC112ED00D51FF6 /* CallKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CallKit.framework; path = System/Library/Frameworks/CallKit.framework; sourceTree = SDKROOT; };
D0B4AF8A1EC1133600D51FF6 /* CallKitIntergation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallKitIntergation.swift; sourceTree = "<group>"; };
@ -4342,6 +4344,7 @@
D0FA0AC01E7725AA005BB9B7 /* TwoStepVerificationResetController.swift */,
D0760B231E9D015D00F1F3C4 /* PasscodeOptionsController.swift */,
D0CE6F6F213EEE5000BCD44B /* CreatePasswordController.swift */,
D0B3AC7F2142E2E900CD1374 /* ResetPasswordController.swift */,
);
name = "Privacy and Security";
sourceTree = "<group>";
@ -4857,6 +4860,7 @@
D0461439200514F000EC0EF2 /* LiveLocationSummaryManager.swift in Sources */,
D056CD781FF2A6EE00880D28 /* ChatMessageSwipeToReplyNode.swift in Sources */,
D0CE67941F7DB45100FFB557 /* ChatMessageContactBubbleContentNode.swift in Sources */,
D0B3AC802142E2E900CD1374 /* ResetPasswordController.swift in Sources */,
D0943AFE1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift in Sources */,
D0ADF966212E05A300310BBC /* TonePlayer.swift in Sources */,
D007019E2029EFDD006B9E34 /* ICloudResources.swift in Sources */,

View File

@ -158,22 +158,10 @@ private func universalServiceMessageString(theme: PresentationTheme?, strings: P
type = .video
}
break inner
case let .Audio(isVoice, _, performer, title, _):
case let .Audio(isVoice, _, _, _, _):
if isVoice {
type = .audio
} else {
// let descriptionString: String
// if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
// descriptionString = title + " " + performer
// } else if let title = title, !title.isEmpty {
// descriptionString = title
// } else if let performer = performer, !performer.isEmpty {
// descriptionString = performer
// } else if let fileName = file.fileName {
// descriptionString = strings.Message_File
// } else {
// descriptionString = strings.Message_Audio
// }
type = .file
}
break inner
@ -203,7 +191,13 @@ private func universalServiceMessageString(theme: PresentationTheme?, strings: P
if clippedText.count > 14 {
clippedText = "\(clippedText[...clippedText.index(clippedText.startIndex, offsetBy: 14)])..."
}
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedTextMessage(authorName, clippedText), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
let textWithRanges: (String, [(Int, NSRange)])
if clippedText.isEmpty {
textWithRanges = strings.PINNED_NOTEXT(authorName)
} else {
textWithRanges = strings.Notification_PinnedTextMessage(authorName, clippedText)
}
attributedString = addAttributesToStringWithRanges(textWithRanges, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
case .game:
attributedString = addAttributesToStringWithRanges(strings.PINNED_GAME(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
case .photo:
@ -225,7 +219,7 @@ private func universalServiceMessageString(theme: PresentationTheme?, strings: P
case .contact:
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedContactMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
case .deleted:
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedDeletedMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
attributedString = addAttributesToStringWithRanges(strings.PINNED_NOTEXT(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
}
case .joinedByLink:
attributedString = addAttributesToStringWithRanges(strings.Notification_JoinedGroupByLink(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))

View File

@ -8,18 +8,18 @@ import SwiftSignalKit
public final class ChatMessageNotificationItem: NotificationItem {
let account: Account
let strings: PresentationStrings
let message: Message
let messages: [Message]
let tapAction: () -> Bool
let expandAction: (@escaping () -> (ASDisplayNode?, () -> Void)) -> Void
public var groupingKey: AnyHashable? {
return message.id.peerId
return messages.first?.id.peerId
}
public init(account: Account, strings: PresentationStrings, message: Message, tapAction: @escaping () -> Bool, expandAction: @escaping (() -> (ASDisplayNode?, () -> Void)) -> Void) {
public init(account: Account, strings: PresentationStrings, messages: [Message], tapAction: @escaping () -> Bool, expandAction: @escaping (() -> (ASDisplayNode?, () -> Void)) -> Void) {
self.account = account
self.strings = strings
self.message = message
self.messages = messages
self.tapAction = tapAction
self.expandAction = expandAction
}
@ -90,30 +90,30 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
self.item = item
let presentationData = item.account.telegramApplicationContext.currentPresentationData.with { $0 }
if let peer = messageMainPeer(item.message) {
var title: String?
if let firstMessage = item.messages.first, let peer = messageMainPeer(firstMessage) {
self.avatarNode.setPeer(account: item.account, peer: peer)
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
self.titleAttributedText = NSAttributedString(string: peer.displayTitle, font: Font.semibold(16.0), textColor: presentationData.theme.inAppNotification.primaryTextColor)
} else if let author = item.message.author, author.id != peer.id {
self.titleAttributedText = NSAttributedString(string: author.displayTitle + "@" + peer.displayTitle, font: Font.semibold(16.0), textColor: presentationData.theme.inAppNotification.primaryTextColor)
title = peer.displayTitle
} else if let author = firstMessage.author, author.id != peer.id {
title = author.displayTitle + "@" + peer.displayTitle
} else {
self.titleAttributedText = NSAttributedString(string: peer.displayTitle, font: Font.semibold(16.0), textColor: presentationData.theme.inAppNotification.primaryTextColor)
title = peer.displayTitle
}
}
var titleIcon: UIImage?
if item.message.id.peerId.namespace == Namespaces.Peer.SecretChat {
titleIcon = PresentationResourcesRootController.inAppNotificationSecretChatIcon(presentationData.theme)
}
self.titleIconNode.image = titleIcon
var updatedMedia: Media?
var imageDimensions: CGSize?
var isRound = false
if item.message.id.peerId.namespace != Namespaces.Peer.SecretChat {
for media in item.message.media {
let messageText: String
if item.messages.first?.id.peerId.namespace == Namespaces.Peer.SecretChat {
titleIcon = PresentationResourcesRootController.inAppNotificationSecretChatIcon(presentationData.theme)
messageText = item.strings.ENCRYPTED_MESSAGE("").0
} else if item.messages.count == 1 {
let message = item.messages[0]
for media in message.media {
if let image = media as? TelegramMediaImage {
updatedMedia = image
if let representation = largestRepresentationForPhoto(image) {
@ -129,11 +129,82 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
break
}
}
if message.containsSecretMedia {
imageDimensions = nil
}
messageText = descriptionStringForMessage(message, strings: item.strings, accountPeerId: item.account.peerId).0
} else if item.messages.count > 1, let peer = item.messages[0].peers[item.messages[0].id.peerId] {
var displayAuthor = true
if let channel = peer as? TelegramChannel {
switch channel.info {
case .group:
displayAuthor = true
case .broadcast:
displayAuthor = false
}
} else if let _ = peer as? TelegramUser {
displayAuthor = false
}
if item.messages[0].forwardInfo != nil {
if let author = item.messages[0].author, displayAuthor {
title = nil
messageText = presentationData.strings.CHAT_MESSAGE_FWDS(author.compactDisplayTitle, peer.displayTitle, "\(item.messages.count)").0
} else {
title = nil
messageText = presentationData.strings.MESSAGE_FWDS(peer.displayTitle, "\(item.messages.count)").0
}
} else if item.messages[0].groupingKey != nil {
var kind = messageContentKind(item.messages[0], strings: presentationData.strings, accountPeerId: item.account.peerId).key
for i in 1 ..< item.messages.count {
let nextKind = messageContentKind(item.messages[i], strings: presentationData.strings, accountPeerId: item.account.peerId)
if kind != nextKind.key {
kind = .text
break
}
}
var isChannel = false
var isGroup = false
if let peer = peer as? TelegramChannel {
if case .broadcast = peer.info {
isChannel = true
} else {
isGroup = true
}
} else if item.messages[0].id.peerId.namespace == Namespaces.Peer.CloudGroup {
isGroup = true
}
title = nil
if isChannel {
switch kind {
case .image:
messageText = presentationData.strings.CHANNEL_MESSAGE_PHOTOS(peer.compactDisplayTitle, "\(item.messages.count)").0
default:
messageText = presentationData.strings.CHANNEL_MESSAGES(peer.compactDisplayTitle, "\(item.messages.count)").0
}
} else if isGroup, let author = item.messages[0].author {
switch kind {
case .image:
messageText = presentationData.strings.CHAT_MESSAGE_PHOTOS(author.compactDisplayTitle, peer.displayTitle, "\(item.messages.count)").0
default:
messageText = presentationData.strings.CHAT_MESSAGES(author.compactDisplayTitle, peer.displayTitle, "\(item.messages.count)").0
}
} else {
switch kind {
case .image:
messageText = presentationData.strings.MESSAGE_PHOTOS(peer.displayTitle, "\(item.messages.count)").0
default:
messageText = presentationData.strings.MESSAGES(peer.displayTitle, "\(item.messages.count)").0
}
}
} else {
messageText = ""
}
} else {
messageText = ""
}
if item.message.containsSecretMedia {
imageDimensions = nil
}
self.titleAttributedText = NSAttributedString(string: title ?? "", font: Font.semibold(16.0), textColor: presentationData.theme.inAppNotification.primaryTextColor)
let imageNodeLayout = self.imageNode.asyncLayout()
var applyImage: (() -> Void)?
@ -147,25 +218,18 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
}
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
if let updatedMedia = updatedMedia, imageDimensions != nil {
if let firstMessage = item.messages.first, let updatedMedia = updatedMedia, imageDimensions != nil {
if let image = updatedMedia as? TelegramMediaImage {
updateImageSignal = mediaGridMessagePhoto(account: item.account, photoReference: .message(message: MessageReference(item.message), media: image))
updateImageSignal = mediaGridMessagePhoto(account: item.account, photoReference: .message(message: MessageReference(firstMessage), media: image))
} else if let file = updatedMedia as? TelegramMediaFile {
if file.isSticker {
updateImageSignal = chatMessageSticker(account: item.account, file: file, small: true, fetched: true)
} else if file.isVideo {
updateImageSignal = mediaGridMessageVideo(postbox: item.account.postbox, videoReference: .message(message: MessageReference(item.message), media: file))
updateImageSignal = mediaGridMessageVideo(postbox: item.account.postbox, videoReference: .message(message: MessageReference(firstMessage), media: file))
}
}
}
let messageText: String
if item.message.id.peerId.namespace == Namespaces.Peer.SecretChat {
messageText = item.strings.ENCRYPTED_MESSAGE("").0
} else {
messageText = descriptionStringForMessage(item.message, strings: item.strings, accountPeerId: item.account.peerId).0
}
if let applyImage = applyImage {
applyImage()
self.imageNode.isHidden = false

View File

@ -769,8 +769,8 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
override func revealOptionsInteractivelyOpened() {
if let item = self.item {
switch item.peer {
case let .peer(peer, _):
if let peer = peer {
case let .peer(peer, chatPeer):
if let peer = chatPeer ?? peer {
item.setPeerIdWithRevealedOptions?(peer.id, nil)
}
case .deviceContact:
@ -782,8 +782,8 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
override func revealOptionsInteractivelyClosed() {
if let item = self.item {
switch item.peer {
case let .peer(peer, _):
if let peer = peer {
case let .peer(peer, chatPeer):
if let peer = chatPeer ?? peer {
item.setPeerIdWithRevealedOptions?(nil, peer.id)
}
case .deviceContact:
@ -795,8 +795,8 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
if let item = self.item {
switch item.peer {
case let .peer(peer, _):
if let peer = peer {
case let .peer(peer, chatPeer):
if let peer = chatPeer ?? peer {
item.deletePeer?(peer.id)
}
case .deviceContact:

View File

@ -13,9 +13,11 @@ private enum CreatePasswordField {
private final class CreatePasswordControllerArguments {
let updateFieldText: (CreatePasswordField, String) -> Void
let cancelEmailConfirmation: () -> Void
init(updateFieldText: @escaping (CreatePasswordField, String) -> Void) {
init(updateFieldText: @escaping (CreatePasswordField, String) -> Void, cancelEmailConfirmation: @escaping () -> Void) {
self.updateFieldText = updateFieldText
self.cancelEmailConfirmation = cancelEmailConfirmation
}
}
@ -23,6 +25,7 @@ private enum CreatePasswordSection: Int32 {
case password
case hint
case email
case emailCancel
}
private enum CreatePasswordEntryTag: ItemListItemTag {
@ -54,14 +57,19 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
case email(PresentationTheme, String, String)
case emailInfo(PresentationTheme, String)
case emailConfirmation(PresentationTheme, String)
case emailCancel(PresentationTheme, String, Bool)
var section: ItemListSectionId {
switch self {
case .passwordHeader, .password, .passwordConfirmation, .passwordInfo:
return CreatePasswordSection.password.rawValue
case .hintHeader, .hint, .hintInfo:
return CreatePasswordSection.hint.rawValue
case .emailHeader, .email, .emailInfo:
case .emailHeader, .email, .emailInfo, .emailConfirmation:
return CreatePasswordSection.email.rawValue
case .emailCancel:
return CreatePasswordSection.emailCancel.rawValue
}
}
@ -89,6 +97,10 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
return 8
case .emailInfo:
return 9
case .emailConfirmation:
return 10
case .emailCancel:
return 11
}
}
@ -115,8 +127,8 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
case let .hintHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .hint(theme, text, value):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .regular(capitalization: true, autocorrection: false), spacing: 0.0, tag: CreatePasswordEntryTag.password, sectionId: self.section, textUpdated: { updatedText in
arguments.updateFieldText(.password, updatedText)
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .regular(capitalization: true, autocorrection: false), spacing: 0.0, tag: CreatePasswordEntryTag.hint, sectionId: self.section, textUpdated: { updatedText in
arguments.updateFieldText(.hint, updatedText)
}, action: {
})
case let .hintInfo(theme, text):
@ -124,51 +136,75 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
case let .emailHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .email(theme, text, value):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .email, spacing: 0.0, tag: CreatePasswordEntryTag.password, sectionId: self.section, textUpdated: { updatedText in
arguments.updateFieldText(.password, updatedText)
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .email, spacing: 0.0, tag: CreatePasswordEntryTag.email, sectionId: self.section, textUpdated: { updatedText in
arguments.updateFieldText(.email, updatedText)
}, action: {
})
case let .emailInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .emailConfirmation(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .emailCancel(theme, text, enabled):
return ItemListActionItem(theme: theme, title: text, kind: enabled ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.cancelEmailConfirmation()
})
}
}
}
private struct CreatePasswordControllerState: Equatable {
var state: CreatePasswordState
var passwordText: String = ""
var passwordConfirmationText: String = ""
var hintText: String = ""
var emailText: String = ""
var saving: Bool = false
var pendingEmail: String? = nil
init(state: CreatePasswordState) {
self.state = state
}
}
private func createPasswordControllerEntries(presentationData: PresentationData, state: CreatePasswordControllerState) -> [CreatePasswordEntry] {
var entries: [CreatePasswordEntry] = []
entries.append(.passwordHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordSection))
entries.append(.password(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordPlaceholder, state.passwordText))
entries.append(.passwordConfirmation(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordConfirmationPlaceholder, state.passwordConfirmationText))
entries.append(.passwordInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordHelp))
entries.append(.hintHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintSection))
entries.append(.hint(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintPlaceholder, state.hintText))
entries.append(.hintInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintHelp))
entries.append(.emailHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_EmailSection))
entries.append(.email(presentationData.theme, presentationData.strings.FastTwoStepSetup_EmailPlaceholder, state.emailText))
entries.append(.emailInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_EmailHelp))
switch state.state {
case let .setup(currentPassword):
entries.append(.passwordHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordSection))
entries.append(.password(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordPlaceholder, state.passwordText))
entries.append(.passwordConfirmation(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordConfirmationPlaceholder, state.passwordConfirmationText))
entries.append(.passwordInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordHelp))
entries.append(.hintHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintSection))
entries.append(.hint(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintPlaceholder, state.hintText))
entries.append(.hintInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintHelp))
if currentPassword == nil {
entries.append(.emailHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_EmailSection))
entries.append(.email(presentationData.theme, presentationData.strings.FastTwoStepSetup_EmailPlaceholder, state.emailText))
entries.append(.emailInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_EmailHelp))
}
case let .pendingVerification(emailPattern):
entries.append(.emailConfirmation(presentationData.theme, presentationData.strings.TwoStepAuth_ConfirmationText + "\n\(emailPattern)"))
entries.append(.emailCancel(presentationData.theme, presentationData.strings.TwoStepAuth_ConfirmationAbort, !state.saving))
}
return entries
}
func createPasswordController(account: Account, completion: @escaping (String, String) -> Void) -> ViewController {
let statePromise = ValuePromise(CreatePasswordControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: CreatePasswordControllerState())
enum CreatePasswordState: Equatable {
case setup(currentPassword: String?)
case pendingVerification(emailPattern: String)
}
func createPasswordController(account: Account, state: CreatePasswordState, completion: @escaping (String, String, Bool) -> Void, updatePasswordEmailConfirmation: @escaping (String?) -> Void, processPasswordEmailConfirmation: Bool = true) -> ViewController {
let statePromise = ValuePromise(CreatePasswordControllerState(state: state), ignoreRepeated: true)
let stateValue = Atomic(value: CreatePasswordControllerState(state: state))
let updateState: ((CreatePasswordControllerState) -> CreatePasswordControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
var dismissImpl: (() -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
let actionsDisposable = DisposableSet()
@ -191,6 +227,36 @@ func createPasswordController(account: Account, completion: @escaping (String, S
}
return state
}
}, cancelEmailConfirmation: {
var currentPassword: String?
updateState { state in
var state = state
switch state.state {
case let .setup(password):
currentPassword = password
case .pendingVerification:
currentPassword = nil
}
state.saving = true
return state
}
saveDisposable.set((updateTwoStepVerificationPassword(network: account.network, currentPassword: currentPassword, updatedPassword: .none)
|> deliverOnMainQueue).start(next: { _ in
updateState { state in
var state = state
state.saving = false
state.state = .setup(currentPassword: nil)
return state
}
updatePasswordEmailConfirmation(nil)
}, error: { _ in
updateState { state in
var state = state
state.saving = false
return state
}
}))
})
var initialFocusImpl: (() -> Void)?
@ -199,63 +265,109 @@ func createPasswordController(account: Account, completion: @escaping (String, S
|> deliverOnMainQueue
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<CreatePasswordEntry>, CreatePasswordEntry.ItemGenerationArguments)) in
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
})
var rightNavigationButton: ItemListNavigationButton?
if state.saving {
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
} else {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.passwordText.isEmpty, action: {
var state: CreatePasswordControllerState?
updateState { s in
state = s
return s
}
if let state = state {
if state.passwordText.isEmpty {
} else if state.passwordText != state.passwordConfirmationText {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
} else {
let saveImpl: () -> Void = {
updateState { state in
var state = state
state.saving = true
return state
}
saveDisposable.set((updateTwoStepVerificationPassword(network: account.network, currentPassword: nil, updatedPassword: .password(password: state.passwordText, hint: state.hintText, email: state.emailText))
|> deliverOnMainQueue).start(next: { update in
switch update {
case .none:
break
case let .password(password, pendingEmailPattern):
if let pendingEmailPattern = pendingEmailPattern {
updateState { state in
var state = state
state.saving = false
state.pendingEmail = pendingEmailPattern
return state
switch state.state {
case .setup:
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.passwordText.isEmpty, action: {
var state: CreatePasswordControllerState?
updateState { s in
state = s
return s
}
if let state = state {
if state.passwordText.isEmpty {
} else if state.passwordText != state.passwordConfirmationText {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
} else {
let saveImpl: () -> Void = {
var currentPassword: String?
var email: String?
updateState { state in
var state = state
if case let .setup(password) = state.state {
currentPassword = password
if password != nil {
email = nil
} else {
email = state.emailText
}
} else {
completion(password, state.hintText)
}
state.saving = true
return state
}
saveDisposable.set((updateTwoStepVerificationPassword(network: account.network, currentPassword: currentPassword, updatedPassword: .password(password: state.passwordText, hint: state.hintText, email: email))
|> deliverOnMainQueue).start(next: { update in
switch update {
case .none:
break
case let .password(password, pendingEmailPattern):
if let pendingEmailPattern = pendingEmailPattern {
if processPasswordEmailConfirmation {
updateState { state in
var state = state
state.saving = false
state.state = .pendingVerification(emailPattern: pendingEmailPattern)
return state
}
}
updatePasswordEmailConfirmation(pendingEmailPattern)
} else {
completion(password, state.hintText, !state.emailText.isEmpty)
}
}
}, error: { _ in
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}))
}
}, error: { _ in
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}))
var emailAlert = false
switch state.state {
case let .setup(currentPassword):
if currentPassword != nil {
emailAlert = false
} else {
emailAlert = state.emailText.isEmpty
}
case .pendingVerification:
break
}
if emailAlert {
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: presentationData.strings.TwoStepAuth_EmailSkip, action: {
saveImpl()
})]), nil)
} else {
saveImpl()
}
}
}
if state.emailText.isEmpty {
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: presentationData.strings.TwoStepAuth_EmailSkip, action: {
saveImpl()
})]), nil)
} else {
saveImpl()
}
}
}
})
})
case .pendingVerification:
break
}
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.FastTwoStepSetup_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let title: String
switch state.state {
case let .setup(currentPassword):
if currentPassword != nil {
title = presentationData.strings.TwoStepAuth_ChangePassword
} else {
title = presentationData.strings.FastTwoStepSetup_Title
}
case .pendingVerification:
title = presentationData.strings.FastTwoStepSetup_Title
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(entries: createPasswordControllerEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: CreatePasswordEntryTag.password, emptyStateItem: nil, animateChanges: false)
return (controllerState, (listState, arguments))
@ -265,6 +377,10 @@ func createPasswordController(account: Account, completion: @escaping (String, S
}
let controller = ItemListController(account: account, 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)

View File

@ -97,7 +97,8 @@ private let list = PresentationThemeList(
freeInputField: PresentationInputFieldTheme(
backgroundColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5),
placeholderColor: UIColor(rgb: 0x4d4d4d),
primaryColor: .white
primaryColor: .white,
controlColor: UIColor(rgb: 0x4d4d4d)
)
)

View File

@ -97,7 +97,8 @@ private let list = PresentationThemeList(
freeInputField: PresentationInputFieldTheme(
backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.5),
placeholderColor: UIColor(rgb: 0x4d4d4d),
primaryColor: .white
primaryColor: .white,
controlColor: UIColor(rgb: 0x4d4d4d)
)
)

View File

@ -97,7 +97,8 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
freeInputField: PresentationInputFieldTheme(
backgroundColor: UIColor(rgb: 0xd6d6dc),
placeholderColor: UIColor(rgb: 0x96979d),
primaryColor: .black
primaryColor: .black,
controlColor: UIColor(rgb: 0x96979d)
)
)

View File

@ -333,7 +333,7 @@ class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDelegate, It
}
}
@objc func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
@objc private func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.count > 1, let item = self.item, let processPaste = item.processPaste {
let result = processPaste(string)
if result != string {
@ -352,4 +352,9 @@ class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDelegate, It
}
return true
}
@objc func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.item?.action()
return false
}
}

File diff suppressed because it is too large Load Diff

View File

@ -259,11 +259,13 @@ public final class PresentationInputFieldTheme {
public let backgroundColor: UIColor
public let placeholderColor: UIColor
public let primaryColor: UIColor
public let controlColor: UIColor
public init(backgroundColor: UIColor, placeholderColor: UIColor, primaryColor: UIColor) {
public init(backgroundColor: UIColor, placeholderColor: UIColor, primaryColor: UIColor, controlColor: UIColor) {
self.backgroundColor = backgroundColor
self.placeholderColor = placeholderColor
self.primaryColor = primaryColor
self.controlColor = controlColor
}
}

View File

@ -0,0 +1,227 @@
import Foundation
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
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, 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(_ arguments: ResetPasswordControllerArguments) -> ListViewItem {
switch self {
case let .code(theme, text, value):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: text), 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(theme: theme, text: .plain(text), sectionId: self.section)
case let .helpInfo(theme, text):
return ItemListTextItem(theme: theme, 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.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
}
enum ResetPasswordState: Equatable {
case setup(currentPassword: String?)
case pendingVerification(emailPattern: String)
}
func resetPasswordController(account: Account, emailPattern: String, completion: @escaping () -> 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 = account.telegramApplicationContext.currentPresentationData.with { $0 }
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.TwoStepAuth_RecoveryFailed, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
})
var initialFocusImpl: (() -> Void)?
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get())
|> deliverOnMainQueue
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<ResetPasswordEntry>, ResetPasswordEntry.ItemGenerationArguments)) 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((recoverTwoStepVerificationPassword(network: account.network, code: state.code)
|> 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 .codeExpired:
text = presentationData.strings.TwoStepAuth_RecoveryCodeExpired
case .limitExceeded:
text = presentationData.strings.TwoStepAuth_FloodError
case .generic:
text = presentationData.strings.Login_UnknownError
}
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}, completed: {
completion()
}))
}
})
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.TwoStepAuth_RecoveryTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(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(account: account, 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 = {
initialFocusImpl?()
}
return controller
}

View File

@ -9,16 +9,18 @@ final class SecureIdAuthControllerInteraction {
let updateState: ((SecureIdAuthControllerState) -> SecureIdAuthControllerState) -> Void
let present: (ViewController, Any?) -> Void
let checkPassword: (String) -> Void
let openPasswordHelp: () -> Void
let setupPassword: () -> Void
let grant: () -> Void
let openUrl: (String) -> Void
let openMention: (TelegramPeerMention) -> Void
let deleteAll: () -> Void
fileprivate init(updateState: @escaping ((SecureIdAuthControllerState) -> SecureIdAuthControllerState) -> Void, present: @escaping (ViewController, Any?) -> Void, checkPassword: @escaping (String) -> Void, setupPassword: @escaping () -> Void, grant: @escaping () -> Void, openUrl: @escaping (String) -> Void, openMention: @escaping (TelegramPeerMention) -> Void, deleteAll: @escaping () -> Void) {
fileprivate init(updateState: @escaping ((SecureIdAuthControllerState) -> SecureIdAuthControllerState) -> Void, present: @escaping (ViewController, Any?) -> Void, checkPassword: @escaping (String) -> Void, openPasswordHelp: @escaping () -> Void, setupPassword: @escaping () -> Void, grant: @escaping () -> Void, openUrl: @escaping (String) -> Void, openMention: @escaping (TelegramPeerMention) -> Void, deleteAll: @escaping () -> Void) {
self.updateState = updateState
self.present = present
self.checkPassword = checkPassword
self.openPasswordHelp = openPasswordHelp
self.setupPassword = setupPassword
self.grant = grant
self.openUrl = openUrl
@ -46,6 +48,7 @@ final class SecureIdAuthController: ViewController {
private let challengeDisposable = MetaDisposable()
private var formDisposable: Disposable?
private let deleteDisposable = MetaDisposable()
private let recoveryDisposable = MetaDisposable()
private var state: SecureIdAuthControllerState
@ -78,9 +81,9 @@ final class SecureIdAuthController: ViewController {
strongSelf.updateState { state in
var state = state
if data.currentPasswordDerivation != nil {
state.verificationState = .passwordChallenge(data.currentHint ?? "", .none)
state.verificationState = .passwordChallenge(hint: data.currentHint ?? "", state: .none, hasRecoveryEmail: data.hasRecovery)
} else {
state.verificationState = .noChallenge
state.verificationState = .noChallenge(data.unconfirmedEmailPattern)
}
return state
}
@ -153,6 +156,7 @@ final class SecureIdAuthController: ViewController {
self.challengeDisposable.dispose()
self.formDisposable?.dispose()
self.deleteDisposable.dispose()
self.recoveryDisposable.dispose()
}
override func viewDidAppear(_ animated: Bool) {
@ -178,6 +182,8 @@ final class SecureIdAuthController: ViewController {
self?.present(c, in: .window(.root), with: a)
}, checkPassword: { [weak self] password in
self?.checkPassword(password: password, inBackground: false, completion: {})
}, openPasswordHelp: { [weak self] in
self?.openPasswordHelp()
}, setupPassword: { [weak self] in
self?.setupPassword()
}, grant: { [weak self] in
@ -240,17 +246,17 @@ final class SecureIdAuthController: ViewController {
let state = f(self.state)
if state != self.state {
var previousHadProgress = false
if let verificationState = self.state.verificationState, case .passwordChallenge(_, .checking) = verificationState {
if let verificationState = self.state.verificationState, case .passwordChallenge(_, .checking, _) = verificationState {
previousHadProgress = true
}
var updatedHasProgress = false
if let verificationState = state.verificationState, case .passwordChallenge(_, .checking) = verificationState {
if let verificationState = state.verificationState, case .passwordChallenge(_, .checking, _) = verificationState {
updatedHasProgress = true
}
self.state = state
if self.isNodeLoaded {
self.controllerNode.updateState(self.state, transition: .animated(duration: 0.3, curve: .spring))
self.controllerNode.updateState(self.state, transition: animated ? .animated(duration: 0.3, curve: .spring) : .immediate)
}
if previousHadProgress != updatedHasProgress {
@ -269,7 +275,7 @@ final class SecureIdAuthController: ViewController {
}
@objc private func checkPassword(password: String, inBackground: Bool, completion: @escaping () -> Void) {
if let verificationState = self.state.verificationState, case let .passwordChallenge(hint, challengeState) = verificationState {
if let verificationState = self.state.verificationState, case let .passwordChallenge(hint, challengeState, hasRecoveryEmail) = verificationState {
switch challengeState {
case .none, .invalid:
break
@ -278,12 +284,12 @@ final class SecureIdAuthController: ViewController {
}
self.updateState(animated: !inBackground, { state in
var state = state
state.verificationState = .passwordChallenge(hint, .checking)
state.verificationState = .passwordChallenge(hint: hint, state: .checking, hasRecoveryEmail: hasRecoveryEmail)
return state
})
self.challengeDisposable.set((accessSecureId(network: self.account.network, password: password)
|> deliverOnMainQueue).start(next: { [weak self] context in
guard let strongSelf = self, let verificationState = strongSelf.state.verificationState, case .passwordChallenge(_, .checking) = verificationState else {
guard let strongSelf = self, let verificationState = strongSelf.state.verificationState, case .passwordChallenge(_, .checking, _) = verificationState else {
return
}
strongSelf.updateState(animated: !inBackground, { state in
@ -322,10 +328,10 @@ final class SecureIdAuthController: ViewController {
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
if let verificationState = strongSelf.state.verificationState, case let .passwordChallenge(hint, .checking) = verificationState {
if let verificationState = strongSelf.state.verificationState, case let .passwordChallenge(hint, .checking, hasRecoveryEmail) = verificationState {
strongSelf.updateState(animated: !inBackground, { state in
var state = state
state.verificationState = .passwordChallenge(hint, .invalid)
state.verificationState = .passwordChallenge(hint: hint, state: .invalid, hasRecoveryEmail: hasRecoveryEmail)
return state
})
}
@ -334,26 +340,87 @@ final class SecureIdAuthController: ViewController {
}
}
@objc private func setupPassword() {
var completionImpl: ((String, String) -> Void)?
let controller = createPasswordController(account: self.account, completion: { password, hint in
completionImpl?(password, hint)
private func openPasswordHelp() {
guard let verificationState = self.state.verificationState, case let .passwordChallenge(passwordChallenge) = verificationState, case .none = passwordChallenge.state else {
return
}
if passwordChallenge.hasRecoveryEmail {
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: self.presentationData.theme), title: self.presentationData.strings.Passport_ForgottenPassword, text: self.presentationData.strings.Passport_PasswordReset, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Login_ResetAccountProtected_Reset, action: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.recoveryDisposable.set((requestTwoStepVerificationPasswordRecoveryCode(network: strongSelf.account.network)
|> deliverOnMainQueue).start(next: { emailPattern in
guard let strongSelf = self else {
return
}
var completionImpl: (() -> Void)?
let controller = resetPasswordController(account: strongSelf.account, emailPattern: emailPattern, completion: {
completionImpl?()
})
completionImpl = { [weak controller] in
guard let strongSelf = self else {
return
}
strongSelf.updateState(animated: false, { state in
var state = state
state.verificationState = .noChallenge(nil)
return state
})
controller?.view.endEditing(true)
controller?.dismiss()
strongSelf.setupPassword()
}
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}))
})]), in: .window(.root))
} else {
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: self.presentationData.theme), title: nil, text: self.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}
private func setupPassword() {
guard let verificationState = self.state.verificationState, case let .noChallenge(emailPattern) = verificationState else {
return
}
var completionImpl: ((String, String, Bool) -> Void)?
let state: CreatePasswordState
if let emailPattern = emailPattern {
state = .pendingVerification(emailPattern: emailPattern)
} else {
state = .setup(currentPassword: nil)
}
let controller = createPasswordController(account: self.account, state: state, completion: { password, hint, hasRecoveryEmail in
completionImpl?(password, hint, hasRecoveryEmail)
}, updatePasswordEmailConfirmation: { [weak self] pattern in
guard let strongSelf = self else {
return
}
strongSelf.updateState(animated: false, { state in
var state = state
if let verificationState = state.verificationState, case .noChallenge = verificationState {
state.verificationState = .noChallenge(pattern)
}
return state
})
})
completionImpl = { [weak self, weak controller] password, hint in
completionImpl = { [weak self, weak controller] password, hint, hasRecoveryEmail in
guard let strongSelf = self else {
controller?.dismiss()
return
}
strongSelf.updateState(animated: false, { state in
var state = state
state.verificationState = .passwordChallenge(hint, .none)
state.verificationState = .passwordChallenge(hint: hint, state: .none, hasRecoveryEmail: hasRecoveryEmail)
return state
})
strongSelf.checkPassword(password: password, inBackground: true, completion: {
controller?.view.endEditing(true)
controller?.dismiss()
})
}
self.present(controller, in: .window(.root))
self.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
@objc private func grantAccess() {

View File

@ -58,7 +58,12 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight)
var insets = layout.insets(options: [.input])
var insetOptions: ContainerViewLayoutInsetOptions = []
if self.contentNode is SecureIdAuthPasswordOptionContentNode {
insetOptions.insert(.input)
}
var insets = layout.insets(options: insetOptions)
insets.bottom = max(insets.bottom, layout.safeInsets.bottom)
let headerNodeTransition: ContainedViewLayoutTransition = headerNode.bounds.isEmpty ? .immediate : transition
@ -152,7 +157,11 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
if let contentNode = self.contentNode {
self.scrollNode.addSubnode(contentNode)
if let _ = self.validLayout {
self.scheduleLayoutTransitionRequest(.animated(duration: 0.5, curve: .spring))
if transition.isAnimated {
self.scheduleLayoutTransitionRequest(.animated(duration: 0.5, curve: .spring))
} else {
self.scheduleLayoutTransitionRequest(.immediate)
}
}
}
}
@ -180,7 +189,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
})
contentNode = current
}
case let .passwordChallenge(hint, challengeState):
case let .passwordChallenge(hint, challengeState, _):
if let current = self.contentNode as? SecureIdAuthPasswordOptionContentNode {
current.updateIsChecking(challengeState == .checking)
contentNode = current
@ -190,9 +199,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
strongSelf.interaction.checkPassword(password)
}
}, passwordHelp: { [weak self] in
if let strongSelf = self {
}
self?.interaction.openPasswordHelp()
})
current.updateIsChecking(challengeState == .checking)
contentNode = current
@ -227,7 +234,9 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
if case .verified = verificationState {
if self.acceptNode.supernode == nil {
self.addSubnode(self.acceptNode)
self.acceptNode.layer.animatePosition(from: CGPoint(x: 0.0, y: self.acceptNode.bounds.height), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
if transition.isAnimated {
self.acceptNode.layer.animatePosition(from: CGPoint(x: 0.0, y: self.acceptNode.bounds.height), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
}
}
@ -253,7 +262,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
var contentNode: (ASDisplayNode & SecureIdAuthContentNode)?
switch verificationState {
case let .passwordChallenge(hint, challengeState):
case let .passwordChallenge(hint, challengeState, _):
if let current = self.contentNode as? SecureIdAuthPasswordOptionContentNode {
current.updateIsChecking(challengeState == .checking)
contentNode = current

View File

@ -16,32 +16,9 @@ enum SecureIdAuthPasswordChallengeState {
}
enum SecureIdAuthControllerVerificationState: Equatable {
case noChallenge
case passwordChallenge(String, SecureIdAuthPasswordChallengeState)
case noChallenge(String?)
case passwordChallenge(hint: String, state: SecureIdAuthPasswordChallengeState, hasRecoveryEmail: Bool)
case verified(SecureIdAccessContext)
static func ==(lhs: SecureIdAuthControllerVerificationState, rhs: SecureIdAuthControllerVerificationState) -> Bool {
switch lhs {
case .noChallenge:
if case .noChallenge = rhs {
return true
} else {
return false
}
case let .passwordChallenge(hint, state):
if case .passwordChallenge(hint, state) = rhs {
return true
} else {
return false
}
case .verified:
if case .verified = rhs {
return true
} else {
return false
}
}
}
}
struct SecureIdAuthControllerFormState: Equatable {

View File

@ -13,6 +13,7 @@ final class SecureIdAuthPasswordOptionContentNode: ASDisplayNode, SecureIdAuthCo
private let titleNode: ImmediateTextNode
private let inputBackground: ASImageNode
private let inputField: TextFieldNode
private let inputButtonNode: HighlightableButtonNode
private let buttonNode: HighlightableButtonNode
private let buttonBackground: ASImageNode
@ -39,6 +40,13 @@ final class SecureIdAuthPasswordOptionContentNode: ASDisplayNode, SecureIdAuthCo
self.titleNode.textAlignment = .center
self.inputField = TextFieldNode()
self.inputButtonNode = HighlightableButtonNode()
if let image = generateTintedImage(image: UIImage(bundleImageName: "Secure ID/PasswordHelpIcon"), color: theme.list.freeInputField.controlColor) {
self.inputButtonNode.setImage(image, for: [])
self.inputButtonNode.frame = CGRect(origin: CGPoint(), size: image.size)
}
self.inputBackground.image = generateStretchableFilledCircleImage(radius: 10.0, color: theme.list.freeInputField.backgroundColor)
self.inputField.textField.isSecureTextEntry = true
@ -65,6 +73,7 @@ final class SecureIdAuthPasswordOptionContentNode: ASDisplayNode, SecureIdAuthCo
self.inputContainer.addSubnode(self.titleNode)
self.inputContainer.addSubnode(self.inputBackground)
self.inputContainer.addSubnode(self.inputField)
self.inputContainer.addSubnode(self.inputButtonNode)
self.inputContainer.addSubnode(self.buttonNode)
self.addSubnode(self.inputContainer)
@ -84,6 +93,9 @@ final class SecureIdAuthPasswordOptionContentNode: ASDisplayNode, SecureIdAuthCo
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.inputField.textField.delegate = self
self.inputButtonNode.hitTestSlop = UIEdgeInsets(top: -4.0, left: -4.0, bottom: -4.0, right: -4.0)
self.inputButtonNode.addTarget(self, action: #selector(self.inputButtonPressed), forControlEvents: .touchUpInside)
}
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> SecureIdAuthContentLayout {
@ -112,6 +124,8 @@ final class SecureIdAuthPasswordOptionContentNode: ASDisplayNode, SecureIdAuthCo
transition.updateFrame(node: self.inputBackground, frame: inputFrame)
transition.updateFrame(node: self.inputField, frame: inputFrame.insetBy(dx: 6.0, dy: 0.0))
transition.updateFrame(node: self.inputButtonNode, frame: CGRect(origin: CGPoint(x: inputFrame.maxX - self.inputButtonNode.bounds.size.width - 6.0, y: inputFrame.minY + floor((inputFrame.height - self.inputButtonNode.bounds.size.height) / 2.0)), size: self.inputButtonNode.bounds.size))
let buttonBounds = CGRect(origin: CGPoint(), size: buttonSize)
transition.updateFrame(node: self.buttonNode, frame: buttonBounds.offsetBy(dx: floor((width - buttonSize.width) / 2.0), dy: inputFrame.maxY + buttonSpacing))
transition.updateFrame(node: self.buttonBackground, frame: buttonBounds)
@ -152,6 +166,10 @@ final class SecureIdAuthPasswordOptionContentNode: ASDisplayNode, SecureIdAuthCo
}
}
@objc private func inputButtonPressed() {
self.passwordHelp()
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return !self.isChecking
}

View File

@ -229,7 +229,7 @@ private func twoStepVerificationPasswordEntryControllerEntries(presentationData:
entries.append(.passwordEntryTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupPasswordEnterPasswordNew))
entries.append(.passwordEntry(presentationData.theme, text))
case let .reentry(_, text):
entries.append(.passwordEntryTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupPasswordEnterPasswordChange))
entries.append(.passwordEntryTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupPasswordConfirmPassword))
entries.append(.passwordEntry(presentationData.theme, text))
case let .hint(_, text):
entries.append(.hintTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupHint))
@ -416,7 +416,15 @@ func twoStepVerificationPasswordEntryController(account: Account, mode: TwoStepV
})
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.TwoStepAuth_EnterPasswordTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let title: String
switch mode {
case .setup, .change:
title = presentationData.strings.TwoStepAuth_EnterPasswordTitle
case .setupEmail:
title = presentationData.strings.TwoStepAuth_EmailTitle
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(entries: twoStepVerificationPasswordEntryControllerEntries(presentationData: presentationData, state: state, mode: mode), style: .blocks, focusItemTag: TwoStepVerificationPasswordEntryTag.input, emptyStateItem: nil, animateChanges: false)
return (controllerState, (listState, arguments))
@ -431,6 +439,7 @@ func twoStepVerificationPasswordEntryController(account: Account, mode: TwoStepV
}
}
dismissImpl = { [weak controller] in
controller?.view.endEditing(true)
controller?.dismiss()
}

View File

@ -6,14 +6,16 @@ import TelegramCore
private final class TwoStepVerificationUnlockSettingsControllerArguments {
let updatePasswordText: (String) -> Void
let checkPassword: () -> Void
let openForgotPassword: () -> Void
let openSetupPassword: () -> Void
let openDisablePassword: () -> Void
let openSetupEmail: () -> Void
let openResetPendingEmail: () -> Void
init(updatePasswordText: @escaping (String) -> Void, openForgotPassword: @escaping () -> Void, openSetupPassword: @escaping () -> Void, openDisablePassword: @escaping () -> Void, openSetupEmail: @escaping () -> Void, openResetPendingEmail: @escaping () -> Void) {
init(updatePasswordText: @escaping (String) -> Void, checkPassword: @escaping () -> Void, openForgotPassword: @escaping () -> Void, openSetupPassword: @escaping () -> Void, openDisablePassword: @escaping () -> Void, openSetupEmail: @escaping () -> Void, openResetPendingEmail: @escaping () -> Void) {
self.updatePasswordText = updatePasswordText
self.checkPassword = checkPassword
self.openForgotPassword = openForgotPassword
self.openSetupPassword = openSetupPassword
self.openDisablePassword = openDisablePassword
@ -155,6 +157,7 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry {
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: text, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: "", type: .password, spacing: 10.0, tag: TwoStepVerificationUnlockSettingsEntryTag.password, sectionId: self.section, textUpdated: { updatedText in
arguments.updatePasswordText(updatedText)
}, action: {
arguments.checkPassword()
})
case let .passwordEntryInfo(theme, text):
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section, linkAction: { action in
@ -306,8 +309,46 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep
updateState {
$0.withUpdatedPasswordText(updatedText)
}
}, checkPassword: {
var wasChecking = false
var password: String?
updateState { state in
wasChecking = state.checking
password = state.passwordText
return state.withUpdatedChecking(true)
}
if let password = password, !password.isEmpty, !wasChecking {
checkDisposable.set((requestTwoStepVerifiationSettings(network: account.network, password: password) |> deliverOnMainQueue).start(next: { settings in
updateState {
$0.withUpdatedChecking(false)
}
replaceControllerImpl?(twoStepVerificationUnlockSettingsController(account: account, mode: .manage(password: password, email: settings.email, pendingEmailPattern: "", hasSecureValues: settings.secureSecret != nil)))
}, error: { error in
updateState {
$0.withUpdatedChecking(false)
}
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let text: String
switch error {
case .limitExceeded:
text = presentationData.strings.LoginPassword_FloodError
case .invalidPassword:
text = presentationData.strings.LoginPassword_InvalidPasswordError
case .generic:
text = presentationData.strings.Login_UnknownError
}
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}))
}
}, openForgotPassword: {
setupDisposable.set((dataPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { data in
setupDisposable.set((dataPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { data in
switch data {
case let .access(configuration):
if let configuration = configuration {
@ -318,17 +359,22 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep
updateState {
$0.withUpdatedChecking(true)
}
setupResultDisposable.set((requestTwoStepVerificationPasswordRecoveryCode(network: account.network) |> deliverOnMainQueue).start(next: { emailPattern in
setupResultDisposable.set((requestTwoStepVerificationPasswordRecoveryCode(network: account.network)
|> deliverOnMainQueue).start(next: { emailPattern in
updateState {
$0.withUpdatedChecking(false)
}
let result = Promise<Bool>()
let controller = twoStepVerificationResetController(account: account, emailPattern: emailPattern, result: result)
var completionImpl: (() -> Void)?
let controller = resetPasswordController(account: account, emailPattern: emailPattern, completion: {
completionImpl?()
})
completionImpl = { [weak controller] in
dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationConfiguration.notSet(pendingEmailPattern: ""))))
controller?.view.endEditing(true)
controller?.dismiss()
}
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
setupDisposable.set((result.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] _ in
dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationConfiguration.notSet(pendingEmailPattern: ""))))
controller?.dismiss()
}))
}, error: { _ in
updateState {
$0.withUpdatedChecking(false)
@ -347,13 +393,39 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep
}
}))
}, openSetupPassword: {
setupDisposable.set((dataPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { data in
setupDisposable.set((dataPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { data in
switch data {
case let .access(configuration):
if let configuration = configuration {
switch configuration {
case .notSet:
let result = Promise<TwoStepVerificationPasswordEntryResult?>()
var completionImpl: ((String, String, Bool) -> Void)?
var updatePatternImpl: ((String?) -> Void)?
let controller = createPasswordController(account: account, state: .setup(currentPassword: nil), completion: { password, hint, emailPattern in
completionImpl?(password, hint, emailPattern)
}, updatePasswordEmailConfirmation: { pattern in
updatePatternImpl?(pattern)
}, processPasswordEmailConfirmation: false)
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
completionImpl = { [weak controller] password, hint, hasRecovery in
dataPromise.set(.single(.manage(password: password, emailSet: hasRecovery, pendingEmailPattern: "", hasSecureValues: false)))
controller?.view.endEditing(true)
controller?.dismiss()
}
updatePatternImpl = { [weak controller] pattern in
if let pattern = pattern {
dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: .notSet(pendingEmailPattern: pattern))))
} else {
dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: .notSet(pendingEmailPattern: ""))))
}
controller?.view.endEditing(true)
controller?.dismiss()
}
/*let result = Promise<TwoStepVerificationPasswordEntryResult?>()
let controller = twoStepVerificationPasswordEntryController(account: account, mode: .setup, result: result)
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
setupResultDisposable.set((result.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] updatedPassword in
@ -365,12 +437,36 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep
}
controller?.dismiss()
}
}))
}))*/
case .set:
break
}
}
case let .manage(password, emailSet, pendingEmailPattern, hasSecureValues):
case let .manage(password, hasRecovery, pendingEmailPattern, hasSecureValues):
var completionImpl: ((String, String, Bool) -> Void)?
var updatePatternImpl: ((String?) -> Void)?
let controller = createPasswordController(account: account, state: .setup(currentPassword: password), completion: { password, hint, emailPattern in
completionImpl?(password, hint, emailPattern)
}, updatePasswordEmailConfirmation: { pattern in
updatePatternImpl?(pattern)
}, processPasswordEmailConfirmation: false)
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
completionImpl = { [weak controller] password, hint, _ in
dataPromise.set(.single(.manage(password: password, emailSet: hasRecovery, pendingEmailPattern: pendingEmailPattern, hasSecureValues: hasSecureValues)))
controller?.view.endEditing(true)
controller?.dismiss()
}
updatePatternImpl = { [weak controller] pattern in
if let pattern = pattern {
dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: .notSet(pendingEmailPattern: pattern))))
} else {
dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: .notSet(pendingEmailPattern: ""))))
}
controller?.view.endEditing(true)
controller?.dismiss()
}
/*
let result = Promise<TwoStepVerificationPasswordEntryResult?>()
let controller = twoStepVerificationPasswordEntryController(account: account, mode: .change(current: password), result: result)
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
@ -379,7 +475,7 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep
dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.manage(password: updatedPassword.password, emailSet: emailSet, pendingEmailPattern: pendingEmailPattern, hasSecureValues: hasSecureValues)))
controller?.dismiss()
}
}))
}))*/
}
}))
}, openDisablePassword: {
@ -485,41 +581,7 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep
break
case let .set(_, _, _, hasSecureValues):
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Next), style: .bold, enabled: true, action: {
var wasChecking = false
var password: String?
updateState { state in
wasChecking = state.checking
password = state.passwordText
return state.withUpdatedChecking(true)
}
if let password = password, !wasChecking {
checkDisposable.set((requestTwoStepVerifiationSettings(network: account.network, password: password) |> deliverOnMainQueue).start(next: { settings in
updateState {
$0.withUpdatedChecking(false)
}
replaceControllerImpl?(twoStepVerificationUnlockSettingsController(account: account, mode: .manage(password: password, email: settings.email, pendingEmailPattern: "", hasSecureValues: hasSecureValues)))
}, error: { error in
updateState {
$0.withUpdatedChecking(false)
}
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let text: String
switch error {
case .limitExceeded:
text = presentationData.strings.LoginPassword_FloodError
case .invalidPassword:
text = presentationData.strings.LoginPassword_InvalidPasswordError
case .generic:
text = presentationData.strings.Login_UnknownError
}
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}))
}
arguments.checkPassword()
})
}
}