no message

This commit is contained in:
Ilya Laktyushin 2018-09-17 20:38:17 +01:00
parent c2149ecd2e
commit 8407cd1c5f
43 changed files with 753 additions and 405 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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 = []

View File

@ -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: {

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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() {

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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()
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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)))

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -170,4 +170,8 @@ final class SecureIdValueFormPhoneItemNode: FormBlockItemNode<SecureIdValueFormP
private func numberTextUpdated(_ value: String) {
self.item?.updateNumber(value)
}
func activate() {
self.phoneInputNode.numberField.becomeFirstResponder()
}
}

View File

@ -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()
}

View File

@ -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)

View File

@ -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?

View File

@ -163,6 +163,7 @@
switch (screenSize)
{
case 812:
case 896:
titleY = 445 + 44;
imageY = 141 + 44;
descY = 490 + 44;

View File

@ -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

View File

@ -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) {