mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 11:20:18 +00:00
no message
This commit is contained in:
parent
c2149ecd2e
commit
8407cd1c5f
@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ModernMessageSelectionChecked@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ModernMessageSelectionUnchecked@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ModernContactSelectionChecked@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 795 B |
@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ModernContactSelectionEmpty@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 665 B |
@ -268,7 +268,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
default:
|
||||
break
|
||||
}
|
||||
if let codeLength = codeLength, let text = textField.text, text.characters.count == Int(codeLength) {
|
||||
if let codeLength = codeLength, let text = textField.text, text.count == Int(codeLength) {
|
||||
self.loginWithCode?(text)
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,7 +182,7 @@ func callListNodeEntriesForView(_ view: CallListView, state: CallListNodeState,
|
||||
}
|
||||
}
|
||||
if showSettings {
|
||||
result.append(.displayTabInfo(state.theme, state.strings.Calls_CallTabDescription))
|
||||
result.append(.displayTabInfo(state.theme, state.strings.CallSettings_TabIconDescription))
|
||||
result.append(.displayTab(state.theme, state.strings.Calls_CallTabTitle, showCallsTab))
|
||||
}
|
||||
return result
|
||||
|
||||
@ -3525,7 +3525,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin
|
||||
strongSelf.controllerNavigationDisposable.set((dataSignal
|
||||
|> deliverOnMainQueue).start(next: { peerAndContactData in
|
||||
if let strongSelf = self, let contactData = peerAndContactData.1, contactData.basicData.phoneNumbers.count != 0 {
|
||||
if contactData.basicData.phoneNumbers.count == 1, false {
|
||||
if contactData.isPrimitive {
|
||||
let phone = contactData.basicData.phoneNumbers[0].value
|
||||
let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peerAndContactData.0?.id, vCardData: nil)
|
||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||
|
||||
@ -707,7 +707,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
isPinned = item.index.pinningIndex != nil
|
||||
}
|
||||
|
||||
if item.enableContextActions && !isAd {
|
||||
if item.enableContextActions && !item.editing && !isAd {
|
||||
peerRevealOptions = revealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isPinned: isPinned, isMuted: item.account.peerId != item.index.messageIndex.id.peerId ? (currentMutedIconImage != nil) : nil, hasPeerGroupId: hasPeerGroupId, canDelete: true)
|
||||
if itemPeer.peerId != item.account.peerId {
|
||||
peerLeftRevealOptions = leftRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isUnread: unreadCount.unread)
|
||||
@ -721,7 +721,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
case .groupReference:
|
||||
let isPinned = item.index.pinningIndex != nil
|
||||
|
||||
if item.enableContextActions {
|
||||
if item.enableContextActions && !item.editing {
|
||||
peerRevealOptions = revealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isPinned: isPinned, isMuted: nil, hasPeerGroupId: nil, canDelete: false)
|
||||
} else {
|
||||
peerRevealOptions = []
|
||||
|
||||
@ -703,8 +703,9 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
let previousEntries = previousRecentItems.swap(entries)
|
||||
|
||||
let transition = chatListSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries, account: account, filter: filter, peerSelected: { peer in
|
||||
self?.recentListNode.clearHighlightAnimated(true)
|
||||
openPeer(peer, true)
|
||||
let _ = addRecentlySearchedPeer(postbox: account.postbox, peerId: peer.id).start()
|
||||
self?.recentListNode.clearHighlightAnimated(true)
|
||||
}, peerLongTapped: { peer in
|
||||
openRecentPeerOptions(peer)
|
||||
}, clearRecentlySearchedPeers: {
|
||||
|
||||
@ -734,7 +734,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
adminBadgeString = NSAttributedString(string: " \(item.presentationData.strings.Conversation_Admin)", font: inlineBotPrefixFont, textColor: incoming ? item.presentationData.theme.theme.chat.bubble.incomingSecondaryTextColor : item.presentationData.theme.theme.chat.bubble.outgoingSecondaryTextColor)
|
||||
}
|
||||
if let authorNameString = authorNameString, let authorNameColor = authorNameColor, let inlineBotNameString = inlineBotNameString {
|
||||
|
||||
let mutableString = NSMutableAttributedString(string: "\(authorNameString) ", attributes: [NSAttributedStringKey.font: nameFont, NSAttributedStringKey.foregroundColor: authorNameColor])
|
||||
let bodyAttributes = MarkdownAttributeSet(font: nameFont, textColor: inlineBotNameColor)
|
||||
let boldAttributes = MarkdownAttributeSet(font: inlineBotPrefixFont, textColor: inlineBotNameColor)
|
||||
@ -1501,6 +1500,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
item.controllerInteraction.updateInputState { textInputState in
|
||||
return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " "))
|
||||
}
|
||||
item.controllerInteraction.updateInputMode { _ in
|
||||
return .text
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -11,9 +11,6 @@ private let titleBoldFont = Font.medium(17.0)
|
||||
private let statusFont = Font.regular(13.0)
|
||||
private let badgeFont = Font.regular(14.0)
|
||||
|
||||
private let selectedImage = UIImage(bundleImageName: "Contact List/SelectionChecked")?.precomposed()
|
||||
private let selectableImage = UIImage(bundleImageName: "Contact List/SelectionUnchecked")?.precomposed()
|
||||
|
||||
enum ContactsPeerItemStatus {
|
||||
case none
|
||||
case presence(PeerPresence, PresentationTimeFormat)
|
||||
|
||||
@ -13,10 +13,14 @@ private enum CreatePasswordField {
|
||||
|
||||
private final class CreatePasswordControllerArguments {
|
||||
let updateFieldText: (CreatePasswordField, String) -> Void
|
||||
let selectNextInputItem: (CreatePasswordEntryTag) -> Void
|
||||
let save: () -> Void
|
||||
let cancelEmailConfirmation: () -> Void
|
||||
|
||||
init(updateFieldText: @escaping (CreatePasswordField, String) -> Void, cancelEmailConfirmation: @escaping () -> Void) {
|
||||
init(updateFieldText: @escaping (CreatePasswordField, String) -> Void, selectNextInputItem: @escaping (CreatePasswordEntryTag) -> Void, save: @escaping () -> Void, cancelEmailConfirmation: @escaping () -> Void) {
|
||||
self.updateFieldText = updateFieldText
|
||||
self.selectNextInputItem = selectNextInputItem
|
||||
self.save = save
|
||||
self.cancelEmailConfirmation = cancelEmailConfirmation
|
||||
}
|
||||
}
|
||||
@ -50,7 +54,7 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
|
||||
case passwordInfo(PresentationTheme, String)
|
||||
|
||||
case hintHeader(PresentationTheme, String)
|
||||
case hint(PresentationTheme, String, String)
|
||||
case hint(PresentationTheme, String, String, Bool)
|
||||
case hintInfo(PresentationTheme, String)
|
||||
|
||||
case emailHeader(PresentationTheme, String)
|
||||
@ -83,14 +87,12 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
|
||||
return 2
|
||||
case .passwordInfo:
|
||||
return 3
|
||||
|
||||
case .hintHeader:
|
||||
return 4
|
||||
case .hint:
|
||||
return 5
|
||||
case .hintInfo:
|
||||
return 6
|
||||
|
||||
case .emailHeader:
|
||||
return 7
|
||||
case .email:
|
||||
@ -113,32 +115,40 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable {
|
||||
case let .passwordHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .password(theme, text, value):
|
||||
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .password, spacing: 0.0, tag: CreatePasswordEntryTag.password, sectionId: self.section, textUpdated: { updatedText in
|
||||
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .password, returnKeyType: .next, spacing: 0.0, tag: CreatePasswordEntryTag.password, sectionId: self.section, textUpdated: { updatedText in
|
||||
arguments.updateFieldText(.password, updatedText)
|
||||
}, action: {
|
||||
arguments.selectNextInputItem(CreatePasswordEntryTag.password)
|
||||
})
|
||||
case let .passwordConfirmation(theme, text, value):
|
||||
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .password, spacing: 0.0, tag: CreatePasswordEntryTag.passwordConfirmation, sectionId: self.section, textUpdated: { updatedText in
|
||||
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .password, returnKeyType: .next, spacing: 0.0, tag: CreatePasswordEntryTag.passwordConfirmation, sectionId: self.section, textUpdated: { updatedText in
|
||||
arguments.updateFieldText(.passwordConfirmation, updatedText)
|
||||
}, action: {
|
||||
arguments.selectNextInputItem(CreatePasswordEntryTag.passwordConfirmation)
|
||||
})
|
||||
case let .passwordInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
||||
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.hint, sectionId: self.section, textUpdated: { updatedText in
|
||||
case let .hint(theme, text, value, last):
|
||||
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .regular(capitalization: true, autocorrection: false), returnKeyType: last ? .done : .next, spacing: 0.0, tag: CreatePasswordEntryTag.hint, sectionId: self.section, textUpdated: { updatedText in
|
||||
arguments.updateFieldText(.hint, updatedText)
|
||||
}, action: {
|
||||
if last {
|
||||
arguments.save()
|
||||
} else {
|
||||
arguments.selectNextInputItem(CreatePasswordEntryTag.hint)
|
||||
}
|
||||
})
|
||||
case let .hintInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
||||
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.email, sectionId: self.section, textUpdated: { updatedText in
|
||||
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(), text: value, placeholder: text, type: .email, returnKeyType: .done, spacing: 0.0, tag: CreatePasswordEntryTag.email, sectionId: self.section, textUpdated: { updatedText in
|
||||
arguments.updateFieldText(.email, updatedText)
|
||||
}, action: {
|
||||
arguments.save()
|
||||
})
|
||||
case let .emailInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
||||
@ -175,11 +185,13 @@ private func createPasswordControllerEntries(presentationData: PresentationData,
|
||||
entries.append(.passwordConfirmation(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordConfirmationPlaceholder, state.passwordConfirmationText))
|
||||
entries.append(.passwordInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordHelp))
|
||||
|
||||
let showEmail = currentPassword == nil
|
||||
|
||||
entries.append(.hintHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintSection))
|
||||
entries.append(.hint(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintPlaceholder, state.hintText))
|
||||
entries.append(.hint(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintPlaceholder, state.hintText, !showEmail))
|
||||
entries.append(.hintInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_HintHelp))
|
||||
|
||||
if currentPassword == nil {
|
||||
if showEmail {
|
||||
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))
|
||||
@ -212,78 +224,20 @@ func createPasswordController(account: Account, state: CreatePasswordState, comp
|
||||
let saveDisposable = MetaDisposable()
|
||||
actionsDisposable.add(saveDisposable)
|
||||
|
||||
let arguments = CreatePasswordControllerArguments(updateFieldText: { field, updatedText in
|
||||
updateState { state in
|
||||
var state = state
|
||||
switch field {
|
||||
case .password:
|
||||
state.passwordText = updatedText
|
||||
case .passwordConfirmation:
|
||||
state.passwordConfirmationText = updatedText
|
||||
case .hint:
|
||||
state.hintText = updatedText
|
||||
case .email:
|
||||
state.emailText = updatedText
|
||||
}
|
||||
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)?
|
||||
|
||||
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get())
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<CreatePasswordEntry>, CreatePasswordEntry.ItemGenerationArguments)) in
|
||||
var selectNextInputItemImpl: ((CreatePasswordEntryTag) -> Void)?
|
||||
|
||||
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 {
|
||||
switch state.state {
|
||||
case .setup:
|
||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.passwordText.isEmpty, action: {
|
||||
let saveImpl = {
|
||||
var state: CreatePasswordControllerState?
|
||||
updateState { s in
|
||||
state = s
|
||||
return s
|
||||
}
|
||||
if let state = state {
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
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 = {
|
||||
@ -349,6 +303,73 @@ func createPasswordController(account: Account, state: CreatePasswordState, comp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let arguments = CreatePasswordControllerArguments(updateFieldText: { field, updatedText in
|
||||
updateState { state in
|
||||
var state = state
|
||||
switch field {
|
||||
case .password:
|
||||
state.passwordText = updatedText
|
||||
case .passwordConfirmation:
|
||||
state.passwordConfirmationText = updatedText
|
||||
case .hint:
|
||||
state.hintText = updatedText
|
||||
case .email:
|
||||
state.emailText = updatedText
|
||||
}
|
||||
return state
|
||||
}
|
||||
}, selectNextInputItem: { tag in
|
||||
selectNextInputItemImpl?(tag)
|
||||
}, save: {
|
||||
saveImpl()
|
||||
}, 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
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get())
|
||||
|> 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 {
|
||||
switch state.state {
|
||||
case .setup:
|
||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.passwordText.isEmpty, action: {
|
||||
saveImpl()
|
||||
})
|
||||
case .pendingVerification:
|
||||
break
|
||||
@ -402,6 +423,28 @@ func createPasswordController(account: Account, state: CreatePasswordState, comp
|
||||
resultItemNode.focus()
|
||||
}
|
||||
}
|
||||
selectNextInputItemImpl = { [weak controller] currentTag in
|
||||
guard let controller = controller else {
|
||||
return
|
||||
}
|
||||
|
||||
var resultItemNode: ItemListSingleLineInputItemNode?
|
||||
var focusOnNext = false
|
||||
let _ = controller.frameForItemNode({ itemNode in
|
||||
if let itemNode = itemNode as? ItemListSingleLineInputItemNode, let tag = itemNode.tag {
|
||||
if focusOnNext && resultItemNode == nil {
|
||||
resultItemNode = itemNode
|
||||
return true
|
||||
} else if currentTag.isEqual(to: tag) {
|
||||
focusOnNext = true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if let resultItemNode = resultItemNode {
|
||||
resultItemNode.focus()
|
||||
}
|
||||
}
|
||||
controller.didAppear = { firstTime in
|
||||
if !firstTime {
|
||||
return
|
||||
|
||||
@ -390,6 +390,40 @@ public extension DeviceContactExtendedData {
|
||||
let basicData = DeviceContactBasicData(firstName: contact.givenName, lastName: contact.familyName, phoneNumbers: phoneNumbers)
|
||||
self.init(basicData: basicData, middleName: contact.middleName, prefix: contact.namePrefix, suffix: contact.nameSuffix, organization: contact.organizationName, jobTitle: contact.jobTitle, department: contact.departmentName, emailAddresses: emailAddresses, urls: urls, addresses: addresses, birthdayDate: birthdayDate, socialProfiles: socialProfiles, instantMessagingProfiles: instantMessagingProfiles)
|
||||
}
|
||||
|
||||
public var isPrimitive: Bool {
|
||||
if self.basicData.phoneNumbers.count > 1 {
|
||||
return false
|
||||
}
|
||||
if !self.organization.isEmpty {
|
||||
return false
|
||||
}
|
||||
if !self.jobTitle.isEmpty {
|
||||
return false
|
||||
}
|
||||
if !self.department.isEmpty {
|
||||
return false
|
||||
}
|
||||
if !self.emailAddresses.isEmpty {
|
||||
return false
|
||||
}
|
||||
if !self.urls.isEmpty {
|
||||
return false
|
||||
}
|
||||
if !self.addresses.isEmpty {
|
||||
return false
|
||||
}
|
||||
if self.birthdayDate != nil {
|
||||
return false
|
||||
}
|
||||
if !self.socialProfiles.isEmpty {
|
||||
return false
|
||||
}
|
||||
if !self.instantMessagingProfiles.isEmpty {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension DeviceContactExtendedData {
|
||||
|
||||
@ -288,7 +288,7 @@ final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate {
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: nodeHeight - separatorHeight), size: CGSize(width: width, height: separatorHeight)))
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: nodeHeight)))
|
||||
|
||||
if !fabs(previousContentHeight - contentHeight).isLess(than: CGFloat.ulpOfOne) {
|
||||
if !abs(previousContentHeight - contentHeight).isLess(than: CGFloat.ulpOfOne) {
|
||||
let contentOffset = CGPoint(x: 0, y: max(0, contentHeight - nodeHeight))
|
||||
if case .immediate = transition {
|
||||
self.scrollNode.view.contentOffset = contentOffset
|
||||
|
||||
@ -339,6 +339,14 @@ class FormControllerNode<InitParams, InnerState: FormControllerInnerState>: View
|
||||
let contentOffset = CGPoint(x: 0.0, y: -insets.top)
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: contentOffset.y), size: layout.size))
|
||||
}
|
||||
|
||||
if previousLayout == nil {
|
||||
self.didAppear()
|
||||
}
|
||||
}
|
||||
|
||||
func didAppear() {
|
||||
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
|
||||
@ -91,7 +91,7 @@ final class FormControllerTextInputItemNode: FormBlockItemNode<FormControllerTex
|
||||
}
|
||||
|
||||
return (FormControllerItemPreLayout(aligningInset: aligningInset), { params in
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleSize))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: titleSize))
|
||||
|
||||
let capitalizationType: UITextAutocapitalizationType
|
||||
let autocorrectionType: UITextAutocorrectionType
|
||||
|
||||
@ -165,11 +165,12 @@ class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem {
|
||||
let updatingImage: ItemListAvatarAndNameInfoItemUpdatingAvatar?
|
||||
let call: (() -> Void)?
|
||||
let action: (() -> Void)?
|
||||
let longTapAction: (() -> Void)?
|
||||
let tag: ItemListItemTag?
|
||||
|
||||
let selectable: Bool
|
||||
|
||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, mode: ItemListAvatarAndNameInfoItemMode, peer: Peer?, presence: PeerPresence?, label: String? = nil, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState, sectionId: ItemListSectionId, style: ItemListAvatarAndNameInfoItemStyle, editingNameUpdated: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, avatarTapped: @escaping () -> Void, context: ItemListAvatarAndNameInfoItemContext? = nil, updatingImage: ItemListAvatarAndNameInfoItemUpdatingAvatar? = nil, call: (() -> Void)? = nil, action: (() -> Void)? = nil, tag: ItemListItemTag? = nil) {
|
||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, mode: ItemListAvatarAndNameInfoItemMode, peer: Peer?, presence: PeerPresence?, label: String? = nil, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState, sectionId: ItemListSectionId, style: ItemListAvatarAndNameInfoItemStyle, editingNameUpdated: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, avatarTapped: @escaping () -> Void, context: ItemListAvatarAndNameInfoItemContext? = nil, updatingImage: ItemListAvatarAndNameInfoItemUpdatingAvatar? = nil, call: (() -> Void)? = nil, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, tag: ItemListItemTag? = nil) {
|
||||
self.account = account
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
@ -187,6 +188,7 @@ class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem {
|
||||
self.updatingImage = updatingImage
|
||||
self.call = call
|
||||
self.action = action
|
||||
self.longTapAction = longTapAction
|
||||
self.tag = tag
|
||||
|
||||
if case .settings = mode {
|
||||
@ -909,4 +911,12 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
|
||||
func focus() {
|
||||
self.inputFirstField?.becomeFirstResponder()
|
||||
}
|
||||
|
||||
override func longTapped() {
|
||||
self.item?.longTapAction?()
|
||||
}
|
||||
|
||||
override var canBeLongTapped: Bool {
|
||||
return self.item?.longTapAction != nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ final class ItemListRevealOptionsGestureRecognizer: UIPanGestureRecognizer {
|
||||
}
|
||||
}
|
||||
|
||||
class ItemListRevealOptionsItemNode: ListViewItemNode {
|
||||
class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerDelegate {
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
||||
|
||||
private var leftRevealNode: ItemListRevealOptionsNode?
|
||||
@ -64,6 +64,7 @@ class ItemListRevealOptionsItemNode: ListViewItemNode {
|
||||
private(set) var revealOffset: CGFloat = 0.0
|
||||
|
||||
private var recognizer: ItemListRevealOptionsGestureRecognizer?
|
||||
private var tapRecognizer: UITapGestureRecognizer?
|
||||
private var hapticFeedback: HapticFeedback?
|
||||
|
||||
private var allowAnyDirection = false
|
||||
@ -88,6 +89,11 @@ class ItemListRevealOptionsItemNode: ListViewItemNode {
|
||||
recognizer.allowAnyDirection = self.allowAnyDirection
|
||||
self.view.addGestureRecognizer(recognizer)
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.revealTapGesture(_:)))
|
||||
self.tapRecognizer = tapRecognizer
|
||||
tapRecognizer.delegate = self
|
||||
self.view.addGestureRecognizer(tapRecognizer)
|
||||
|
||||
self.view.disablesInteractiveTransitionGestureRecognizer = self.allowAnyDirection
|
||||
}
|
||||
|
||||
@ -136,6 +142,24 @@ class ItemListRevealOptionsItemNode: ListViewItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if let recognizer = self.recognizer, gestureRecognizer == self.tapRecognizer {
|
||||
return abs(self.revealOffset) > 0.0 && !recognizer.validatedGesture
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@objc func revealTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.updateRevealOffsetInternal(offset: 0.0, transition: .animated(duration: 0.3, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
@objc func revealGesture(_ recognizer: ItemListRevealOptionsGestureRecognizer) {
|
||||
guard let (size, _, _) = self.validLayout else {
|
||||
return
|
||||
|
||||
@ -186,9 +186,9 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
|
||||
}
|
||||
self.titleNode.frame = titleFrame
|
||||
|
||||
if (fabs(revealFactor) >= 0.4) {
|
||||
if (abs(revealFactor) >= 0.4) {
|
||||
animationNode.play()
|
||||
} else if fabs(revealFactor) < CGFloat.ulpOfOne {
|
||||
} else if abs(revealFactor) < CGFloat.ulpOfOne {
|
||||
animationNode.reset()
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
||||
let text: String
|
||||
let placeholder: String
|
||||
let type: ItemListSingleLineInputItemType
|
||||
let returnKeyType: UIReturnKeyType
|
||||
let spacing: CGFloat
|
||||
let clearButton: Bool
|
||||
let sectionId: ItemListSectionId
|
||||
@ -25,12 +26,13 @@ class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
||||
let processPaste: ((String) -> String)?
|
||||
let tag: ItemListItemTag?
|
||||
|
||||
init(theme: PresentationTheme, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), spacing: CGFloat = 0.0, clearButton: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, processPaste: ((String) -> String)? = nil, action: @escaping () -> Void) {
|
||||
init(theme: PresentationTheme, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, spacing: CGFloat = 0.0, clearButton: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, processPaste: ((String) -> String)? = nil, action: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.placeholder = placeholder
|
||||
self.type = type
|
||||
self.returnKeyType = returnKeyType
|
||||
self.spacing = spacing
|
||||
self.clearButton = clearButton
|
||||
self.tag = tag
|
||||
@ -244,6 +246,9 @@ class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDelegate, It
|
||||
if strongSelf.textNode.textField.autocorrectionType != autocorrectionType {
|
||||
strongSelf.textNode.textField.autocorrectionType = autocorrectionType
|
||||
}
|
||||
if strongSelf.textNode.textField.returnKeyType != item.returnKeyType {
|
||||
strongSelf.textNode.textField.returnKeyType = item.returnKeyType
|
||||
}
|
||||
|
||||
if let currentText = strongSelf.textNode.textField.text {
|
||||
if currentText != item.text {
|
||||
|
||||
@ -27,6 +27,7 @@ func legacySecureIdScanController(theme: PresentationTheme, strings: Presentatio
|
||||
legacyController?.dismiss()
|
||||
}, rootController: nil)
|
||||
|
||||
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
return legacyController
|
||||
|
||||
@ -280,7 +280,7 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode {
|
||||
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
|
||||
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width, y: minimizedTitleFrame.minY + 8.0), size: closeButtonSize))
|
||||
let rateButtonSize = CGSize(width: 24.0, height: minHeight)
|
||||
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width - 8.0 - rateButtonSize.width, y: 0.0), size: rateButtonSize))
|
||||
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width - 18.0 - rateButtonSize.width, y: 0.0), size: rateButtonSize))
|
||||
transition.updateFrame(node: self.actionPlayNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))
|
||||
transition.updateFrame(node: self.actionPauseNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))
|
||||
transition.updateFrame(node: self.actionButton, frame: CGRect(origin: CGPoint(x: 0.0, y: minimizedTitleFrame.minY - 4.0), size: CGSize(width: 40.0, height: 37.0)))
|
||||
|
||||
@ -5,8 +5,8 @@ import TelegramCore
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
public func openExternalUrl(account: Account, url: String, presentationData: PresentationData, applicationContext: TelegramApplicationContext, navigationController: NavigationController?, dismissInput: @escaping () -> Void) {
|
||||
if url.lowercased().hasPrefix("tel:") || url.lowercased().hasPrefix("calshow:") {
|
||||
public func openExternalUrl(account: Account, url: String, forceExternal: Bool = false, presentationData: PresentationData, applicationContext: TelegramApplicationContext, navigationController: NavigationController?, dismissInput: @escaping () -> Void) {
|
||||
if forceExternal || url.lowercased().hasPrefix("tel:") || url.lowercased().hasPrefix("calshow:") {
|
||||
applicationContext.applicationBindings.openUrl(url)
|
||||
return
|
||||
}
|
||||
@ -198,6 +198,7 @@ public func openExternalUrl(account: Account, url: String, presentationData: Pre
|
||||
var botId: Int32?
|
||||
var scope: String?
|
||||
var publicKey: String?
|
||||
var callbackUrl: String?
|
||||
var opaquePayload = Data()
|
||||
var opaqueNonce = Data()
|
||||
if let queryItems = components.queryItems {
|
||||
@ -211,6 +212,8 @@ public func openExternalUrl(account: Account, url: String, presentationData: Pre
|
||||
scope = value
|
||||
} else if queryItem.name == "public_key" {
|
||||
publicKey = value
|
||||
} else if queryItem.name == "callback_url" {
|
||||
callbackUrl = value
|
||||
} else if queryItem.name == "payload" {
|
||||
if let data = value.data(using: .utf8) {
|
||||
opaquePayload = data
|
||||
@ -236,7 +239,7 @@ public func openExternalUrl(account: Account, url: String, presentationData: Pre
|
||||
}
|
||||
|
||||
if valid && GlobalExperimentalSettings.enablePassport {
|
||||
if let botId = botId, let scope = scope, let publicKey = publicKey {
|
||||
if let botId = botId, let scope = scope, let publicKey = publicKey, let callbackUrl = callbackUrl {
|
||||
if scope.hasPrefix("{") && scope.hasSuffix("}") {
|
||||
opaquePayload = Data()
|
||||
if opaqueNonce.isEmpty {
|
||||
@ -245,7 +248,7 @@ public func openExternalUrl(account: Account, url: String, presentationData: Pre
|
||||
} else if opaquePayload.isEmpty {
|
||||
return
|
||||
}
|
||||
let controller = SecureIdAuthController(account: account, mode: .form(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: botId), scope: scope, publicKey: publicKey, opaquePayload: opaquePayload, opaqueNonce: opaqueNonce))
|
||||
let controller = SecureIdAuthController(account: account, mode: .form(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: botId), scope: scope, publicKey: publicKey, callbackUrl: callbackUrl, opaquePayload: opaquePayload, opaqueNonce: opaqueNonce))
|
||||
|
||||
if let navigationController = navigationController {
|
||||
navigationController.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
|
||||
|
||||
@ -5,6 +5,30 @@ import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
public enum SecureIdRequestResult: String {
|
||||
case success = "success"
|
||||
case cancel = "cancel"
|
||||
case error = "error"
|
||||
}
|
||||
|
||||
public func secureIdCallbackUrl(with baseUrl: String, peerId: PeerId, result: SecureIdRequestResult, parameters: [String : String]) -> String {
|
||||
var query = (parameters.compactMap({ (key, value) -> String in
|
||||
return "\(key)=\(value)"
|
||||
}) as Array).joined(separator: "&")
|
||||
|
||||
if !query.isEmpty {
|
||||
query = "?" + query
|
||||
}
|
||||
|
||||
let url: String
|
||||
if baseUrl.hasPrefix("tgbot") {
|
||||
url = "tgbot\(peerId.id)://passport/" + result.rawValue + query
|
||||
} else {
|
||||
url = baseUrl + (baseUrl.range(of: "?") != nil ? "&" : "?") + "tg_passport=" + result.rawValue + query
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
final class SecureIdAuthControllerInteraction {
|
||||
let updateState: ((SecureIdAuthControllerState) -> SecureIdAuthControllerState) -> Void
|
||||
let present: (ViewController, Any?) -> Void
|
||||
@ -30,7 +54,7 @@ final class SecureIdAuthControllerInteraction {
|
||||
}
|
||||
|
||||
enum SecureIdAuthControllerMode {
|
||||
case form(peerId: PeerId, scope: String, publicKey: String, opaquePayload: Data, opaqueNonce: Data)
|
||||
case form(peerId: PeerId, scope: String, publicKey: String, callbackUrl: String, opaquePayload: Data, opaqueNonce: Data)
|
||||
case list
|
||||
}
|
||||
|
||||
@ -63,9 +87,9 @@ final class SecureIdAuthController: ViewController {
|
||||
|
||||
switch mode {
|
||||
case .form:
|
||||
self.state = .form(SecureIdAuthControllerFormState(encryptedFormData: nil, formData: nil, verificationState: nil, removingValues: false))
|
||||
self.state = .form(SecureIdAuthControllerFormState(twoStepEmail: nil, encryptedFormData: nil, formData: nil, verificationState: nil, removingValues: false))
|
||||
case .list:
|
||||
self.state = .list(SecureIdAuthControllerListState(accountPeer: nil, verificationState: nil, encryptedValues: nil, primaryLanguageByCountry: [:], values: nil, removingValues: false))
|
||||
self.state = .list(SecureIdAuthControllerListState(accountPeer: nil, twoStepEmail: nil, verificationState: nil, encryptedValues: nil, primaryLanguageByCountry: [:], values: nil, removingValues: false))
|
||||
}
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
@ -90,6 +114,7 @@ final class SecureIdAuthController: ViewController {
|
||||
strongSelf.updateState(animated: true, { state in
|
||||
var state = state
|
||||
state.verificationState = .verified(context.context)
|
||||
state.twoStepEmail = context.settings.email
|
||||
switch state {
|
||||
case var .form(form):
|
||||
form.formData = form.encryptedFormData.flatMap({ decryptedSecureIdForm(context: context.context, form: $0.form) })
|
||||
@ -126,8 +151,44 @@ final class SecureIdAuthController: ViewController {
|
||||
}
|
||||
}))
|
||||
|
||||
let handleError: (Any, String?, PeerId?) -> Void = { [weak self] error, callbackUrl, peerId in
|
||||
if let strongSelf = self {
|
||||
var passError: String?
|
||||
var appUpdateRequired = false
|
||||
switch error {
|
||||
case let error as RequestSecureIdFormError:
|
||||
if case let .serverError(error) = error, ["BOT_INVALID", "PUBLIC_KEY_REQUIRED", "PUBLIC_KEY_INVALID", "SCOPE_EMPTY", "PAYLOAD_EMPTY", "NONCE_EMPTY"].contains(error) {
|
||||
passError = error
|
||||
} else if case .versionOutdated = error {
|
||||
appUpdateRequired = true
|
||||
}
|
||||
break
|
||||
case let error as GetAllSecureIdValuesError:
|
||||
if case .versionOutdated = error {
|
||||
appUpdateRequired = true
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if appUpdateRequired {
|
||||
let errorText = strongSelf.presentationData.strings.Passport_UpdateRequiredError
|
||||
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Application_Update, action: {})]), in: .window(.root))
|
||||
} else if let callbackUrl = callbackUrl, let peerId = peerId {
|
||||
let errorText = strongSelf.presentationData.strings.Login_UnknownError
|
||||
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
||||
if let error = passError {
|
||||
strongSelf.openUrl(secureIdCallbackUrl(with: callbackUrl, peerId: peerId, result: .error, parameters: ["error": error]))
|
||||
}
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
switch self.mode {
|
||||
case let .form(peerId, scope, publicKey, _, _):
|
||||
case let .form(peerId, scope, publicKey, callbackUrl, _, _):
|
||||
self.formDisposable = (combineLatest(requestSecureIdForm(postbox: account.postbox, network: account.network, peerId: peerId, scope: scope, publicKey: publicKey), secureIdConfiguration(postbox: account.postbox, network: account.network) |> introduceError(RequestSecureIdFormError.self))
|
||||
|> mapToSignal { form, configuration -> Signal<SecureIdEncryptedFormData, RequestSecureIdFormError> in
|
||||
return account.postbox.transaction { transaction -> Signal<SecureIdEncryptedFormData, RequestSecureIdFormError> in
|
||||
@ -155,12 +216,8 @@ final class SecureIdAuthController: ViewController {
|
||||
return state
|
||||
}
|
||||
}
|
||||
}, error: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
let errorText = strongSelf.presentationData.strings.Login_UnknownError
|
||||
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))
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
}, error: { error in
|
||||
handleError(error, callbackUrl, peerId)
|
||||
})
|
||||
case .list:
|
||||
self.formDisposable = (combineLatest(getAllSecureIdValues(network: self.account.network), secureIdConfiguration(postbox: account.postbox, network: account.network) |> introduceError(GetAllSecureIdValuesError.self), account.postbox.transaction { transaction -> Signal<Peer, GetAllSecureIdValuesError> in
|
||||
@ -190,6 +247,8 @@ final class SecureIdAuthController: ViewController {
|
||||
return state
|
||||
}
|
||||
}
|
||||
}, error: { error in
|
||||
handleError(error, nil, nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -323,8 +382,18 @@ final class SecureIdAuthController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private func openUrl(_ url: String) {
|
||||
openExternalUrl(account: self.account, url: url, forceExternal: true, presentationData: self.presentationData, applicationContext: self.account.telegramApplicationContext, navigationController: nil, dismissInput: { [weak self] in
|
||||
self?.view.endEditing(true)
|
||||
})
|
||||
}
|
||||
|
||||
@objc private func cancelPressed() {
|
||||
self.dismiss()
|
||||
|
||||
if case let .form(reqForm) = self.mode {
|
||||
self.openUrl(secureIdCallbackUrl(with: reqForm.callbackUrl, peerId: reqForm.peerId, result: .cancel, parameters: [:]))
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func checkPassword(password: String, inBackground: Bool, completion: @escaping () -> Void) {
|
||||
@ -349,6 +418,7 @@ final class SecureIdAuthController: ViewController {
|
||||
strongSelf.updateState(animated: !inBackground, { state in
|
||||
var state = state
|
||||
state.verificationState = .verified(context.context)
|
||||
state.twoStepEmail = context.settings.email
|
||||
switch state {
|
||||
case var .form(form):
|
||||
form.formData = form.encryptedFormData.flatMap({ decryptedSecureIdForm(context: context.context, form: $0.form) })
|
||||
@ -492,6 +562,7 @@ final class SecureIdAuthController: ViewController {
|
||||
let _ = (grantSecureIdAccess(network: self.account.network, peerId: encryptedFormData.servicePeer.id, publicKey: reqForm.publicKey, scope: reqForm.scope, opaquePayload: reqForm.opaquePayload, opaqueNonce: reqForm.opaqueNonce, values: values, requestedFields: formData.requestedFields)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
self?.dismiss()
|
||||
self?.openUrl(secureIdCallbackUrl(with: reqForm.callbackUrl, peerId: reqForm.peerId, result: .success, parameters: [:]))
|
||||
})
|
||||
}
|
||||
case .list:
|
||||
|
||||
@ -421,7 +421,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
||||
return !touchedKeys.contains(value.value.key)
|
||||
}
|
||||
values.append(contentsOf: updatedValues)
|
||||
return .form(SecureIdAuthControllerFormState(encryptedFormData: form.encryptedFormData, formData: SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: values), verificationState: form.verificationState, removingValues: form.removingValues))
|
||||
return .form(SecureIdAuthControllerFormState(twoStepEmail: form.twoStepEmail, encryptedFormData: form.encryptedFormData, formData: SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: values), verificationState: form.verificationState, removingValues: form.removingValues))
|
||||
}
|
||||
}
|
||||
|
||||
@ -594,6 +594,9 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
currentValue = findValue(formData.values, key: .phone)?.1
|
||||
case .email:
|
||||
if let email = form.twoStepEmail {
|
||||
immediatelyAvailableValue = .email(SecureIdEmailValue(email: email))
|
||||
}
|
||||
currentValue = findValue(formData.values, key: .email)?.1
|
||||
}
|
||||
let openForm: () -> Void = { [weak self] in
|
||||
@ -618,7 +621,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
||||
if let valueWithContext = valueWithContext {
|
||||
values.append(valueWithContext)
|
||||
}
|
||||
return .form(SecureIdAuthControllerFormState(encryptedFormData: form.encryptedFormData, formData: SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: values), verificationState: form.verificationState, removingValues: form.removingValues))
|
||||
return .form(SecureIdAuthControllerFormState(twoStepEmail: form.twoStepEmail, encryptedFormData: form.encryptedFormData, formData: SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: values), verificationState: form.verificationState, removingValues: form.removingValues))
|
||||
}
|
||||
return state
|
||||
}
|
||||
@ -742,6 +745,58 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
}
|
||||
|
||||
let deleteField: (SecureIdValueKey) -> Void = { [weak self] field in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let controller = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
|
||||
let dismissAction: () -> Void = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
let text: String
|
||||
switch field {
|
||||
case .phone:
|
||||
text = strongSelf.presentationData.strings.Passport_Phone_Delete
|
||||
default:
|
||||
text = strongSelf.presentationData.strings.Passport_Email_Delete
|
||||
}
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: text, color: .destructive, action: { [weak self] in
|
||||
dismissAction()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.interaction.updateState { state in
|
||||
if case var .list(list) = state {
|
||||
list.removingValues = true
|
||||
return .list(list)
|
||||
}
|
||||
return state
|
||||
}
|
||||
strongSelf.deleteValueDisposable.set((deleteSecureIdValues(network: strongSelf.account.network, keys: Set([field]))
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.interaction.updateState { state in
|
||||
if case var .list(list) = state , let values = list.values {
|
||||
list.removingValues = false
|
||||
list.values = values.filter {
|
||||
$0.value.key != field
|
||||
}
|
||||
return .list(list)
|
||||
}
|
||||
return state
|
||||
}
|
||||
}))
|
||||
})]),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
strongSelf.view.endEditing(true)
|
||||
strongSelf.interaction.present(controller, nil)
|
||||
}
|
||||
|
||||
switch field {
|
||||
case .identity, .address:
|
||||
let keys: [(SecureIdValueKey, String, String)]
|
||||
@ -782,6 +837,9 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
||||
self.view.endEditing(true)
|
||||
self.interaction.present(controller, nil)
|
||||
case .phone:
|
||||
if findValue(values, key: .phone) != nil {
|
||||
deleteField(.phone)
|
||||
} else {
|
||||
var immediatelyAvailableValue: SecureIdValue?
|
||||
if let peer = list.accountPeer as? TelegramUser, let phone = peer.phone, !phone.isEmpty {
|
||||
immediatelyAvailableValue = .phone(SecureIdPhoneValue(phone: phone))
|
||||
@ -789,12 +847,21 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
||||
self.interaction.present(SecureIdPlaintextFormController(account: self.account, context: context, type: .phone, immediatelyAvailableValue: immediatelyAvailableValue, updatedValue: { value in
|
||||
updatedValues(.phone)(value.flatMap({ [$0] }) ?? [])
|
||||
}), nil)
|
||||
}
|
||||
case .email:
|
||||
self.interaction.present(SecureIdPlaintextFormController(account: self.account, context: context, type: .email, immediatelyAvailableValue: nil, updatedValue: { value in
|
||||
if findValue(values, key: .email) != nil {
|
||||
deleteField(.email)
|
||||
} else {
|
||||
var immediatelyAvailableValue: SecureIdValue?
|
||||
if let email = list.twoStepEmail {
|
||||
immediatelyAvailableValue = .email(SecureIdEmailValue(email: email))
|
||||
}
|
||||
self.interaction.present(SecureIdPlaintextFormController(account: self.account, context: context, type: .email, immediatelyAvailableValue: immediatelyAvailableValue, updatedValue: { value in
|
||||
updatedValues(.email)(value.flatMap({ [$0] }) ?? [])
|
||||
}), nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteAllValues() {
|
||||
let controller = ActionSheetController(presentationTheme: self.presentationData.theme)
|
||||
|
||||
@ -22,20 +22,24 @@ enum SecureIdAuthControllerVerificationState: Equatable {
|
||||
}
|
||||
|
||||
struct SecureIdAuthControllerFormState: Equatable {
|
||||
var twoStepEmail: String?
|
||||
var encryptedFormData: SecureIdEncryptedFormData?
|
||||
var formData: SecureIdForm?
|
||||
var verificationState: SecureIdAuthControllerVerificationState?
|
||||
var removingValues: Bool = false
|
||||
|
||||
static func ==(lhs: SecureIdAuthControllerFormState, rhs: SecureIdAuthControllerFormState) -> Bool {
|
||||
if (lhs.formData != nil) != (rhs.formData != nil) {
|
||||
if let lhsTwoStepEmail = lhs.twoStepEmail, let rhsTwoStepEmail = rhs.twoStepEmail, lhsTwoStepEmail != rhsTwoStepEmail {
|
||||
return false
|
||||
} else if (lhs.twoStepEmail != nil) != (rhs.twoStepEmail != nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (lhs.encryptedFormData != nil) != (rhs.encryptedFormData != nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (lhs.formData != nil) != (rhs.formData != nil) {
|
||||
return false
|
||||
}
|
||||
if let lhsFormData = lhs.formData, let rhsFormData = rhs.formData {
|
||||
if lhsFormData != rhsFormData {
|
||||
return false
|
||||
@ -43,21 +47,19 @@ struct SecureIdAuthControllerFormState: Equatable {
|
||||
} else if (lhs.formData != nil) != (rhs.formData != nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.verificationState != rhs.verificationState {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.removingValues != rhs.removingValues {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
struct SecureIdAuthControllerListState: Equatable {
|
||||
var accountPeer: Peer?
|
||||
var twoStepEmail: String?
|
||||
var verificationState: SecureIdAuthControllerVerificationState?
|
||||
var encryptedValues: EncryptedAllSecureIdValues?
|
||||
var primaryLanguageByCountry: [String: String]?
|
||||
@ -65,6 +67,14 @@ struct SecureIdAuthControllerListState: Equatable {
|
||||
var removingValues: Bool = false
|
||||
|
||||
static func ==(lhs: SecureIdAuthControllerListState, rhs: SecureIdAuthControllerListState) -> Bool {
|
||||
if !arePeersEqual(lhs.accountPeer, rhs.accountPeer) {
|
||||
return false
|
||||
}
|
||||
if let lhsTwoStepEmail = lhs.twoStepEmail, let rhsTwoStepEmail = rhs.twoStepEmail, lhsTwoStepEmail != rhsTwoStepEmail {
|
||||
return false
|
||||
} else if (lhs.twoStepEmail != nil) != (rhs.twoStepEmail != nil) {
|
||||
return false
|
||||
}
|
||||
if lhs.verificationState != rhs.verificationState {
|
||||
return false
|
||||
}
|
||||
@ -88,6 +98,26 @@ enum SecureIdAuthControllerState: Equatable {
|
||||
case form(SecureIdAuthControllerFormState)
|
||||
case list(SecureIdAuthControllerListState)
|
||||
|
||||
var twoStepEmail: String? {
|
||||
get {
|
||||
switch self {
|
||||
case let .form(form):
|
||||
return form.twoStepEmail
|
||||
case let .list(list):
|
||||
return list.twoStepEmail
|
||||
}
|
||||
} set(value) {
|
||||
switch self {
|
||||
case var .form(form):
|
||||
form.twoStepEmail = value
|
||||
self = .form(form)
|
||||
case var .list(list):
|
||||
list.twoStepEmail = value
|
||||
self = .list(list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var verificationState: SecureIdAuthControllerVerificationState? {
|
||||
get {
|
||||
switch self {
|
||||
|
||||
@ -532,12 +532,18 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
|
||||
placeholder = strings.Passport_FieldIdentityDetailsHelp
|
||||
}
|
||||
|
||||
if personalDetails != nil {
|
||||
if let personalDetails = personalDetails {
|
||||
if let value = findValue(values, key: .personalDetails), case let .personalDetails(personalDetailsValue) = value.1.value {
|
||||
if !text.isEmpty {
|
||||
text.append(", ")
|
||||
}
|
||||
text.append(fieldsText(personalDetailsValue.latinName.firstName, personalDetailsValue.latinName.lastName, countryName(code: personalDetailsValue.countryCode, strings: strings)))
|
||||
let fullName: String
|
||||
if let nativeName = personalDetailsValue.nativeName, !nativeName.firstName.isEmpty, personalDetails.nativeNames {
|
||||
fullName = nativeName.firstName + " " + nativeName.lastName
|
||||
} else {
|
||||
fullName = personalDetailsValue.latinName.firstName + " " + personalDetailsValue.latinName.lastName
|
||||
}
|
||||
text.append(fieldsText(fullName, countryName(code: personalDetailsValue.countryCode, strings: strings)))
|
||||
}
|
||||
}
|
||||
case let .address(addressDetails, document, _):
|
||||
@ -567,7 +573,7 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
|
||||
if !text.isEmpty {
|
||||
text.append(", ")
|
||||
}
|
||||
text.append(fieldsText(addressValue.postcode, addressValue.street1, addressValue.street2, addressValue.city))
|
||||
text.append(fieldsText(addressValue.street1, addressValue.street2, addressValue.city, addressValue.state, addressValue.postcode, countryName(code: addressValue.countryCode, strings: strings)))
|
||||
}
|
||||
}
|
||||
case .phone:
|
||||
@ -596,6 +602,7 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
|
||||
}
|
||||
|
||||
private struct ValueAdditionalData {
|
||||
var nativeNames: Bool = false
|
||||
var selfie: Bool = false
|
||||
var translation: Bool = false
|
||||
}
|
||||
@ -603,6 +610,8 @@ private struct ValueAdditionalData {
|
||||
private func extractValueAdditionalData(_ value: SecureIdValue) -> ValueAdditionalData {
|
||||
var data = ValueAdditionalData()
|
||||
switch value {
|
||||
case let .personalDetails(value):
|
||||
data.nativeNames = value.nativeName != nil
|
||||
case let .passport(value):
|
||||
data.selfie = value.selfieDocument != nil
|
||||
data.translation = !value.translations.isEmpty
|
||||
@ -724,7 +733,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
|
||||
|
||||
func updateValues(_ values: [SecureIdValueWithContext]) {
|
||||
var (title, text) = fieldTitleAndText(field: self.field, strings: self.strings, values: values)
|
||||
var textColor = self.theme.list.itemSecondaryTextColor
|
||||
let textColor = self.theme.list.itemSecondaryTextColor
|
||||
/*switch self.field {
|
||||
case .identity:
|
||||
if let error = errors[.personalDetails]?.first {
|
||||
@ -734,14 +743,18 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
|
||||
default:
|
||||
break
|
||||
}*/
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: titleFont, textColor: self.theme.list.itemPrimaryTextColor)
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: textColor)
|
||||
|
||||
var filled = true
|
||||
switch self.field {
|
||||
case let .identity(personalDetails, document, selfie, translation):
|
||||
if personalDetails != nil {
|
||||
if findValue(values, key: .personalDetails) == nil {
|
||||
if let personalDetails = personalDetails {
|
||||
if let value = findValue(values, key: .personalDetails)?.1 {
|
||||
let data = extractValueAdditionalData(value.value)
|
||||
if personalDetails.nativeNames && !data.nativeNames {
|
||||
filled = false
|
||||
text = strings.Passport_FieldIdentityDetailsHelp
|
||||
}
|
||||
} else {
|
||||
filled = false
|
||||
}
|
||||
}
|
||||
@ -752,9 +765,11 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
|
||||
let data = extractValueAdditionalData(value.value)
|
||||
if selfie && !data.selfie {
|
||||
filled = false
|
||||
text = strings.Passport_FieldIdentitySelfieHelp
|
||||
}
|
||||
if translation && !data.translation {
|
||||
filled = false
|
||||
text = strings.Passport_FieldIdentityTranslationHelp
|
||||
}
|
||||
} else {
|
||||
filled = false
|
||||
@ -794,6 +809,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
|
||||
let data = extractValueAdditionalData(value.value)
|
||||
if translation && !data.translation {
|
||||
filled = false
|
||||
text = strings.Passport_FieldAddressTranslationHelp
|
||||
}
|
||||
} else {
|
||||
filled = false
|
||||
@ -827,6 +843,9 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: titleFont, textColor: self.theme.list.itemPrimaryTextColor)
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: textColor)
|
||||
|
||||
self.checkNode.isHidden = !filled
|
||||
self.disclosureNode.isHidden = filled
|
||||
|
||||
|
||||
@ -45,9 +45,9 @@ private func fieldTitleAndText(field: SecureIdAuthListContentField, strings: Pre
|
||||
let keyList: [(SecureIdValueKey, String)] = [
|
||||
(.personalDetails, strings.Passport_Identity_TypePersonalDetails),
|
||||
(.passport, strings.Passport_Identity_TypePassport),
|
||||
(.internalPassport, strings.Passport_Identity_TypeInternalPassport),
|
||||
(.idCard, strings.Passport_Identity_TypeIdentityCard),
|
||||
(.driversLicense, strings.Passport_Identity_TypeDriversLicense),
|
||||
(.idCard, strings.Passport_Identity_TypeIdentityCard)
|
||||
(.internalPassport, strings.Passport_Identity_TypeInternalPassport)
|
||||
]
|
||||
|
||||
var fields: [String] = []
|
||||
@ -66,11 +66,11 @@ private func fieldTitleAndText(field: SecureIdAuthListContentField, strings: Pre
|
||||
|
||||
let keyList: [(SecureIdValueKey, String)] = [
|
||||
(.address, strings.Passport_Address_TypeResidentialAddress),
|
||||
(.passportRegistration, strings.Passport_Address_TypePassportRegistration),
|
||||
(.temporaryRegistration, strings.Passport_Address_TypeTemporaryRegistration),
|
||||
(.utilityBill, strings.Passport_Address_TypeUtilityBill),
|
||||
(.bankStatement, strings.Passport_Address_TypeBankStatement),
|
||||
(.rentalAgreement, strings.Passport_Address_TypeRentalAgreement)
|
||||
(.rentalAgreement, strings.Passport_Address_TypeRentalAgreement),
|
||||
(.passportRegistration, strings.Passport_Address_TypePassportRegistration),
|
||||
(.temporaryRegistration, strings.Passport_Address_TypeTemporaryRegistration)
|
||||
]
|
||||
|
||||
var fields: [String] = []
|
||||
@ -161,7 +161,7 @@ final class SecureIdAuthListFieldNode: ASDisplayNode {
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.isLayerBacked = true
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
self.textNode.maximumNumberOfLines = 4
|
||||
|
||||
self.disclosureNode = ASImageNode()
|
||||
self.disclosureNode.isLayerBacked = true
|
||||
@ -215,14 +215,13 @@ final class SecureIdAuthListFieldNode: ASDisplayNode {
|
||||
self.validLayout = (width, hasPrevious, hasNext)
|
||||
let leftInset: CGFloat = 16.0
|
||||
let rightInset: CGFloat = 16.0
|
||||
let height: CGFloat = 64.0
|
||||
|
||||
let rightTextInset = rightInset + 24.0
|
||||
|
||||
let titleTextSpacing: CGFloat = 5.0
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: width - leftInset - rightTextInset, height: 100.0))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightTextInset, height: 100.0))
|
||||
let height = max(64.0, 11.0 + titleSize.height + titleTextSpacing + textSize.height + 11.0)
|
||||
|
||||
let textOrigin = floor((height - titleSize.height - titleTextSpacing - textSize.height) / 2.0)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: textOrigin), size: titleSize)
|
||||
|
||||
@ -331,6 +331,56 @@ private enum SecureIdDocumentFormDocumentState {
|
||||
}
|
||||
}
|
||||
|
||||
extension SecureIdDocumentFormDocumentState {
|
||||
mutating func updateWithRecognizedData(_ data: SecureIdRecognizedDocumentData) {
|
||||
if case var .identity(state) = self {
|
||||
if var details = state.details {
|
||||
if details.firstName.isEmpty {
|
||||
details.firstName = data.firstName ?? ""
|
||||
}
|
||||
if details.lastName.isEmpty {
|
||||
details.lastName = data.lastName ?? ""
|
||||
}
|
||||
if details.birthdate == nil, let birthdate = data.birthDate {
|
||||
details.birthdate = SecureIdDate(timestamp: Int32(birthdate.timeIntervalSince1970))
|
||||
}
|
||||
if details.gender == nil, let gender = data.gender {
|
||||
if gender == "M" {
|
||||
details.gender = .male
|
||||
} else {
|
||||
details.gender = .female
|
||||
}
|
||||
}
|
||||
if details.countryCode.isEmpty {
|
||||
details.countryCode = data.issuingCountry ?? ""
|
||||
}
|
||||
state.details = details
|
||||
}
|
||||
|
||||
if var document = state.document {
|
||||
switch document.type {
|
||||
case .passport:
|
||||
break
|
||||
case .internalPassport:
|
||||
break
|
||||
case .driversLicense:
|
||||
break
|
||||
case .idCard:
|
||||
break
|
||||
}
|
||||
|
||||
if document.identifier.isEmpty {
|
||||
document.identifier = data.documentNumber ?? ""
|
||||
}
|
||||
if document.expiryDate == nil, let expiryDate = data.expiryDate {
|
||||
document.expiryDate = SecureIdDate(timestamp: Int32(expiryDate.timeIntervalSince1970))
|
||||
}
|
||||
state.document = document
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum SecureIdDocumentFormActionState {
|
||||
case none
|
||||
case saving
|
||||
@ -670,14 +720,35 @@ struct SecureIdDocumentFormState: FormControllerInnerState {
|
||||
} else {
|
||||
result.append(.spacer)
|
||||
}
|
||||
result.append(.entry(SecureIdDocumentFormEntry.deleteDocument))
|
||||
result.append(.entry(SecureIdDocumentFormEntry.deleteDocument(.identity, identity.document != nil)))
|
||||
}
|
||||
|
||||
return result
|
||||
case let .address(address):
|
||||
var result: [FormControllerItemEntry<SecureIdDocumentFormEntry>] = []
|
||||
var errorIndex = 0
|
||||
|
||||
if let details = address.details {
|
||||
result.append(.entry(SecureIdDocumentFormEntry.infoHeader(.address)))
|
||||
|
||||
let previousValue: SecureIdValueWithContext? = self.previousValues[.address]
|
||||
let valueErrorKey: SecureIdValueContentErrorKey = .value(.address)
|
||||
if let previousValue = previousValue {
|
||||
maybeAddError(key: valueErrorKey, value: previousValue, entries: &result, errorIndex: &errorIndex)
|
||||
}
|
||||
result.append(.entry(SecureIdDocumentFormEntry.street1(details.street1, self.previousValues[.address]?.errors[.field(.address(.streetLine1))])))
|
||||
result.append(.entry(SecureIdDocumentFormEntry.street2(details.street2, self.previousValues[.address]?.errors[.field(.address(.streetLine2))])))
|
||||
result.append(.entry(SecureIdDocumentFormEntry.city(details.city, self.previousValues[.address]?.errors[.field(.address(.city))])))
|
||||
result.append(.entry(SecureIdDocumentFormEntry.state(details.state, self.previousValues[.address]?.errors[.field(.address(.state))])))
|
||||
result.append(.entry(SecureIdDocumentFormEntry.countryCode(.address, details.countryCode, self.previousValues[.address]?.errors[.field(.address(.countryCode))])))
|
||||
result.append(.entry(SecureIdDocumentFormEntry.postcode(details.postcode, self.previousValues[.address]?.errors[.field(.address(.postCode))])))
|
||||
}
|
||||
|
||||
if let document = address.document {
|
||||
if let last = result.last, case .spacer = last {
|
||||
} else {
|
||||
result.append(.spacer)
|
||||
}
|
||||
result.append(.entry(SecureIdDocumentFormEntry.scansHeader))
|
||||
|
||||
let filesType: SecureIdValueKey
|
||||
@ -755,25 +826,12 @@ struct SecureIdDocumentFormState: FormControllerInnerState {
|
||||
result.append(.spacer)
|
||||
}
|
||||
|
||||
if let details = address.details {
|
||||
|
||||
result.append(.entry(SecureIdDocumentFormEntry.infoHeader(.address)))
|
||||
|
||||
let previousValue: SecureIdValueWithContext? = self.previousValues[.address]
|
||||
let valueErrorKey: SecureIdValueContentErrorKey = .value(.address)
|
||||
if let previousValue = previousValue {
|
||||
maybeAddError(key: valueErrorKey, value: previousValue, entries: &result, errorIndex: &errorIndex)
|
||||
}
|
||||
result.append(.entry(SecureIdDocumentFormEntry.street1(details.street1, self.previousValues[.address]?.errors[.field(.address(.streetLine1))])))
|
||||
result.append(.entry(SecureIdDocumentFormEntry.street2(details.street2, self.previousValues[.address]?.errors[.field(.address(.streetLine2))])))
|
||||
result.append(.entry(SecureIdDocumentFormEntry.city(details.city, self.previousValues[.address]?.errors[.field(.address(.city))])))
|
||||
result.append(.entry(SecureIdDocumentFormEntry.state(details.state, self.previousValues[.address]?.errors[.field(.address(.state))])))
|
||||
result.append(.entry(SecureIdDocumentFormEntry.countryCode(.address, details.countryCode, self.previousValues[.address]?.errors[.field(.address(.countryCode))])))
|
||||
result.append(.entry(SecureIdDocumentFormEntry.postcode(details.postcode, self.previousValues[.address]?.errors[.field(.address(.postCode))])))
|
||||
}
|
||||
|
||||
if self.translationsRequired, let document = address.document {
|
||||
if let last = result.last, case .spacer = last {
|
||||
} else {
|
||||
result.append(.spacer)
|
||||
}
|
||||
|
||||
result.append(.entry(SecureIdDocumentFormEntry.translationsHeader))
|
||||
|
||||
let filesType: SecureIdValueKey
|
||||
@ -845,7 +903,7 @@ struct SecureIdDocumentFormState: FormControllerInnerState {
|
||||
} else {
|
||||
result.append(.spacer)
|
||||
}
|
||||
result.append(.entry(SecureIdDocumentFormEntry.deleteDocument))
|
||||
result.append(.entry(SecureIdDocumentFormEntry.deleteDocument(.address, address.document != nil)))
|
||||
}
|
||||
|
||||
return result
|
||||
@ -1318,7 +1376,7 @@ enum SecureIdDocumentFormEntry: FormControllerEntry {
|
||||
case residenceCountryCode(String, String?)
|
||||
case birthdate(SecureIdDate?, String?)
|
||||
case expiryDate(SecureIdDate?, String?)
|
||||
case deleteDocument
|
||||
case deleteDocument(SecureIdDocumentFormEntryCategory, Bool)
|
||||
case requestedDocumentsHeader
|
||||
case selfie(Int, SecureIdVerificationDocument?, String?)
|
||||
case frontSide(Int, SecureIdRequestedIdentityDocument?, SecureIdVerificationDocument?, String?)
|
||||
@ -1543,8 +1601,8 @@ enum SecureIdDocumentFormEntry: FormControllerEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .deleteDocument:
|
||||
if case .deleteDocument = to {
|
||||
case let .deleteDocument(lhsCategory, lhsHasDocument):
|
||||
if case let .deleteDocument(rhsCategory, rhsHasDocument) = to, lhsCategory == rhsCategory, lhsHasDocument == rhsHasDocument {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -1776,8 +1834,17 @@ enum SecureIdDocumentFormEntry: FormControllerEntry {
|
||||
return FormControllerDetailActionItem(title: strings.Passport_Identity_ExpiryDate, text: value.flatMap({ stringForDate(timestamp: $0.timestamp, strings: strings) }) ?? strings.Passport_Identity_ExpiryDateNone, placeholder: strings.Passport_Identity_ExpiryDatePlaceholder, error: error, activated: {
|
||||
params.activateSelection(.date(value?.timestamp, .expiry))
|
||||
})
|
||||
case .deleteDocument:
|
||||
return FormControllerActionItem(type: .destructive, title: strings.Passport_DeleteDocument, activated: {
|
||||
case let .deleteDocument(category, hasDocument):
|
||||
var title = strings.Passport_DeleteDocument
|
||||
if !hasDocument {
|
||||
switch category {
|
||||
case .identity:
|
||||
title = strings.Passport_DeletePersonalDetails
|
||||
case .address:
|
||||
title = strings.Passport_DeleteAddress
|
||||
}
|
||||
}
|
||||
return FormControllerActionItem(type: .destructive, title: title, activated: {
|
||||
params.deleteValue()
|
||||
})
|
||||
case let .street1(value, error):
|
||||
@ -2196,7 +2263,10 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode<SecureIdDocum
|
||||
}, scanPassport: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let controller = legacySecureIdScanController(theme: theme, strings: strings, finished: { recognizedData in
|
||||
|
||||
if let strongSelf = self, let recognizedData = recognizedData, var innerState = strongSelf.innerState {
|
||||
innerState.documentState.updateWithRecognizedData(recognizedData)
|
||||
strongSelf.updateInnerState(transition: .immediate, with: innerState)
|
||||
}
|
||||
})
|
||||
strongSelf.present(controller, nil)
|
||||
}
|
||||
@ -2393,55 +2463,7 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode<SecureIdDocum
|
||||
}
|
||||
}
|
||||
if let recognizedData = recognizedData {
|
||||
switch innerState.documentState {
|
||||
case var .identity(identity):
|
||||
if var document = identity.document {
|
||||
switch document.type {
|
||||
case .passport:
|
||||
break
|
||||
case .internalPassport:
|
||||
break
|
||||
case .driversLicense:
|
||||
break
|
||||
case .idCard:
|
||||
break
|
||||
}
|
||||
|
||||
if var details = identity.details {
|
||||
if details.firstName.isEmpty {
|
||||
details.firstName = recognizedData.firstName ?? ""
|
||||
}
|
||||
if details.lastName.isEmpty {
|
||||
details.lastName = recognizedData.lastName ?? ""
|
||||
}
|
||||
if details.birthdate == nil, let birthdate = recognizedData.birthDate {
|
||||
details.birthdate = SecureIdDate(timestamp: Int32(birthdate.timeIntervalSince1970))
|
||||
}
|
||||
if details.gender == nil, let gender = recognizedData.gender {
|
||||
if gender == "M" {
|
||||
details.gender = .male
|
||||
} else {
|
||||
details.gender = .female
|
||||
}
|
||||
}
|
||||
if details.countryCode.isEmpty {
|
||||
|
||||
details.countryCode = recognizedData.issuingCountry ?? ""
|
||||
}
|
||||
identity.details = details
|
||||
}
|
||||
if document.identifier.isEmpty {
|
||||
document.identifier = recognizedData.documentNumber ?? ""
|
||||
}
|
||||
if document.expiryDate == nil, let expiryDate = recognizedData.expiryDate {
|
||||
document.expiryDate = SecureIdDate(timestamp: Int32(expiryDate.timeIntervalSince1970))
|
||||
}
|
||||
identity.document = document
|
||||
innerState.documentState = .identity(identity)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
innerState.documentState.updateWithRecognizedData(recognizedData)
|
||||
}
|
||||
self.updateInnerState(transition: .immediate, with: innerState)
|
||||
}
|
||||
@ -2656,7 +2678,7 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode<SecureIdDocum
|
||||
strongSelf.presentAssetPicker(target, replaceDocumentId: document.id)
|
||||
}
|
||||
}),
|
||||
ActionSheetButtonItem(title: strings.Common_Delete, action: { [weak self] in
|
||||
ActionSheetButtonItem(title: strings.Common_Delete, color: .destructive, action: { [weak self] in
|
||||
dismissAction()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -2684,37 +2706,52 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode<SecureIdDocum
|
||||
var entries: [SecureIdDocumentGalleryEntry] = []
|
||||
var index = 0
|
||||
var centralIndex = 0
|
||||
if let selfieDocument = innerState.selfieDocument, selfieDocument.id == document.id {
|
||||
entries.append(SecureIdDocumentGalleryEntry(index: Int32(index), resource: selfieDocument.resource, location: SecureIdDocumentGalleryEntryLocation(position: Int32(index), totalCount: 1), error: ""))
|
||||
var totalCount: Int32 = 0
|
||||
if innerState.frontSideDocument != nil {
|
||||
totalCount += 1
|
||||
}
|
||||
if innerState.backSideDocument != nil {
|
||||
totalCount += 1
|
||||
}
|
||||
if innerState.selfieDocument != nil {
|
||||
totalCount += 1
|
||||
}
|
||||
totalCount += Int32(innerState.documents.count)
|
||||
totalCount += Int32(innerState.translations.count)
|
||||
|
||||
if let frontSideDocument = innerState.frontSideDocument {
|
||||
entries.append(SecureIdDocumentGalleryEntry(index: Int32(index), resource: frontSideDocument.resource, location: SecureIdDocumentGalleryEntryLocation(position: Int32(index), totalCount: totalCount), error: ""))
|
||||
centralIndex = index
|
||||
index += 1
|
||||
} else if let frontSideDocument = innerState.frontSideDocument, frontSideDocument.id == document.id {
|
||||
entries.append(SecureIdDocumentGalleryEntry(index: Int32(index), resource: frontSideDocument.resource, location: SecureIdDocumentGalleryEntryLocation(position: Int32(index), totalCount: 1), error: ""))
|
||||
}
|
||||
if let backSideDocument = innerState.backSideDocument {
|
||||
entries.append(SecureIdDocumentGalleryEntry(index: Int32(index), resource: backSideDocument.resource, location: SecureIdDocumentGalleryEntryLocation(position: Int32(index), totalCount: totalCount), error: ""))
|
||||
centralIndex = index
|
||||
index += 1
|
||||
} else if let backSideDocument = innerState.backSideDocument, backSideDocument.id == document.id {
|
||||
entries.append(SecureIdDocumentGalleryEntry(index: Int32(index), resource: backSideDocument.resource, location: SecureIdDocumentGalleryEntryLocation(position: Int32(index), totalCount: 1), error: ""))
|
||||
}
|
||||
if let selfieDocument = innerState.selfieDocument {
|
||||
entries.append(SecureIdDocumentGalleryEntry(index: Int32(index), resource: selfieDocument.resource, location: SecureIdDocumentGalleryEntryLocation(position: Int32(index), totalCount: totalCount), error: ""))
|
||||
centralIndex = index
|
||||
index += 1
|
||||
} else {
|
||||
}
|
||||
if let _ = innerState.documents.index(where: { $0.id == document.id }) {
|
||||
for itemDocument in innerState.documents {
|
||||
entries.append(SecureIdDocumentGalleryEntry(index: Int32(index), resource: itemDocument.resource, location: SecureIdDocumentGalleryEntryLocation(position: Int32(index), totalCount: Int32(innerState.documents.count)), error: ""))
|
||||
entries.append(SecureIdDocumentGalleryEntry(index: Int32(index), resource: itemDocument.resource, location: SecureIdDocumentGalleryEntryLocation(position: Int32(index), totalCount: totalCount), error: ""))
|
||||
if document.id == itemDocument.id {
|
||||
centralIndex = index
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
} else if let _ = innerState.translations.index(where: { $0.id == document.id }) {
|
||||
}
|
||||
if let _ = innerState.translations.index(where: { $0.id == document.id }) {
|
||||
for itemDocument in innerState.translations {
|
||||
entries.append(SecureIdDocumentGalleryEntry(index: Int32(index), resource: itemDocument.resource, location: SecureIdDocumentGalleryEntryLocation(position: Int32(index), totalCount: Int32(innerState.documents.count)), error: ""))
|
||||
entries.append(SecureIdDocumentGalleryEntry(index: Int32(index), resource: itemDocument.resource, location: SecureIdDocumentGalleryEntryLocation(position: Int32(index), totalCount: totalCount), error: ""))
|
||||
if document.id == itemDocument.id {
|
||||
centralIndex = index
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let galleryController = SecureIdDocumentGalleryController(account: self.account, context: self.context, entries: entries, centralIndex: centralIndex, replaceRootController: { _, _ in
|
||||
|
||||
|
||||
@ -23,12 +23,14 @@ final class SecureIdPlaintextFormParams {
|
||||
fileprivate let updateTextField: (SecureIdPlaintextFormTextField, String) -> Void
|
||||
fileprivate let usePhone: (String) -> Void
|
||||
fileprivate let useEmailAddress: (String) -> Void
|
||||
fileprivate let save: () -> Void
|
||||
|
||||
fileprivate init(openCountrySelection: @escaping () -> Void, updateTextField: @escaping (SecureIdPlaintextFormTextField, String) -> Void, usePhone: @escaping (String) -> Void, useEmailAddress: @escaping (String) -> Void) {
|
||||
fileprivate init(openCountrySelection: @escaping () -> Void, updateTextField: @escaping (SecureIdPlaintextFormTextField, String) -> Void, usePhone: @escaping (String) -> Void, useEmailAddress: @escaping (String) -> Void, save: @escaping () -> Void) {
|
||||
self.openCountrySelection = openCountrySelection
|
||||
self.updateTextField = updateTextField
|
||||
self.usePhone = usePhone
|
||||
self.useEmailAddress = useEmailAddress
|
||||
self.save = save
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,7 +289,18 @@ struct SecureIdPlaintextFormInnerState: FormControllerInnerState {
|
||||
result.append(.entry(SecureIdPlaintextFormEntry.numberInputInfo))
|
||||
case let .verify(verify):
|
||||
result.append(.spacer)
|
||||
result.append(.entry(SecureIdPlaintextFormEntry.numberCode(verify.code)))
|
||||
var codeLength: Int32 = 5
|
||||
switch verify.payload.type {
|
||||
case let .sms(length):
|
||||
codeLength = length
|
||||
case let .call(length):
|
||||
codeLength = length
|
||||
case let .otherSession(length):
|
||||
codeLength = length
|
||||
default:
|
||||
break
|
||||
}
|
||||
result.append(.entry(SecureIdPlaintextFormEntry.numberCode(verify.code, codeLength)))
|
||||
result.append(.entry(SecureIdPlaintextFormEntry.numberVerifyInfo))
|
||||
}
|
||||
return result
|
||||
@ -308,7 +321,7 @@ struct SecureIdPlaintextFormInnerState: FormControllerInnerState {
|
||||
result.append(.entry(SecureIdPlaintextFormEntry.emailInputInfo))
|
||||
case let .verify(verify):
|
||||
result.append(.spacer)
|
||||
result.append(.entry(SecureIdPlaintextFormEntry.numberCode(verify.code)))
|
||||
result.append(.entry(SecureIdPlaintextFormEntry.numberCode(verify.code, verify.payload.length)))
|
||||
result.append(.entry(SecureIdPlaintextFormEntry.emailVerifyInfo(verify.email)))
|
||||
}
|
||||
return result
|
||||
@ -414,7 +427,7 @@ enum SecureIdPlaintextFormEntry: FormControllerEntry {
|
||||
case numberInputHeader
|
||||
case numberInput(countryCode: String, number: String)
|
||||
case numberInputInfo
|
||||
case numberCode(String)
|
||||
case numberCode(String, Int32)
|
||||
case numberVerifyInfo
|
||||
case immediatelyAvailableEmail(String)
|
||||
case immediatelyAvailableEmailInfo
|
||||
@ -489,8 +502,8 @@ enum SecureIdPlaintextFormEntry: FormControllerEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .numberCode(code):
|
||||
if case .numberCode(code) = to {
|
||||
case let .numberCode(code, length):
|
||||
if case .numberCode(code, length) = to {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -570,9 +583,12 @@ enum SecureIdPlaintextFormEntry: FormControllerEntry {
|
||||
})
|
||||
case .numberInputInfo:
|
||||
return FormControllerTextItem(text: strings.Passport_Phone_Help)
|
||||
case let .numberCode(code):
|
||||
case let .numberCode(code, length):
|
||||
return FormControllerTextInputItem(title: strings.ChangePhoneNumberCode_CodePlaceholder, text: code, placeholder: strings.ChangePhoneNumberCode_CodePlaceholder, type: .number, textUpdated: { value in
|
||||
params.updateTextField(.code, value)
|
||||
if value.count == length {
|
||||
params.save()
|
||||
}
|
||||
}, returnPressed: {
|
||||
|
||||
})
|
||||
@ -663,6 +679,8 @@ final class SecureIdPlaintextFormControllerNode: FormControllerNode<SecureIdPlai
|
||||
self?.savePhone(value)
|
||||
}, useEmailAddress: { [weak self] value in
|
||||
self?.saveEmailAddress(value)
|
||||
}, save: { [weak self] in
|
||||
self?.save()
|
||||
})
|
||||
}
|
||||
|
||||
@ -680,6 +698,29 @@ final class SecureIdPlaintextFormControllerNode: FormControllerNode<SecureIdPlai
|
||||
}
|
||||
}
|
||||
|
||||
func activateMainInput() {
|
||||
self.enumerateItemsAndEntries({ itemEntry, itemNode in
|
||||
switch itemEntry {
|
||||
case .emailAddress, .numberCode, .emailCode:
|
||||
if let inputNode = itemNode as? FormControllerTextInputItemNode {
|
||||
inputNode.activate()
|
||||
}
|
||||
return false
|
||||
case .numberInput:
|
||||
if let inputNode = itemNode as? SecureIdValueFormPhoneItemNode {
|
||||
inputNode.activate()
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override func didAppear() {
|
||||
self.activateMainInput()
|
||||
}
|
||||
|
||||
func save() {
|
||||
guard var innerState = self.innerState else {
|
||||
return
|
||||
@ -813,6 +854,7 @@ final class SecureIdPlaintextFormControllerNode: FormControllerNode<SecureIdPlai
|
||||
innerState.actionState = .none
|
||||
innerState.data = .phone(.verify(PhoneVerifyState(phone: inputPhone, payload: result, code: "")))
|
||||
strongSelf.updateInnerState(transition: .immediate, with: innerState)
|
||||
strongSelf.activateMainInput()
|
||||
}
|
||||
}, error: { [weak self] error in
|
||||
if let strongSelf = self {
|
||||
@ -858,6 +900,7 @@ final class SecureIdPlaintextFormControllerNode: FormControllerNode<SecureIdPlai
|
||||
innerState.actionState = .none
|
||||
innerState.data = .email(.verify(EmailVerifyState(email: value, payload: result, code: "")))
|
||||
strongSelf.updateInnerState(transition: .immediate, with: innerState)
|
||||
strongSelf.activateMainInput()
|
||||
}
|
||||
}, error: { [weak self] error in
|
||||
if let strongSelf = self {
|
||||
|
||||
@ -170,4 +170,8 @@ final class SecureIdValueFormPhoneItemNode: FormBlockItemNode<SecureIdValueFormP
|
||||
private func numberTextUpdated(_ value: String) {
|
||||
self.item?.updateNumber(value)
|
||||
}
|
||||
|
||||
func activate() {
|
||||
self.phoneInputNode.numberField.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,6 +46,7 @@ private struct SettingsItemArguments {
|
||||
let openFaq: () -> Void
|
||||
let openEditing: () -> Void
|
||||
let updateArchivedPacks: ([ArchivedStickerPackItem]?) -> Void
|
||||
let displayCopyContextMenu: () -> Void
|
||||
}
|
||||
|
||||
private enum SettingsSection: Int32 {
|
||||
@ -263,6 +264,8 @@ private enum SettingsEntry: ItemListNodeEntry {
|
||||
arguments.avatarTapAction()
|
||||
}, context: arguments.avatarAndNameInfoContext, updatingImage: updatingImage, action: {
|
||||
arguments.openEditing()
|
||||
}, longTapAction: {
|
||||
arguments.displayCopyContextMenu()
|
||||
})
|
||||
case let .setProfilePhoto(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
@ -432,6 +435,7 @@ public func settingsController(account: Account, accountManager: AccountManager)
|
||||
var updateHiddenAvatarImpl: (() -> Void)?
|
||||
var changeProfilePhotoImpl: (() -> Void)?
|
||||
var openSavedMessagesImpl: (() -> Void)?
|
||||
var displayCopyContextMenuImpl: ((Peer) -> Void)?
|
||||
|
||||
let archivedPacks = Promise<[ArchivedStickerPackItem]?>()
|
||||
|
||||
@ -544,6 +548,14 @@ public func settingsController(account: Account, accountManager: AccountManager)
|
||||
})
|
||||
}, updateArchivedPacks: { packs in
|
||||
archivedPacks.set(.single(packs))
|
||||
}, displayCopyContextMenu: {
|
||||
let _ = (account.postbox.transaction { transaction -> (Peer?) in
|
||||
return transaction.getPeer(account.peerId)
|
||||
} |> deliverOnMainQueue).start(next: { peer in
|
||||
if let peer = peer {
|
||||
displayCopyContextMenuImpl?(peer)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
changeProfilePhotoImpl = {
|
||||
@ -715,6 +727,45 @@ public func settingsController(account: Account, accountManager: AccountManager)
|
||||
controller.tabBarItemDebugTapAction = {
|
||||
pushControllerImpl?(debugController(account: account, accountManager: accountManager))
|
||||
}
|
||||
|
||||
displayCopyContextMenuImpl = { [weak controller] peer in
|
||||
if let strongController = controller {
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
var resultItemNode: ListViewItemNode?
|
||||
let _ = strongController.frameForItemNode({ itemNode in
|
||||
if let itemNode = itemNode as? ItemListAvatarAndNameInfoItemNode {
|
||||
resultItemNode = itemNode
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if let resultItemNode = resultItemNode, let user = peer as? TelegramUser {
|
||||
var actions: [ContextMenuAction] = []
|
||||
|
||||
if let phone = user.phone, !phone.isEmpty {
|
||||
actions.append(ContextMenuAction(content: .text(presentationData.strings.Settings_CopyPhoneNumber), action: {
|
||||
UIPasteboard.general.string = formatPhoneNumber(phone)
|
||||
}))
|
||||
}
|
||||
|
||||
if let username = user.username, !username.isEmpty {
|
||||
actions.append(ContextMenuAction(content: .text(presentationData.strings.Settings_CopyUsername), action: {
|
||||
UIPasteboard.general.string = username
|
||||
}))
|
||||
}
|
||||
|
||||
let contextMenuController = ContextMenuController(actions: actions)
|
||||
strongController.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak resultItemNode] in
|
||||
if let strongController = controller, let resultItemNode = resultItemNode {
|
||||
return (resultItemNode, resultItemNode.contentBounds.insetBy(dx: 0.0, dy: -2.0), strongController.displayNode, strongController.view.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
controller.didAppear = { _ in
|
||||
updatePassport()
|
||||
}
|
||||
|
||||
@ -540,7 +540,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { peers in
|
||||
if let strongSelf = self {
|
||||
let searchContentNode = ShareSearchContainerNode(account: strongSelf.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction!, recentPeers: peers.map({ $0.peer }))
|
||||
let searchContentNode = ShareSearchContainerNode(account: strongSelf.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction!, recentPeers: peers.filter({ $0.peer.peerId.namespace != Namespaces.Peer.SecretChat }).map({ $0.peer }))
|
||||
searchContentNode.cancel = {
|
||||
if let strongSelf = self, let peersContentNode = strongSelf.peersContentNode {
|
||||
strongSelf.transitionToContentNode(peersContentNode)
|
||||
|
||||
@ -262,7 +262,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
|
||||
}
|
||||
|
||||
for renderedPeer in foundLocalPeers {
|
||||
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != accountPeer.id {
|
||||
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != accountPeer.id, peer.id.namespace != Namespaces.Peer.SecretChat {
|
||||
if !existingPeerIds.contains(peer.id) {
|
||||
existingPeerIds.insert(peer.id)
|
||||
var associatedPeer: Peer?
|
||||
|
||||
@ -163,6 +163,7 @@
|
||||
switch (screenSize)
|
||||
{
|
||||
case 812:
|
||||
case 896:
|
||||
titleY = 445 + 44;
|
||||
imageY = 141 + 44;
|
||||
descY = 490 + 44;
|
||||
|
||||
@ -1145,7 +1145,7 @@ public func userInfoController(account: Account, peerId: PeerId) -> ViewControll
|
||||
}
|
||||
})
|
||||
}
|
||||
shareMyContactImpl = {
|
||||
shareMyContactImpl = { [weak controller] in
|
||||
let _ = (peerView.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { view in
|
||||
@ -1154,6 +1154,12 @@ public func userInfoController(account: Account, peerId: PeerId) -> ViewControll
|
||||
}
|
||||
|
||||
let contact = TelegramMediaContact(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phoneNumber: phone, peerId: peer.id, vCardData: nil)
|
||||
|
||||
let _ = (enqueueMessages(account: account, peerId: peerId, messages: [.message(text: "", attributes: [], mediaReference: .standalone(media: contact), replyToMessageId: nil, localGroupingKey: nil)])
|
||||
|> deliverOnMainQueue).start(next: { [weak controller] _ in
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
controller?.present(OverlayStatusController(theme: presentationData.theme, type: .success), in: .window(.root))
|
||||
})
|
||||
})
|
||||
}
|
||||
startSecretChatImpl = { [weak controller] in
|
||||
|
||||
@ -66,9 +66,6 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
private let imageNode: TransformImageNode
|
||||
private let playerNode: WebEmbedPlayerNode
|
||||
|
||||
private let thumbnail = Promise<UIImage?>()
|
||||
private var thumbnailDisposable: Disposable?
|
||||
|
||||
private var loadProgressDisposable: Disposable?
|
||||
private var statusDisposable: Disposable?
|
||||
|
||||
@ -83,6 +80,7 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
}
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
|
||||
if let embedUrl = webpageContent.embedUrl {
|
||||
let impl = webEmbedImplementation(embedUrl: embedUrl, url: webpageContent.url)
|
||||
self.playerNode = WebEmbedPlayerNode(impl: impl, intrinsicDimensions: self.intrinsicDimensions)
|
||||
@ -100,14 +98,9 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
|
||||
if let image = webpageContent.image {
|
||||
self.imageNode.setSignal(chatMessagePhoto(postbox: postbox, photoReference: .webPage(webPage: WebpageReference(webPage), media: image)))
|
||||
|
||||
self.thumbnailDisposable = (rawMessagePhoto(postbox: postbox, photoReference: .webPage(webPage: WebpageReference(webPage), media: image))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] image in
|
||||
if let strongSelf = self {
|
||||
strongSelf.thumbnail.set(.single(image))
|
||||
strongSelf._ready.set(.single(Void()))
|
||||
self.imageNode.imageUpdated = { [weak self] in
|
||||
self?._ready.set(.single(Void()))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self._ready.set(.single(Void()))
|
||||
}
|
||||
@ -152,9 +145,7 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
|
||||
deinit {
|
||||
self.audioSessionDisposable.dispose()
|
||||
|
||||
self.loadProgressDisposable?.dispose()
|
||||
self.thumbnailDisposable?.dispose()
|
||||
self.statusDisposable?.dispose()
|
||||
}
|
||||
|
||||
@ -163,31 +154,22 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
transition.updateTransformScale(node: self.playerNode, scale: size.width / self.intrinsicDimensions.width)
|
||||
|
||||
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let makeImageLayout = self.imageNode.asyncLayout()
|
||||
let applyImageLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets()))
|
||||
applyImageLayout()
|
||||
}
|
||||
|
||||
func play() {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
self.playerNode.play()
|
||||
|
||||
// if !self.initializedStatus {
|
||||
// self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true)))
|
||||
// } else {
|
||||
// self.playerView.playVideo()
|
||||
// }
|
||||
//>>>>>>> 368a96b2910b01bf361ed88aefd8662804f55f0a
|
||||
}
|
||||
|
||||
func pause() {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
self.playerNode.pause()
|
||||
|
||||
// if !self.initializedStatus {
|
||||
// self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: self.seekId, status: .paused))
|
||||
// }
|
||||
// self.playerView.pauseVideo()
|
||||
//>>>>>>> 368a96b2910b01bf361ed88aefd8662804f55f0a
|
||||
}
|
||||
|
||||
func togglePlayPause() {
|
||||
@ -197,18 +179,12 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
|
||||
func setSoundEnabled(_ value: Bool) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
/*if value {
|
||||
self.player.playOnceWithSound()
|
||||
} else {
|
||||
self.player.continuePlayingWithoutSound()
|
||||
}*/
|
||||
}
|
||||
|
||||
func seek(_ timestamp: Double) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
self.seekId += 1
|
||||
self.playerNode.seek(timestamp: timestamp)
|
||||
//self.playerView.seek(toPosition: timestamp)
|
||||
}
|
||||
|
||||
func playOnceWithSound(playAndRecord: Bool) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user