mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-05 05:51:42 +00:00
no message
This commit is contained in:
parent
1bde567623
commit
b04ddea9c5
@ -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 */,
|
||||
|
||||
@ -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)]))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -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)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -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)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -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
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
227
TelegramUI/ResetPasswordController.swift
Normal file
227
TelegramUI/ResetPasswordController.swift
Normal 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
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user