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,59 +224,141 @@ func createPasswordController(account: Account, state: CreatePasswordState, comp
let saveDisposable = MetaDisposable()
actionsDisposable.add(saveDisposable)
var initialFocusImpl: (() -> Void)?
var selectNextInputItemImpl: ((CreatePasswordEntryTag) -> Void)?
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 {
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
} else {
let saveImpl: () -> Void = {
var currentPassword: String?
var email: String?
updateState { state in
var state = state
if case let .setup(password) = state.state {
currentPassword = password
if password != nil {
email = nil
} else {
email = state.emailText
}
}
state.saving = true
return state
}
saveDisposable.set((updateTwoStepVerificationPassword(network: account.network, currentPassword: currentPassword, updatedPassword: .password(password: state.passwordText, hint: state.hintText, email: email))
|> deliverOnMainQueue).start(next: { update in
switch update {
case .none:
break
case let .password(password, pendingEmailPattern):
if let pendingEmailPattern = pendingEmailPattern {
if processPasswordEmailConfirmation {
updateState { state in
var state = state
state.saving = false
state.state = .pendingVerification(emailPattern: pendingEmailPattern)
return state
}
}
updatePasswordEmailConfirmation(pendingEmailPattern)
} else {
completion(password, state.hintText, !state.emailText.isEmpty)
}
}
}, error: { _ in
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}))
}
var emailAlert = false
switch state.state {
case let .setup(currentPassword):
if currentPassword != nil {
emailAlert = false
} else {
emailAlert = state.emailText.isEmpty
}
case .pendingVerification:
break
}
if emailAlert {
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: presentationData.strings.TwoStepAuth_EmailSkip, action: {
saveImpl()
})]), nil)
} else {
saveImpl()
}
}
}
}
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
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
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
}
}))
|> 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
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
})
@ -275,80 +369,7 @@ func createPasswordController(account: Account, state: CreatePasswordState, comp
switch state.state {
case .setup:
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.passwordText.isEmpty, action: {
var state: CreatePasswordControllerState?
updateState { s in
state = s
return s
}
if let state = state {
if state.passwordText.isEmpty {
} else if state.passwordText != state.passwordConfirmationText {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
} else {
let saveImpl: () -> Void = {
var currentPassword: String?
var email: String?
updateState { state in
var state = state
if case let .setup(password) = state.state {
currentPassword = password
if password != nil {
email = nil
} else {
email = state.emailText
}
}
state.saving = true
return state
}
saveDisposable.set((updateTwoStepVerificationPassword(network: account.network, currentPassword: currentPassword, updatedPassword: .password(password: state.passwordText, hint: state.hintText, email: email))
|> deliverOnMainQueue).start(next: { update in
switch update {
case .none:
break
case let .password(password, pendingEmailPattern):
if let pendingEmailPattern = pendingEmailPattern {
if processPasswordEmailConfirmation {
updateState { state in
var state = state
state.saving = false
state.state = .pendingVerification(emailPattern: pendingEmailPattern)
return state
}
}
updatePasswordEmailConfirmation(pendingEmailPattern)
} else {
completion(password, state.hintText, !state.emailText.isEmpty)
}
}
}, error: { _ in
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}))
}
var emailAlert = false
switch state.state {
case let .setup(currentPassword):
if currentPassword != nil {
emailAlert = false
} else {
emailAlert = state.emailText.isEmpty
}
case .pendingVerification:
break
}
if emailAlert {
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: presentationData.strings.TwoStepAuth_EmailSkip, action: {
saveImpl()
})]), nil)
} else {
saveImpl()
}
}
}
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

@ -75,7 +75,7 @@ func stringForFullDate(timestamp: Int32, strings: PresentationStrings, timeForma
}
}
func stringForDate(timestamp: Int32, strings: PresentationStrings) -> String {
func stringForDate(timestamp: Int32, strings: PresentationStrings) -> String {
let formatter = DateFormatter()
formatter.timeStyle = .none
formatter.dateStyle = .medium

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

@ -24,7 +24,7 @@ func generatePlayerRateIcon(_ color: UIColor) -> UIImage? {
context.setStrokeColor(color.cgColor)
context.setLineWidth(4.0)
context.scaleBy(x: 0.3333, y: 0.3333)
let _ = try? drawSvgPath(context, path: "M15.3637695,32.1972656 L23.7749023,32.1972656 C24.6127972,32.1972656 25.2519509,32.3691389 25.6923828,32.7128906 C26.1328147,33.0566423 26.3530273,33.5239228 26.3530273,34.1147461 C26.3530273,34.6411159 26.1784685,35.0869122 25.8293457,35.4521484 C25.4802229,35.8173846 24.9511754,36 24.2421875,36 L12.3828125,36 C11.5771444,36 10.9487327,35.7771018 10.4975586,35.3312988 C10.0463845,34.8854958 9.82080078,34.3618194 9.82080078,33.7602539 C9.82080078,33.3735332 9.96581886,32.8605989 10.2558594,32.2214355 C10.5458999,31.5822722 10.8627913,31.08008 11.206543,30.7148438 C12.635261,29.2324145 13.9243107,27.9621635 15.0737305,26.9040527 C16.2231503,25.845942 17.0449194,25.1503923 17.5390625,24.8173828 C18.4199263,24.1943328 19.1530732,23.5686067 19.7385254,22.9401855 C20.3239775,22.3117644 20.7697739,21.6672396 21.0759277,21.0065918 C21.3820816,20.345944 21.5351562,19.6987336 21.5351562,19.0649414 C21.5351562,18.377438 21.3713395,17.7624539 21.0437012,17.2199707 C20.7160628,16.6774875 20.2702665,16.2558609 19.7062988,15.9550781 C19.1423312,15.6542954 18.5273471,15.5039062 17.8613281,15.5039062 C16.4540945,15.5039062 15.3476603,16.1215759 14.5419922,17.3569336 C14.4345698,17.5180672 14.2546399,17.9584925 14.0021973,18.6782227 C13.7497546,19.3979528 13.4650895,19.9511699 13.1481934,20.3378906 C12.8312972,20.7246113 12.3667023,20.9179688 11.7543945,20.9179688 C11.2172825,20.9179688 10.7714861,20.7407244 10.4169922,20.3862305 C10.0624982,20.0317365 9.88525391,19.5483429 9.88525391,18.9360352 C9.88525391,18.1948205 10.0517561,17.4213907 10.3847656,16.6157227 C10.7177751,15.8100546 11.2145963,15.0795931 11.8752441,14.4243164 C12.535892,13.7690397 13.3737742,13.2399922 14.388916,12.8371582 C15.4040578,12.4343242 16.5937432,12.2329102 17.9580078,12.2329102 C19.6015707,12.2329102 21.0034122,12.4907201 22.1635742,13.0063477 C22.9155311,13.3500994 23.576169,13.8227509 24.1455078,14.4243164 C24.7148466,15.0258819 25.1579574,15.7214316 25.4748535,16.5109863 C25.7917496,17.3005411 25.9501953,18.1196247 25.9501953,18.9682617 C25.9501953,20.3002996 25.6198764,21.5114692 24.9592285,22.6018066 C24.2985807,23.6921441 23.6245152,24.5461395 22.9370117,25.1638184 C22.2495083,25.7814972 21.0974202,26.75097 19.4807129,28.0722656 C17.8640056,29.3935613 16.7548858,30.4194299 16.1533203,31.1499023 C15.8955065,31.4399429 15.6323256,31.7890605 15.3637695,32.1972656 Z M28.8464425,31.4077148 L34.1315987,23.6894531 L29.6843331,16.8251953 C29.2653857,16.1591764 28.9511799,15.5871606 28.7417062,15.1091309 C28.5322325,14.6311011 28.4274972,14.1718772 28.4274972,13.7314453 C28.4274972,13.2802712 28.6289112,12.8747577 29.0317452,12.5148926 C29.4345793,12.1550275 29.9260294,11.9750977 30.5061105,11.9750977 C31.1721294,11.9750977 31.6904348,12.1711406 32.0610421,12.5632324 C32.4316494,12.9553242 32.9445837,13.6831002 33.5998605,14.746582 L37.1447823,20.4829102 L40.9314034,14.746582 C41.2429284,14.2631812 41.5087949,13.8496111 41.7290109,13.5058594 C41.9492268,13.1621077 42.1613829,12.8774425 42.3654855,12.6518555 C42.569588,12.4262684 42.7978572,12.2570806 43.0502999,12.1442871 C43.3027426,12.0314936 43.5954643,11.9750977 43.9284737,11.9750977 C44.5300392,11.9750977 45.0214894,12.1550275 45.402839,12.5148926 C45.7841885,12.8747577 45.9748605,13.3017553 45.9748605,13.7958984 C45.9748605,14.5156286 45.5612904,15.4931579 44.7341378,16.7285156 L40.0773995,23.6894531 L45.08863,31.4077148 C45.5398041,32.084476 45.8674376,32.6457497 46.0715401,33.0915527 C46.2756427,33.5373557 46.3776925,33.9589824 46.3776925,34.3564453 C46.3776925,34.7324238 46.2863848,35.0761703 46.1037667,35.3876953 C45.9211486,35.6992203 45.6633387,35.9462881 45.3303292,36.1289062 C44.9973197,36.3115244 44.6213469,36.402832 44.2023995,36.402832 C43.7512254,36.402832 43.3698815,36.3088388 43.0583566,36.1208496 C42.7468316,35.9328604 42.4943927,35.6992201 42.3010323,35.4199219 C42.107672,35.1406236 41.7478123,34.5981486 41.2214425,33.7924805 L37.0642159,27.2504883 L32.6491769,33.9858398 C32.3054251,34.5229519 32.0610428,34.8989247 31.9160226,35.1137695 C31.7710023,35.3286144 31.5964435,35.5380849 31.3923409,35.7421875 C31.1882383,35.9462901 30.9465415,36.1074213 30.6672433,36.2255859 C30.387945,36.3437506 30.0603116,36.402832 29.6843331,36.402832 C29.1042521,36.402832 28.623544,36.2255877 28.2421944,35.8710938 C27.8608449,35.5165998 27.670173,35.0009799 27.670173,34.3242188 C27.670173,33.5292929 28.0622589,32.5571347 28.8464425,31.4077148 Z M8,2 C4.6862915,2 2,4.6862915 2,8 L2,40 C2,43.3137085 4.6862915,46 8,46 L48,46 C51.3137085,46 54,43.3137085 54,40 L54,8 C54,4.6862915 51.3137085,2 48,2 L8,2 S ")
let _ = try? drawSvgPath(context, path: "M15.3637695,32.1972656 L23.7749023,32.1972656 C24.6127972,32.1972656 25.2519509,32.3691389 25.6923828,32.7128906 C26.1328147,33.0566423 26.3530273,33.5239228 26.3530273,34.1147461 C26.3530273,34.6411159 26.1784685,35.0869122 25.8293457,35.4521484 C25.4802229,35.8173846 24.9511754,36 24.2421875,36 L12.3828125,36 C11.5771444,36 10.9487327,35.7771018 10.4975586,35.3312988 C10.0463845,34.8854958 9.82080078,34.3618194 9.82080078,33.7602539 C9.82080078,33.3735332 9.96581886,32.8605989 10.2558594,32.2214355 C10.5458999,31.5822722 10.8627913,31.08008 11.206543,30.7148438 C12.635261,29.2324145 13.9243107,27.9621635 15.0737305,26.9040527 C16.2231503,25.845942 17.0449194,25.1503923 17.5390625,24.8173828 C18.4199263,24.1943328 19.1530732,23.5686067 19.7385254,22.9401855 C20.3239775,22.3117644 20.7697739,21.6672396 21.0759277,21.0065918 C21.3820816,20.345944 21.5351562,19.6987336 21.5351562,19.0649414 C21.5351562,18.377438 21.3713395,17.7624539 21.0437012,17.2199707 C20.7160628,16.6774875 20.2702665,16.2558609 19.7062988,15.9550781 C19.1423312,15.6542954 18.5273471,15.5039062 17.8613281,15.5039062 C16.4540945,15.5039062 15.3476603,16.1215759 14.5419922,17.3569336 C14.4345698,17.5180672 14.2546399,17.9584925 14.0021973,18.6782227 C13.7497546,19.3979528 13.4650895,19.9511699 13.1481934,20.3378906 C12.8312972,20.7246113 12.3667023,20.9179688 11.7543945,20.9179688 C11.2172825,20.9179688 10.7714861,20.7407244 10.4169922,20.3862305 C10.0624982,20.0317365 9.88525391,19.5483429 9.88525391,18.9360352 C9.88525391,18.1948205 10.0517561,17.4213907 10.3847656,16.6157227 C10.7177751,15.8100546 11.2145963,15.0795931 11.8752441,14.4243164 C12.535892,13.7690397 13.3737742,13.2399922 14.388916,12.8371582 C15.4040578,12.4343242 16.5937432,12.2329102 17.9580078,12.2329102 C19.6015707,12.2329102 21.0034122,12.4907201 22.1635742,13.0063477 C22.9155311,13.3500994 23.576169,13.8227509 24.1455078,14.4243164 C24.7148466,15.0258819 25.1579574,15.7214316 25.4748535,16.5109863 C25.7917496,17.3005411 25.9501953,18.1196247 25.9501953,18.9682617 C25.9501953,20.3002996 25.6198764,21.5114692 24.9592285,22.6018066 C24.2985807,23.6921441 23.6245152,24.5461395 22.9370117,25.1638184 C22.2495083,25.7814972 21.0974202,26.75097 19.4807129,28.0722656 C17.8640056,29.3935613 16.7548858,30.4194299 16.1533203,31.1499023 C15.8955065,31.4399429 15.6323256,31.7890605 15.3637695,32.1972656 Z M28.8464425,31.4077148 L34.1315987,23.6894531 L29.6843331,16.8251953 C29.2653857,16.1591764 28.9511799,15.5871606 28.7417062,15.1091309 C28.5322325,14.6311011 28.4274972,14.1718772 28.4274972,13.7314453 C28.4274972,13.2802712 28.6289112,12.8747577 29.0317452,12.5148926 C29.4345793,12.1550275 29.9260294,11.9750977 30.5061105,11.9750977 C31.1721294,11.9750977 31.6904348,12.1711406 32.0610421,12.5632324 C32.4316494,12.9553242 32.9445837,13.6831002 33.5998605,14.746582 L37.1447823,20.4829102 L40.9314034,14.746582 C41.2429284,14.2631812 41.5087949,13.8496111 41.7290109,13.5058594 C41.9492268,13.1621077 42.1613829,12.8774425 42.3654855,12.6518555 C42.569588,12.4262684 42.7978572,12.2570806 43.0502999,12.1442871 C43.3027426,12.0314936 43.5954643,11.9750977 43.9284737,11.9750977 C44.5300392,11.9750977 45.0214894,12.1550275 45.402839,12.5148926 C45.7841885,12.8747577 45.9748605,13.3017553 45.9748605,13.7958984 C45.9748605,14.5156286 45.5612904,15.4931579 44.7341378,16.7285156 L40.0773995,23.6894531 L45.08863,31.4077148 C45.5398041,32.084476 45.8674376,32.6457497 46.0715401,33.0915527 C46.2756427,33.5373557 46.3776925,33.9589824 46.3776925,34.3564453 C46.3776925,34.7324238 46.2863848,35.0761703 46.1037667,35.3876953 C45.9211486,35.6992203 45.6633387,35.9462881 45.3303292,36.1289062 C44.9973197,36.3115244 44.6213469,36.402832 44.2023995,36.402832 C43.7512254,36.402832 43.3698815,36.3088388 43.0583566,36.1208496 C42.7468316,35.9328604 42.4943927,35.6992201 42.3010323,35.4199219 C42.107672,35.1406236 41.7478123,34.5981486 41.2214425,33.7924805 L37.0642159,27.2504883 L32.6491769,33.9858398 C32.3054251,34.5229519 32.0610428,34.8989247 31.9160226,35.1137695 C31.7710023,35.3286144 31.5964435,35.5380849 31.3923409,35.7421875 C31.1882383,35.9462901 30.9465415,36.1074213 30.6672433,36.2255859 C30.387945,36.3437506 30.0603116,36.402832 29.6843331,36.402832 C29.1042521,36.402832 28.623544,36.2255877 28.2421944,35.8710938 C27.8608449,35.5165998 27.670173,35.0009799 27.670173,34.3242188 C27.670173,33.5292929 28.0622589,32.5571347 28.8464425,31.4077148 Z M8,2 C4.6862915,2 2,4.6862915 2,8 L2,40 C2,43.3137085 4.6862915,46 8,46 L48,46 C51.3137085,46 54,43.3137085 54,40 L54,8 C54,4.6862915 51.3137085,2 48,2 L8,2 S")
})
}

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,17 +837,29 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
self.view.endEditing(true)
self.interaction.present(controller, nil)
case .phone:
var immediatelyAvailableValue: SecureIdValue?
if let peer = list.accountPeer as? TelegramUser, let phone = peer.phone, !phone.isEmpty {
immediatelyAvailableValue = .phone(SecureIdPhoneValue(phone: 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))
}
self.interaction.present(SecureIdPlaintextFormController(account: self.account, context: context, type: .phone, immediatelyAvailableValue: immediatelyAvailableValue, updatedValue: { value in
updatedValues(.phone)(value.flatMap({ [$0] }) ?? [])
}), nil)
}
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
updatedValues(.email)(value.flatMap({ [$0] }) ?? [])
}), nil)
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)
}
}
}

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 {
result.append(.spacer)
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,35 +2706,50 @@ 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: ""))
if document.id == itemDocument.id {
centralIndex = index
}
index += 1
}
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: totalCount), error: ""))
if document.id == itemDocument.id {
centralIndex = index
}
} else 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: ""))
if document.id == itemDocument.id {
centralIndex = index
}
index += 1
index += 1
}
}
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: totalCount), error: ""))
if document.id == itemDocument.id {
centralIndex = index
}
index += 1
}
}

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