mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-03 21:16:35 +00:00
Merge branch 'master' of github.com:peter-iakovlev/TelegramUI
This commit is contained in:
commit
c6a489edf8
@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ModernMessageSelectionChecked@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ModernMessageSelectionUnchecked@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ModernContactSelectionChecked@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 795 B |
@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ModernContactSelectionEmpty@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 665 B |
@ -34,6 +34,7 @@
|
||||
09874E5721078FA100E190B8 /* YoutubeUserScript.js in Resources */ = {isa = PBXBuildFile; fileRef = 0979788121065F8B0077D77F /* YoutubeUserScript.js */; };
|
||||
09874E582107A4C300E190B8 /* VimeoEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E3A21075BF400E190B8 /* VimeoEmbedImplementation.swift */; };
|
||||
09874E592107BD4100E190B8 /* GenericEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E4021075C1700E190B8 /* GenericEmbedImplementation.swift */; };
|
||||
09AE3823214C110900850BFD /* LegacySecureIdScanController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */; };
|
||||
09C500242142BA6400EF253E /* ItemListWebsiteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */; };
|
||||
D007019C2029E8F2006B9E34 /* LegqacyICloudFileController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007019B2029E8F2006B9E34 /* LegqacyICloudFileController.swift */; };
|
||||
D007019E2029EFDD006B9E34 /* ICloudResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007019D2029EFDD006B9E34 /* ICloudResources.swift */; };
|
||||
@ -1050,6 +1051,7 @@
|
||||
09874E4021075C1700E190B8 /* GenericEmbedImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericEmbedImplementation.swift; sourceTree = "<group>"; };
|
||||
09874E4221075C3000E190B8 /* VKEmbedImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VKEmbedImplementation.swift; sourceTree = "<group>"; };
|
||||
09874E4421075C3F00E190B8 /* StreamableEmbedImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamableEmbedImplementation.swift; sourceTree = "<group>"; };
|
||||
09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySecureIdScanController.swift; sourceTree = "<group>"; };
|
||||
09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListWebsiteItem.swift; sourceTree = "<group>"; };
|
||||
D00219051DDD1C9E00BE708A /* ImageContainingNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageContainingNode.swift; sourceTree = "<group>"; };
|
||||
D002A0D01E9B99F500A81812 /* SoftwareVideoSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SoftwareVideoSource.swift; sourceTree = "<group>"; };
|
||||
@ -2861,6 +2863,7 @@
|
||||
D07551921DDA540F0073E051 /* TelegramInitializeLegacyComponents.swift */,
|
||||
D023ED2D1DDB5BEC00BD496D /* LegacyAttachmentMenu.swift */,
|
||||
D0119CCF20CAE75F00895300 /* LegacySecureIdAttachmentMenu.swift */,
|
||||
09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */,
|
||||
D023EBB11DDA800700BD496D /* LegacyMediaPickers.swift */,
|
||||
D0671F2C2145AB28000A8AE7 /* LegacyAvatarPicker.swift */,
|
||||
D00E15251DDBD4E700ACF65C /* LegacyCamera.swift */,
|
||||
@ -5122,6 +5125,7 @@
|
||||
D0EC6DAA1EB9F58900EBF1C3 /* ChatPanelInterfaceInteraction.swift in Sources */,
|
||||
D00FF2091F4E2414006FA332 /* InstantPageSettingsNode.swift in Sources */,
|
||||
D0BE3037206139F500FBE6D8 /* ImageCompression.swift in Sources */,
|
||||
09AE3823214C110900850BFD /* LegacySecureIdScanController.swift in Sources */,
|
||||
D0EC6DAB1EB9F58900EBF1C3 /* ChatInterfaceStateAccessoryPanels.swift in Sources */,
|
||||
D0EC6DAC1EB9F58900EBF1C3 /* ChatInterfaceStateInputPanels.swift in Sources */,
|
||||
D056CD761FF2A30900880D28 /* ChatSwipeToReplyRecognizer.swift in Sources */,
|
||||
|
||||
@ -26,7 +26,8 @@ func authorizationNextOptionText(_ type: AuthorizationCodeNextType?, timeout: In
|
||||
if timeout <= 0 {
|
||||
return (NSAttributedString(string: strings.Login_CodeSentSms, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false)
|
||||
} else {
|
||||
return (NSAttributedString(string: strings.Login_SmsRequestState1(Int(minutes), Int(seconds)).0, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false)
|
||||
let timeString = NSString(format: "%d:%.02d", Int(minutes), Int(seconds))
|
||||
return (NSAttributedString(string: strings.Login_WillSendSms(timeString as String).0, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false)
|
||||
}
|
||||
case .call, .flashCall:
|
||||
if timeout <= 0 {
|
||||
@ -268,7 +269,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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,16 +49,18 @@ class AutodownloadSizeLimitItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? AutodownloadSizeLimitItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? AutodownloadSizeLimitItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -27,16 +27,18 @@ class BotCheckoutHeaderItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? BotCheckoutHeaderItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? BotCheckoutHeaderItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -28,16 +28,18 @@ class BotCheckoutPriceItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? BotCheckoutPriceItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? BotCheckoutPriceItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -24,16 +24,18 @@ class CalculatingCacheSizeItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? CalculatingCacheSizeItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? CalculatingCacheSizeItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -99,18 +99,20 @@ class CallListCallItem: ListViewItem {
|
||||
node.contentSize = nodeLayout.contentSize
|
||||
node.insets = nodeLayout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
nodeApply().1(false)
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
nodeApply().1(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? CallListCallItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let layout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? CallListCallItemNode {
|
||||
let layout = nodeValue.asyncLayout()
|
||||
async {
|
||||
let (first, last, firstWithHeader) = CallListCallItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
let (nodeLayout, apply) = layout(self, params, first, last, firstWithHeader, callListNeighbors(item: self, topItem: previousItem, bottomItem: nextItem))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -152,7 +152,7 @@ final class ChangePhoneNumberControllerNode: ASDisplayNode {
|
||||
if let code = Int(code), let (coutnryId, countryName) = countryCodeToIdAndName[code] {
|
||||
strongSelf.countryButton.setTitle(countryName, with: Font.regular(17.0), with: strongSelf.presentationData.theme.list.itemPrimaryTextColor, for: [])
|
||||
} else {
|
||||
strongSelf.countryButton.setTitle(strongSelf.presentationData.strings.Login_CountryCode, with: Font.regular(17.0), with: .black, for: [])
|
||||
strongSelf.countryButton.setTitle(strongSelf.presentationData.strings.Login_CountryCode, with: Font.regular(17.0), with: strongSelf.presentationData.theme.list.itemPrimaryTextColor, for: [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,9 +33,11 @@ final class ChatBotInfoItem: ListViewItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
}
|
||||
}
|
||||
if Thread.isMainThread {
|
||||
async {
|
||||
@ -46,10 +48,10 @@ final class ChatBotInfoItem: ListViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ChatBotInfoItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let nodeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ChatBotInfoItemNode {
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = nodeLayout(self, params)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -41,10 +41,10 @@ final class ChatEmptyItem: ListViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ChatEmptyItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let nodeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ChatEmptyItemNode {
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = nodeLayout(self, params)
|
||||
|
||||
@ -24,15 +24,19 @@ class ChatHoleItem: ListViewItem {
|
||||
async {
|
||||
let node = ChatHoleItemNode()
|
||||
node.layoutForParams(params, item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: node.insets), {
|
||||
})
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: node().insets), {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -429,7 +429,7 @@ struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
|
||||
public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
|
||||
let timestamp: Int32
|
||||
let composeInputState: ChatTextInputState
|
||||
let composeDisableUrlPreview: String?
|
||||
@ -442,7 +442,7 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
|
||||
let mediaRecordingMode: ChatTextInputMediaRecordingButtonMode
|
||||
let silentPosting: Bool
|
||||
|
||||
var associatedMessageIds: [MessageId] {
|
||||
public var associatedMessageIds: [MessageId] {
|
||||
var ids: [MessageId] = []
|
||||
if let editMessage = self.editMessage {
|
||||
ids.append(editMessage.messageId)
|
||||
@ -450,7 +450,7 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
|
||||
return ids
|
||||
}
|
||||
|
||||
var chatListEmbeddedState: PeerChatListEmbeddedInterfaceState? {
|
||||
public var chatListEmbeddedState: PeerChatListEmbeddedInterfaceState? {
|
||||
if self.composeInputState.inputText.length != 0 && self.timestamp != 0 {
|
||||
return ChatEmbeddedInterfaceState(timestamp: self.timestamp, text: self.composeInputState.inputText)
|
||||
} else {
|
||||
@ -458,7 +458,7 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
var synchronizeableInputState: SynchronizeableChatInputState? {
|
||||
public var synchronizeableInputState: SynchronizeableChatInputState? {
|
||||
if self.composeInputState.inputText.length == 0 {
|
||||
return nil
|
||||
} else {
|
||||
@ -466,11 +466,11 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
var historyScrollMessageIndex: MessageIndex? {
|
||||
public var historyScrollMessageIndex: MessageIndex? {
|
||||
return self.historyScrollState?.messageIndex
|
||||
}
|
||||
|
||||
func withUpdatedSynchronizeableInputState(_ state: SynchronizeableChatInputState?) -> SynchronizeableChatInterfaceState {
|
||||
public func withUpdatedSynchronizeableInputState(_ state: SynchronizeableChatInputState?) -> SynchronizeableChatInterfaceState {
|
||||
var result = self.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: state?.text ?? ""))).withUpdatedReplyMessageId(state?.replyToMessageId)
|
||||
if let timestamp = state?.timestamp {
|
||||
result = result.withUpdatedTimestamp(timestamp)
|
||||
@ -486,7 +486,7 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
public init() {
|
||||
self.timestamp = 0
|
||||
self.composeInputState = ChatTextInputState()
|
||||
self.composeDisableUrlPreview = nil
|
||||
@ -514,7 +514,7 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
|
||||
self.silentPosting = silentPosting
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.timestamp = decoder.decodeInt32ForKey("ts", orElse: 0)
|
||||
if let inputState = decoder.decodeObjectForKey("is", decoder: { return ChatTextInputState(decoder: $0) }) as? ChatTextInputState {
|
||||
self.composeInputState = inputState
|
||||
@ -563,7 +563,7 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
|
||||
self.silentPosting = decoder.decodeInt32ForKey("sip", orElse: 0) != 0
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.timestamp, forKey: "ts")
|
||||
encoder.encodeObject(self.composeInputState, forKey: "is")
|
||||
if let composeDisableUrlPreview = self.composeDisableUrlPreview {
|
||||
@ -611,7 +611,7 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
|
||||
encoder.encodeInt32(self.silentPosting ? 1 : 0, forKey: "sip")
|
||||
}
|
||||
|
||||
func isEqual(to: PeerChatInterfaceState) -> Bool {
|
||||
public func isEqual(to: PeerChatInterfaceState) -> Bool {
|
||||
if let to = to as? ChatInterfaceState, self == to {
|
||||
return true
|
||||
} else {
|
||||
@ -619,7 +619,7 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: ChatInterfaceState, rhs: ChatInterfaceState) -> Bool {
|
||||
public static func ==(lhs: ChatInterfaceState, rhs: ChatInterfaceState) -> Bool {
|
||||
if lhs.composeDisableUrlPreview != rhs.composeDisableUrlPreview {
|
||||
return false
|
||||
}
|
||||
@ -725,7 +725,7 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: mediaRecordingMode, silentPosting: self.silentPosting)
|
||||
}
|
||||
|
||||
func withUpdatedSilentPosting(_ silentPosting: Bool) -> ChatInterfaceState {
|
||||
public func withUpdatedSilentPosting(_ silentPosting: Bool) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: silentPosting)
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,26 +22,31 @@ class ChatListHoleItem: ListViewItem {
|
||||
node.relativePosition = (first: previousItem == nil, last: nextItem == nil)
|
||||
node.insets = ChatListItemNode.insets(first: false, last: false, firstWithHeader: false)
|
||||
node.layoutForParams(params, item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
assert(node is ChatListHoleItemNode)
|
||||
if let node = node as? ChatListHoleItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let layout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
assert(node() is ChatListHoleItemNode)
|
||||
if let nodeValue = node() as? ChatListHoleItemNode {
|
||||
|
||||
let layout = nodeValue.asyncLayout()
|
||||
async {
|
||||
let first = previousItem == nil
|
||||
let last = nextItem == nil
|
||||
|
||||
let (nodeLayout, apply) = layout(self, params, first, last)
|
||||
Queue.mainQueue().async {
|
||||
completion(nodeLayout, { [weak node] in
|
||||
completion(nodeLayout, {
|
||||
apply()
|
||||
node?.updateBackgroundAndSeparatorsLayout()
|
||||
if let nodeValue = node() as? ChatListHoleItemNode {
|
||||
nodeValue.updateBackgroundAndSeparatorsLayout()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,21 +60,23 @@ class ChatListItem: ListViewItem {
|
||||
node.insets = nodeLayout.insets
|
||||
node.contentSize = nodeLayout.contentSize
|
||||
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
apply(false)
|
||||
node.updateIsHighlighted(transition: .immediate)
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
apply(false)
|
||||
node.updateIsHighlighted(transition: .immediate)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
assert(node is ChatListItemNode)
|
||||
if let node = node as? ChatListItemNode {
|
||||
Queue.mainQueue().async {
|
||||
node.setupItem(item: self)
|
||||
let layout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
assert(node() is ChatListItemNode)
|
||||
if let nodeValue = node() as? ChatListItemNode {
|
||||
nodeValue.setupItem(item: self)
|
||||
let layout = nodeValue.asyncLayout()
|
||||
async {
|
||||
let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
var animated = true
|
||||
@ -707,7 +709,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 +723,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 = []
|
||||
|
||||
@ -646,7 +646,7 @@ final class ChatListNode: ListView {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.reorderItem = { [weak self] fromIndex, toIndex, transactionOpaqueState in
|
||||
self.reorderItem = { [weak self] fromIndex, toIndex, transactionOpaqueState -> Signal<Bool, NoError> in
|
||||
if let strongSelf = self, let filteredEntries = (transactionOpaqueState as? ChatListOpaqueTransactionState)?.chatListView.filteredEntries {
|
||||
if fromIndex >= 0 && fromIndex < filteredEntries.count && toIndex >= 0 && toIndex < filteredEntries.count {
|
||||
let fromEntry = filteredEntries[filteredEntries.count - 1 - fromIndex]
|
||||
@ -670,7 +670,7 @@ final class ChatListNode: ListView {
|
||||
}
|
||||
|
||||
if let _ = fromEntry.index.pinningIndex {
|
||||
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
|
||||
return strongSelf.account.postbox.transaction { transaction -> Bool in
|
||||
var itemIds = transaction.getPinnedItemIds()
|
||||
|
||||
var itemId: PinnedItemId?
|
||||
@ -706,13 +706,15 @@ final class ChatListNode: ListView {
|
||||
} else {
|
||||
itemIds.append(itemId)
|
||||
}
|
||||
reorderPinnedItemIds(transaction: transaction, itemIds: itemIds)
|
||||
//transaction.setPinnedItemIds(itemIds)
|
||||
return reorderPinnedItemIds(transaction: transaction, itemIds: itemIds)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single(false)
|
||||
}
|
||||
self.didEndScrolling = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
|
||||
@ -38,10 +38,10 @@ class ChatListRecentPeersListItem: ListViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ChatListRecentPeersListItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let layout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ChatListRecentPeersListItemNode {
|
||||
let layout = nodeValue.asyncLayout()
|
||||
async {
|
||||
let (nodeLayout, apply) = layout(self, params, nextItem != nil)
|
||||
Queue.mainQueue().async {
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -36,18 +36,20 @@ class ChatListSearchItem: ListViewItem {
|
||||
node.insets = layout.insets
|
||||
|
||||
node.activate = self.activate
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
apply(false)
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
apply(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ChatListSearchItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let layout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ChatListSearchItemNode {
|
||||
let layout = nodeValue.asyncLayout()
|
||||
async {
|
||||
var nextIsPinned = false
|
||||
if let nextItem = nextItem as? ChatListItem, nextItem.index.pinningIndex != nil {
|
||||
|
||||
@ -37,19 +37,23 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
|
||||
node.updateTheme(theme: self.theme)
|
||||
node.updateIsHighlighted()
|
||||
node.updateAppearanceTransition(transition: .immediate)
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: node.insets), {
|
||||
(node as? ChatMediaInputMetaSectionItemNode)?.setItem(item: self)
|
||||
(node as? ChatMediaInputMetaSectionItemNode)?.updateTheme(theme: self.theme)
|
||||
})
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: node().insets), {
|
||||
(node() as? ChatMediaInputMetaSectionItemNode)?.setItem(item: self)
|
||||
(node() as? ChatMediaInputMetaSectionItemNode)?.updateTheme(theme: self.theme)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func selected(listView: ListView) {
|
||||
|
||||
@ -32,19 +32,23 @@ final class ChatMediaInputPeerSpecificItem: ListViewItem {
|
||||
node.contentSize = boundingSize
|
||||
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
node.inputNodeInteraction = self.inputNodeInteraction
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
node.updateItem(account: self.account, peer: self.peer, collectionId: self.collectionId, theme: self.theme)
|
||||
node.updateAppearanceTransition(transition: .immediate)
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
node.updateItem(account: self.account, peer: self.peer, collectionId: self.collectionId, theme: self.theme)
|
||||
node.updateAppearanceTransition(transition: .immediate)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), {
|
||||
(node as? ChatMediaInputPeerSpecificItemNode)?.updateItem(account: self.account, peer: self.peer, collectionId: self.collectionId, theme: self.theme)
|
||||
})
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), {
|
||||
(node() as? ChatMediaInputPeerSpecificItemNode)?.updateItem(account: self.account, peer: self.peer, collectionId: self.collectionId, theme: self.theme)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func selected(listView: ListView) {
|
||||
|
||||
@ -29,16 +29,20 @@ final class ChatMediaInputRecentGifsItem: ListViewItem {
|
||||
node.updateTheme(theme: self.theme)
|
||||
node.updateIsHighlighted()
|
||||
node.updateAppearanceTransition(transition: .immediate)
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), {
|
||||
(node as? ChatMediaInputRecentGifsItemNode)?.updateTheme(theme: self.theme)
|
||||
})
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), {
|
||||
(node() as? ChatMediaInputRecentGifsItemNode)?.updateTheme(theme: self.theme)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func selected(listView: ListView) {
|
||||
|
||||
@ -28,16 +28,20 @@ final class ChatMediaInputSettingsItem: ListViewItem {
|
||||
node.inputNodeInteraction = self.inputNodeInteraction
|
||||
node.updateTheme(theme: self.theme)
|
||||
node.updateAppearanceTransition(transition: .immediate)
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), {
|
||||
(node as? ChatMediaInputSettingsItemNode)?.updateTheme(theme: self.theme)
|
||||
})
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), {
|
||||
(node() as? ChatMediaInputSettingsItemNode)?.updateTheme(theme: self.theme)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func selected(listView: ListView) {
|
||||
|
||||
@ -34,19 +34,23 @@ final class ChatMediaInputStickerPackItem: ListViewItem {
|
||||
node.contentSize = boundingSize
|
||||
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
node.inputNodeInteraction = self.inputNodeInteraction
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
node.updateStickerPackItem(account: self.account, item: self.stickerPackItem, collectionId: self.collectionId, theme: self.theme)
|
||||
node.updateAppearanceTransition(transition: .immediate)
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
node.updateStickerPackItem(account: self.account, item: self.stickerPackItem, collectionId: self.collectionId, theme: self.theme)
|
||||
node.updateAppearanceTransition(transition: .immediate)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), {
|
||||
(node as? ChatMediaInputStickerPackItemNode)?.updateStickerPackItem(account: self.account, item: self.stickerPackItem, collectionId: self.collectionId, theme: self.theme)
|
||||
})
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), {
|
||||
(node() as? ChatMediaInputStickerPackItemNode)?.updateStickerPackItem(account: self.account, item: self.stickerPackItem, collectionId: self.collectionId, theme: self.theme)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func selected(listView: ListView) {
|
||||
|
||||
@ -29,16 +29,20 @@ final class ChatMediaInputTrendingItem: ListViewItem {
|
||||
node.updateTheme(theme: self.theme)
|
||||
node.updateIsHighlighted()
|
||||
node.updateAppearanceTransition(transition: .immediate)
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), {
|
||||
(node as? ChatMediaInputTrendingItemNode)?.updateTheme(theme: self.theme)
|
||||
})
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), {
|
||||
(node() as? ChatMediaInputTrendingItemNode)?.updateTheme(theme: self.theme)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func selected(listView: ListView) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -352,7 +352,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if let action = media as? TelegramMediaAction {
|
||||
} else if let _ = media as? TelegramMediaAction {
|
||||
viewClassName = ChatMessageBubbleItemNode.self
|
||||
} else if let _ = media as? TelegramMediaExpiredContent {
|
||||
viewClassName = ChatMessageBubbleItemNode.self
|
||||
@ -373,9 +373,11 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
}
|
||||
}
|
||||
if Thread.isMainThread {
|
||||
async {
|
||||
@ -417,12 +419,12 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
||||
return (mergedTop, mergedBottom, dateAtBottom)
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ChatMessageItemView {
|
||||
Queue.mainQueue().async {
|
||||
node.setupItem(self)
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ChatMessageItemView {
|
||||
nodeValue.setupItem(self)
|
||||
|
||||
let nodeLayout = node.asyncLayout()
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (top, bottom, dateAtBottom) = self.mergedWithItems(top: previousItem, bottom: nextItem)
|
||||
@ -431,8 +433,10 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, {
|
||||
apply(animation)
|
||||
node.updateSelectionState(animated: false)
|
||||
node.updateHighlightedState(animated: false)
|
||||
if let nodeValue = node() as? ChatMessageItemView {
|
||||
nodeValue.updateSelectionState(animated: false)
|
||||
nodeValue.updateHighlightedState(animated: false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,16 +24,18 @@ class ChatUnreadItem: ListViewItem {
|
||||
async {
|
||||
let node = ChatUnreadItemNode()
|
||||
node.layoutForParams(params, item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ChatUnreadItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let nodeLayout = node.asyncLayout()
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ChatUnreadItemNode {
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let dateAtBottom = !chatItemsHaveCommonDateHeader(self, nextItem)
|
||||
@ -45,9 +47,9 @@ class ChatUnreadItem: ListViewItem {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,6 +49,10 @@ public final class DeviceAccess {
|
||||
return self.contactsPromise.get()
|
||||
}
|
||||
|
||||
public static func isMicrophoneAccessAuthorized() -> Bool? {
|
||||
return AVAudioSession.sharedInstance().recordPermission() == .granted
|
||||
}
|
||||
|
||||
public static func authorizeAccess(to subject: DeviceAccessSubject, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void, openSettings: @escaping () -> Void, displayNotificatoinFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void) {
|
||||
switch subject {
|
||||
case .camera:
|
||||
|
||||
@ -31,9 +31,11 @@ final class CommandChatInputPanelItem: ListViewItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
}
|
||||
}
|
||||
if Thread.isMainThread {
|
||||
async {
|
||||
@ -44,10 +46,10 @@ final class CommandChatInputPanelItem: ListViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? CommandChatInputPanelItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let nodeLayout = node.asyncLayout()
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? CommandChatInputPanelItemNode {
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (top, bottom) = (previousItem != nil, nextItem != nil)
|
||||
@ -59,9 +61,9 @@ final class CommandChatInputPanelItem: ListViewItem {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,16 +24,18 @@ class ContactListActionItem: ListViewItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ContactListActionItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ContactListActionItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params)
|
||||
|
||||
@ -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)
|
||||
@ -207,19 +204,21 @@ class ContactsPeerItem: ListViewItem {
|
||||
node.contentSize = nodeLayout.contentSize
|
||||
node.insets = nodeLayout.insets
|
||||
|
||||
completion(node, {
|
||||
let (signal, apply) = nodeApply()
|
||||
return (signal, {
|
||||
apply(false)
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
let (signal, apply) = nodeApply()
|
||||
return (signal, {
|
||||
apply(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ContactsPeerItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let layout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ContactsPeerItemNode {
|
||||
let layout = nodeValue.asyncLayout()
|
||||
async {
|
||||
let (first, last, firstWithHeader) = ContactsPeerItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
let (nodeLayout, apply) = layout(self, params, first, last, firstWithHeader)
|
||||
|
||||
@ -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)
|
||||
@ -165,7 +175,7 @@ private struct CreatePasswordControllerState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private func createPasswordControllerEntries(presentationData: PresentationData, state: CreatePasswordControllerState) -> [CreatePasswordEntry] {
|
||||
private func createPasswordControllerEntries(presentationData: PresentationData, context: CreatePasswordContext, state: CreatePasswordControllerState) -> [CreatePasswordEntry] {
|
||||
var entries: [CreatePasswordEntry] = []
|
||||
|
||||
switch state.state {
|
||||
@ -173,13 +183,18 @@ private func createPasswordControllerEntries(presentationData: PresentationData,
|
||||
entries.append(.passwordHeader(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordSection))
|
||||
entries.append(.password(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordPlaceholder, state.passwordText))
|
||||
entries.append(.passwordConfirmation(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordConfirmationPlaceholder, state.passwordConfirmationText))
|
||||
entries.append(.passwordInfo(presentationData.theme, presentationData.strings.FastTwoStepSetup_PasswordHelp))
|
||||
|
||||
if case .paymentInfo = context {
|
||||
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))
|
||||
@ -192,12 +207,18 @@ private func createPasswordControllerEntries(presentationData: PresentationData,
|
||||
return entries
|
||||
}
|
||||
|
||||
enum CreatePasswordContext {
|
||||
case account
|
||||
case secureId
|
||||
case paymentInfo
|
||||
}
|
||||
|
||||
enum CreatePasswordState: Equatable {
|
||||
case setup(currentPassword: String?)
|
||||
case pendingVerification(emailPattern: String)
|
||||
}
|
||||
|
||||
func createPasswordController(account: Account, state: CreatePasswordState, completion: @escaping (String, String, Bool) -> Void, updatePasswordEmailConfirmation: @escaping (String?) -> Void, processPasswordEmailConfirmation: Bool = true) -> ViewController {
|
||||
func createPasswordController(account: Account, context: CreatePasswordContext, state: CreatePasswordState, completion: @escaping (String, String, Bool) -> Void, updatePasswordEmailConfirmation: @escaping (String?) -> Void, processPasswordEmailConfirmation: Bool = true) -> ViewController {
|
||||
let statePromise = ValuePromise(CreatePasswordControllerState(state: state), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: CreatePasswordControllerState(state: state))
|
||||
let updateState: ((CreatePasswordControllerState) -> CreatePasswordControllerState) -> Void = { f in
|
||||
@ -212,59 +233,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 +378,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
|
||||
@ -368,7 +398,7 @@ func createPasswordController(account: Account, state: CreatePasswordState, comp
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: createPasswordControllerEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: CreatePasswordEntryTag.password, emptyStateItem: nil, animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: createPasswordControllerEntries(presentationData: presentationData, context: context, state: state), style: .blocks, focusItemTag: CreatePasswordEntryTag.password, emptyStateItem: nil, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
@ -402,6 +432,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
|
||||
|
||||
@ -24,6 +24,7 @@ private enum DebugControllerSection: Int32 {
|
||||
case payments
|
||||
case logging
|
||||
case experiments
|
||||
case info
|
||||
}
|
||||
|
||||
private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
@ -38,6 +39,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case keepChatNavigationStack(PresentationTheme, Bool)
|
||||
case clearTips(PresentationTheme)
|
||||
case reimport(PresentationTheme)
|
||||
case versionInfo(PresentationTheme)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
@ -53,6 +55,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .clearTips, .reimport:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .versionInfo:
|
||||
return DebugControllerSection.info.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,77 +84,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return 9
|
||||
case .reimport:
|
||||
return 10
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: DebugControllerEntry, rhs: DebugControllerEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .sendLogs(lhsTheme):
|
||||
if case let .sendLogs(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .sendOneLog(lhsTheme):
|
||||
if case let .sendOneLog(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .accounts(lhsTheme):
|
||||
if case let .accounts(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .clearPaymentData(lhsTheme):
|
||||
if case let .clearPaymentData(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .logToFile(lhsTheme, lhsValue):
|
||||
if case let .logToFile(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .logToConsole(lhsTheme, lhsValue):
|
||||
if case let .logToConsole(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .redactSensitiveData(lhsTheme, lhsValue):
|
||||
if case let .redactSensitiveData(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .enableRaiseToSpeak(lhsTheme, lhsValue):
|
||||
if case let .enableRaiseToSpeak(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .keepChatNavigationStack(lhsTheme, lhsValue):
|
||||
if case let .keepChatNavigationStack(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .clearTips(lhsTheme):
|
||||
if case let .clearTips(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .reimport(lhsTheme):
|
||||
if case let .reimport(rhsTheme) = rhs, lhsTheme === rhsTheme {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .versionInfo:
|
||||
return 11
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,6 +198,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
exit(0)
|
||||
}
|
||||
})
|
||||
case let .versionInfo(theme):
|
||||
let bundle = Bundle.main
|
||||
let bundleId = bundle.bundleIdentifier ?? ""
|
||||
let bundleVersion = bundle.infoDictionary?["CFBundleShortVersionString"] ?? ""
|
||||
let bundleBuild = bundle.infoDictionary?[kCFBundleVersionKey as String] ?? ""
|
||||
return ItemListTextItem(theme: theme, text: .plain("\(bundleId)\n\(bundleVersion) (\(bundleBuild))"), sectionId: self.section)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -285,6 +226,7 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
|
||||
if hasLegacyAppData {
|
||||
entries.append(.reimport(presentationData.theme))
|
||||
}
|
||||
entries.append(.versionInfo(presentationData.theme))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
@ -390,6 +390,40 @@ public extension DeviceContactExtendedData {
|
||||
let basicData = DeviceContactBasicData(firstName: contact.givenName, lastName: contact.familyName, phoneNumbers: phoneNumbers)
|
||||
self.init(basicData: basicData, middleName: contact.middleName, prefix: contact.namePrefix, suffix: contact.nameSuffix, organization: contact.organizationName, jobTitle: contact.jobTitle, department: contact.departmentName, emailAddresses: emailAddresses, urls: urls, addresses: addresses, birthdayDate: birthdayDate, socialProfiles: socialProfiles, instantMessagingProfiles: instantMessagingProfiles)
|
||||
}
|
||||
|
||||
public var isPrimitive: Bool {
|
||||
if self.basicData.phoneNumbers.count > 1 {
|
||||
return false
|
||||
}
|
||||
if !self.organization.isEmpty {
|
||||
return false
|
||||
}
|
||||
if !self.jobTitle.isEmpty {
|
||||
return false
|
||||
}
|
||||
if !self.department.isEmpty {
|
||||
return false
|
||||
}
|
||||
if !self.emailAddresses.isEmpty {
|
||||
return false
|
||||
}
|
||||
if !self.urls.isEmpty {
|
||||
return false
|
||||
}
|
||||
if !self.addresses.isEmpty {
|
||||
return false
|
||||
}
|
||||
if self.birthdayDate != nil {
|
||||
return false
|
||||
}
|
||||
if !self.socialProfiles.isEmpty {
|
||||
return false
|
||||
}
|
||||
if !self.instantMessagingProfiles.isEmpty {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension DeviceContactExtendedData {
|
||||
|
||||
@ -288,7 +288,7 @@ final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate {
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: nodeHeight - separatorHeight), size: CGSize(width: width, height: separatorHeight)))
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: nodeHeight)))
|
||||
|
||||
if !fabs(previousContentHeight - contentHeight).isLess(than: CGFloat.ulpOfOne) {
|
||||
if !abs(previousContentHeight - contentHeight).isLess(than: CGFloat.ulpOfOne) {
|
||||
let contentOffset = CGPoint(x: 0, y: max(0, contentHeight - nodeHeight))
|
||||
if case .immediate = transition {
|
||||
self.scrollNode.view.contentOffset = contentOffset
|
||||
|
||||
@ -31,9 +31,11 @@ final class EmojisChatInputPanelItem: ListViewItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
}
|
||||
}
|
||||
if Thread.isMainThread {
|
||||
async {
|
||||
@ -44,10 +46,10 @@ final class EmojisChatInputPanelItem: ListViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? EmojisChatInputPanelItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let nodeLayout = node.asyncLayout()
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? EmojisChatInputPanelItemNode {
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (top, bottom) = (previousItem != nil, nextItem != nil)
|
||||
@ -59,9 +61,9 @@ final class EmojisChatInputPanelItem: ListViewItem {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -95,10 +95,11 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
||||
} else {
|
||||
let data = postbox.mediaBox.resourceData(resourceReference.resource, pathExtension: nil, option: .complete(waitUntilFetchStatus: false))
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
let readingOffset = context.readingOffset
|
||||
let disposable = data.start(next: { next in
|
||||
if next.complete {
|
||||
let readCount = max(0, min(next.size - context.readingOffset, Int(bufferSize)))
|
||||
let range = context.readingOffset ..< (context.readingOffset + readCount)
|
||||
let readCount = max(0, min(next.size - readingOffset, Int(bufferSize)))
|
||||
let range = readingOffset ..< (readingOffset + readCount)
|
||||
|
||||
let fd = open(next.path, O_RDONLY, S_IRUSR)
|
||||
if fd >= 0 {
|
||||
|
||||
@ -103,7 +103,11 @@ final class FormControllerDetailActionItemNode: FormBlockItemNode<FormController
|
||||
})
|
||||
}
|
||||
|
||||
override func selected() {
|
||||
func activate() {
|
||||
self.item?.activated()
|
||||
}
|
||||
|
||||
override func selected() {
|
||||
activate()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -5,6 +5,11 @@ import Display
|
||||
private let textFont = Font.regular(17.0)
|
||||
private let errorFont = Font.regular(13.0)
|
||||
|
||||
enum FormControllerTextInputItemColor {
|
||||
case primary
|
||||
case error
|
||||
}
|
||||
|
||||
enum FormControllerTextInputItemType: Equatable {
|
||||
case regular(capitalization: UITextAutocapitalizationType, autocorrection: Bool)
|
||||
case latin(capitalization: UITextAutocapitalizationType)
|
||||
@ -16,16 +21,20 @@ final class FormControllerTextInputItem: FormControllerItem {
|
||||
let title: String
|
||||
let text: String
|
||||
let placeholder: String
|
||||
let color: FormControllerTextInputItemColor
|
||||
let type: FormControllerTextInputItemType
|
||||
let returnKeyType: UIReturnKeyType
|
||||
let error: String?
|
||||
let textUpdated: (String) -> Void
|
||||
let returnPressed: () -> Void
|
||||
|
||||
init(title: String, text: String, placeholder: String, type: FormControllerTextInputItemType, error: String? = nil, textUpdated: @escaping (String) -> Void, returnPressed: @escaping () -> Void) {
|
||||
init(title: String, text: String, placeholder: String, color: FormControllerTextInputItemColor = .primary, type: FormControllerTextInputItemType, returnKeyType: UIReturnKeyType = .next, error: String? = nil, textUpdated: @escaping (String) -> Void, returnPressed: @escaping () -> Void) {
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.placeholder = placeholder
|
||||
self.color = color
|
||||
self.type = type
|
||||
self.returnKeyType = returnKeyType
|
||||
self.error = error
|
||||
self.textUpdated = textUpdated
|
||||
self.returnPressed = returnPressed
|
||||
@ -64,7 +73,6 @@ final class FormControllerTextInputItemNode: FormBlockItemNode<FormControllerTex
|
||||
|
||||
self.textField = TextFieldNode()
|
||||
self.textField.textField.font = textFont
|
||||
self.textField.textField.returnKeyType = .next
|
||||
|
||||
super.init(selectable: false, topSeparatorInset: .regular)
|
||||
|
||||
@ -91,7 +99,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
|
||||
@ -125,12 +133,20 @@ final class FormControllerTextInputItemNode: FormBlockItemNode<FormControllerTex
|
||||
if self.textField.textField.autocorrectionType != autocorrectionType {
|
||||
self.textField.textField.autocorrectionType = autocorrectionType
|
||||
}
|
||||
if self.textField.textField.returnKeyType != item.returnKeyType {
|
||||
self.textField.textField.returnKeyType = item.returnKeyType
|
||||
}
|
||||
|
||||
let attributedPlaceholder = NSAttributedString(string: item.placeholder, font: textFont, textColor: theme.list.itemPlaceholderTextColor)
|
||||
if !(self.textField.textField.attributedPlaceholder?.isEqual(to: attributedPlaceholder) ?? false) {
|
||||
self.textField.textField.attributedPlaceholder = attributedPlaceholder
|
||||
}
|
||||
self.textField.textField.textColor = theme.list.itemPrimaryTextColor
|
||||
switch item.color {
|
||||
case .primary:
|
||||
self.textField.textField.textColor = theme.list.itemPrimaryTextColor
|
||||
case .error:
|
||||
self.textField.textField.textColor = theme.list.itemDestructiveColor
|
||||
}
|
||||
|
||||
if self.textField.textField.text != item.text {
|
||||
self.textField.textField.text = item.text
|
||||
|
||||
@ -249,7 +249,7 @@ final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
if scrollView.isDragging || scrollView.isDecelerating {
|
||||
let position = scrollView.contentInset.left + scrollView.contentOffset.x
|
||||
let index = max(0, min(self.items.count, Int(round(position / (itemBaseSize.width + spacing)))))
|
||||
let index = max(0, min(self.items.count - 1, Int(round(position / (itemBaseSize.width + spacing)))))
|
||||
|
||||
if let (currentCentralIndex, _) = self.centralIndexAndProgress, currentCentralIndex != index {
|
||||
self.centralIndexAndProgress = (index, nil)
|
||||
|
||||
@ -36,16 +36,18 @@ final class GroupStickerPackCurrentItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? GroupStickerPackCurrentItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? GroupStickerPackCurrentItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
var animated = true
|
||||
if case .None = animation {
|
||||
|
||||
@ -29,9 +29,11 @@ final class HashtagChatInputPanelItem: ListViewItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
}
|
||||
}
|
||||
if Thread.isMainThread {
|
||||
async {
|
||||
@ -42,10 +44,10 @@ final class HashtagChatInputPanelItem: ListViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? HashtagChatInputPanelItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let nodeLayout = node.asyncLayout()
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? HashtagChatInputPanelItemNode {
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (top, bottom) = (previousItem != nil, nextItem != nil)
|
||||
@ -57,9 +59,9 @@ final class HashtagChatInputPanelItem: ListViewItem {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,9 +30,11 @@ final class HorizontalListContextResultsChatInputPanelItem: ListViewItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
}
|
||||
}
|
||||
if Thread.isMainThread {
|
||||
async {
|
||||
@ -43,10 +45,10 @@ final class HorizontalListContextResultsChatInputPanelItem: ListViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? HorizontalListContextResultsChatInputPanelItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let nodeLayout = node.asyncLayout()
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? HorizontalListContextResultsChatInputPanelItemNode {
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (top, bottom) = (previousItem != nil, nextItem != nil)
|
||||
@ -58,9 +60,9 @@ final class HorizontalListContextResultsChatInputPanelItem: ListViewItem {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -45,19 +45,21 @@ final class HorizontalPeerItem: ListViewItem {
|
||||
node.insets = nodeLayout.insets
|
||||
node.contentSize = nodeLayout.contentSize
|
||||
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
apply(false)
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, {
|
||||
apply(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
assert(node is HorizontalPeerItemNode)
|
||||
if let node = node as? HorizontalPeerItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let layout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
assert(node() is HorizontalPeerItemNode)
|
||||
if let nodeValue = node() as? HorizontalPeerItemNode {
|
||||
let layout = nodeValue.asyncLayout()
|
||||
async {
|
||||
let (nodeLayout, apply) = layout(self, params)
|
||||
Queue.mainQueue().async {
|
||||
|
||||
@ -44,16 +44,18 @@ class ItemListActionItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListActionItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListActionItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -26,20 +26,22 @@ class ItemListActivityTextItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
guard let node = node as? ItemListActivityTextItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
guard let nodeValue = node() as? ItemListActivityTextItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -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 {
|
||||
@ -210,14 +212,14 @@ class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListAvatarAndNameInfoItemNode {
|
||||
var animated = true
|
||||
if case .None = animation {
|
||||
animated = false
|
||||
}
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListAvatarAndNameInfoItemNode {
|
||||
var animated = true
|
||||
if case .None = animation {
|
||||
animated = false
|
||||
}
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,16 +42,18 @@ class ItemListCheckboxItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListCheckboxItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListCheckboxItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -166,6 +166,7 @@ class ItemListControllerNode<Entry: ItemListNodeEntry>: ViewControllerTracingNod
|
||||
reorderEntry(fromIndex, toIndex, mergedEntries)
|
||||
}
|
||||
}
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
self.listNode.visibleBottomContentOffsetChanged = { [weak self] offset in
|
||||
|
||||
@ -59,16 +59,18 @@ class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListDisclosureItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListDisclosureItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -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,28 @@ 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 {
|
||||
if let recognizer = self.recognizer, otherGestureRecognizer == recognizer {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@ -34,16 +34,18 @@ class ItemListMultilineInputItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListMultilineInputItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListMultilineInputItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
@ -146,7 +148,7 @@ class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNodeDelega
|
||||
limitTextString = NSAttributedString(string: "\(max(0, maxLength - item.text.count))", font: titleFont, textColor: item.theme.list.itemSecondaryTextColor)
|
||||
}
|
||||
|
||||
let (limitTextLayout, limitTextApply) = makeLimitTextLayout(TextNodeLayoutArguments(attributedString: limitTextString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0), alignment: .left, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (limitTextLayout, limitTextApply) = makeLimitTextLayout(TextNodeLayoutArguments(attributedString: limitTextString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var rightInset: CGFloat = params.rightInset
|
||||
if !limitTextLayout.size.width.isZero {
|
||||
@ -191,7 +193,7 @@ class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNodeDelega
|
||||
|
||||
let _ = textApply()
|
||||
if let currentText = strongSelf.textNode.attributedText {
|
||||
if !currentText.isEqual(to: attributedText) {
|
||||
if currentText.string != attributedText.string {
|
||||
strongSelf.textNode.attributedText = attributedText
|
||||
}
|
||||
} else {
|
||||
@ -278,12 +280,15 @@ class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNodeDelega
|
||||
|
||||
func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
||||
if let item = self.item {
|
||||
if let text = self.textNode.attributedText?.string {
|
||||
var updatedText = text
|
||||
if let text = self.textNode.attributedText {
|
||||
var updatedText = text.string
|
||||
if let maxLength = item.maxLength, updatedText.count > maxLength {
|
||||
updatedText = String(updatedText[..<updatedText.index(updatedText.startIndex, offsetBy: maxLength)])
|
||||
}
|
||||
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor)
|
||||
let updatedAttributedText = NSAttributedString(string: updatedText, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor)
|
||||
if text.string != updatedAttributedText.string {
|
||||
self.textNode.attributedText = updatedAttributedText
|
||||
}
|
||||
item.textUpdated(updatedText)
|
||||
} else {
|
||||
item.textUpdated("")
|
||||
@ -293,7 +298,7 @@ class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNodeDelega
|
||||
|
||||
func editableTextNodeShouldPaste(_ editableTextNode: ASEditableTextNode) -> Bool {
|
||||
if let item = self.item {
|
||||
var text: String? = UIPasteboard.general.string
|
||||
let text: String? = UIPasteboard.general.string
|
||||
if let text = text {
|
||||
if let maxLength = item.maxLength {
|
||||
let string = self.textNode.attributedText?.string ?? ""
|
||||
|
||||
@ -50,16 +50,18 @@ class ItemListMultilineTextItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListMultilineTextItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListMultilineTextItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -28,16 +28,18 @@ class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListPeerActionItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListPeerActionItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
var animated = true
|
||||
if case .None = animation {
|
||||
|
||||
@ -108,16 +108,18 @@ final class ItemListPeerItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (node.avatarNode.ready, { apply(false) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (node.avatarNode.ready, { apply(false) })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListPeerItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListPeerItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
var animated = true
|
||||
if case .None = animation {
|
||||
|
||||
@ -63,16 +63,18 @@ final class ItemListRecentSessionItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListRecentSessionItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListRecentSessionItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
var animated = true
|
||||
if case .None = animation {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,16 +33,18 @@ class ItemListSecretChatKeyItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListSecretChatKeyItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListSecretChatKeyItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -24,20 +24,22 @@ class ItemListSectionHeaderItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
guard let node = node as? ItemListSectionHeaderItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
guard let nodeValue = node() as? ItemListSectionHeaderItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -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
|
||||
@ -48,16 +50,19 @@ class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListSingleLineInputItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListSingleLineInputItemNode {
|
||||
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
@ -244,6 +249,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 {
|
||||
|
||||
@ -77,16 +77,18 @@ final class ItemListStickerPackItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListStickerPackItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListStickerPackItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
var animated = true
|
||||
if case .None = animation {
|
||||
|
||||
@ -39,16 +39,18 @@ class ItemListSwitchItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListSwitchItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListSwitchItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -36,20 +36,22 @@ class ItemListTextItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
guard let node = node as? ItemListTextItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
guard let nodeValue = node() as? ItemListTextItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -49,16 +49,18 @@ final class ItemListTextWithLabelItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListTextWithLabelItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListTextWithLabelItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
@ -53,16 +53,18 @@ final class ItemListWebsiteItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ItemListWebsiteItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ItemListWebsiteItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
var animated = true
|
||||
if case .None = animation {
|
||||
|
||||
@ -317,14 +317,14 @@ public class LegacyController: ViewController {
|
||||
return self.sizeClass.signal()!
|
||||
}
|
||||
|
||||
public init(presentation: LegacyControllerPresentation, theme: PresentationTheme?, presentationData: PresentationData? = nil, initialLayout: ContainerViewLayout? = nil) {
|
||||
public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) {
|
||||
self.sizeClass.set(SSignal.single(UIUserInterfaceSizeClass.compact.rawValue as NSNumber))
|
||||
self.presentation = presentation
|
||||
self.validLayout = initialLayout
|
||||
|
||||
let navigationBarPresentationData: NavigationBarPresentationData?
|
||||
if let presentationData = presentationData, case .navigation = presentation {
|
||||
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData)
|
||||
if let theme = theme, let strings = strings, case .navigation = presentation {
|
||||
navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(presentationStrings: strings))
|
||||
} else {
|
||||
navigationBarPresentationData = nil
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ final class InstantVideoController: LegacyController {
|
||||
|
||||
private var dismissedVideo = false
|
||||
|
||||
override init(presentation: LegacyControllerPresentation, theme: PresentationTheme?, presentationData: PresentationData? = nil, initialLayout: ContainerViewLayout? = nil) {
|
||||
override init(presentation: LegacyControllerPresentation, theme: PresentationTheme?, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) {
|
||||
self.audioStatus = InstantVideoControllerRecordingStatus(micLevel: self.micLevelValue.get())
|
||||
|
||||
super.init(presentation: presentation, theme: theme, initialLayout: initialLayout)
|
||||
|
||||
@ -126,7 +126,7 @@ func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, acco
|
||||
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
||||
let legacyController = LegacyController(presentation: modal ? .modal(animateIn: true) : .navigation, theme: presentationData.theme, presentationData: presentationData)
|
||||
let legacyController = LegacyController(presentation: modal ? .modal(animateIn: true) : .navigation, theme: presentationData.theme, strings: presentationData.strings)
|
||||
|
||||
let legacyMessage = makeLegacyMessage(message)
|
||||
|
||||
|
||||
@ -83,32 +83,67 @@ func presentLegacySecureIdAttachmentMenu(account: Account, present: @escaping (V
|
||||
}
|
||||
}
|
||||
|
||||
private enum AttachmentItem {
|
||||
case image(UIImage)
|
||||
case iCloud(URL)
|
||||
}
|
||||
|
||||
private func processedLegacySecureIdAttachmentItems(postbox: Postbox, signal: SSignal) -> Signal<[TelegramMediaResource], NoError> {
|
||||
let wrappedSignal = Signal<[TelegramMediaResource], NoError> { subscriber in
|
||||
let nativeSignal = Signal<AttachmentItem?, NoError> { subscriber in
|
||||
let disposable = signal.start(next: { next in
|
||||
if let dict = next as? [String: Any], let image = dict["image"] as? UIImage {
|
||||
var randomId: Int64 = 0
|
||||
arc4random_buf(&randomId, 8)
|
||||
let tempFilePath = NSTemporaryDirectory() + "\(randomId).jpeg"
|
||||
let scaledSize = image.size.aspectFitted(CGSize(width: 2048.0, height: 2048.0))
|
||||
if let scaledImage = TGScaleImageToPixelSize(image, scaledSize), let scaledImageData = compressImageToJPEG(scaledImage, quality: 0.84) {
|
||||
let _ = try? scaledImageData.write(to: URL(fileURLWithPath: tempFilePath))
|
||||
let resource = LocalFileReferenceMediaResource(localFilePath: tempFilePath, randomId: randomId)
|
||||
subscriber.putNext([resource])
|
||||
} else {
|
||||
subscriber.putNext([])
|
||||
}
|
||||
} else {
|
||||
subscriber.putNext([])
|
||||
subscriber.putNext(.image(image))
|
||||
} else if let next = next as? URL {
|
||||
subscriber.putNext(.iCloud(next))
|
||||
}
|
||||
}, completed: {
|
||||
}, error: nil, completed: {
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
return ActionDisposable {
|
||||
disposable?.dispose()
|
||||
}
|
||||
}
|
||||
let collectedItems: Signal<[TelegramMediaResource], NoError> = wrappedSignal
|
||||
let collectedSignal = nativeSignal
|
||||
|> mapToSignal { value -> Signal<UIImage?, NoError> in
|
||||
guard let value = value else {
|
||||
return .single(nil)
|
||||
}
|
||||
switch value {
|
||||
case let .image(image):
|
||||
return .single(image)
|
||||
case let .iCloud(url):
|
||||
return Signal<UIImage?, NoError> { subscriber in
|
||||
let disposable = TGPassportICloud.fetchFile(with: url).start(next: { next in
|
||||
if let url = next as? URL {
|
||||
subscriber.putNext(UIImage(contentsOfFile: url.path))
|
||||
}
|
||||
}, completed: {
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
return ActionDisposable {
|
||||
disposable?.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|> map { image -> [TelegramMediaResource] in
|
||||
guard let image = image else {
|
||||
return []
|
||||
}
|
||||
var randomId: Int64 = 0
|
||||
arc4random_buf(&randomId, 8)
|
||||
let tempFilePath = NSTemporaryDirectory() + "\(randomId).jpeg"
|
||||
let scaledSize = image.size.aspectFitted(CGSize(width: 2048.0, height: 2048.0))
|
||||
if let scaledImage = TGScaleImageToPixelSize(image, scaledSize), let scaledImageData = compressImageToJPEG(scaledImage, quality: 0.84) {
|
||||
let _ = try? scaledImageData.write(to: URL(fileURLWithPath: tempFilePath))
|
||||
let resource = LocalFileReferenceMediaResource(localFilePath: tempFilePath, randomId: randomId)
|
||||
return [resource]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
let collectedItems: Signal<[TelegramMediaResource], NoError> = collectedSignal
|
||||
|> reduceLeft(value: [] as [TelegramMediaResource], f: { (list: [TelegramMediaResource], rest: [TelegramMediaResource]) -> [TelegramMediaResource] in
|
||||
var list = list
|
||||
list.append(contentsOf: rest)
|
||||
@ -192,7 +227,7 @@ private struct IsoCountryInfo {
|
||||
var continent: String
|
||||
}
|
||||
|
||||
private func countryCodeAlpha3ToAlpha2(_ code: String) -> String? {
|
||||
func countryCodeAlpha3ToAlpha2(_ code: String) -> String? {
|
||||
for country in IsoCountries.allCountries {
|
||||
if country.alpha3 == code {
|
||||
return country.alpha2
|
||||
|
||||
34
TelegramUI/LegacySecureIdScanController.swift
Normal file
34
TelegramUI/LegacySecureIdScanController.swift
Normal file
@ -0,0 +1,34 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import LegacyComponents
|
||||
|
||||
func legacySecureIdScanController(theme: PresentationTheme, strings: PresentationStrings, finished: @escaping (SecureIdRecognizedDocumentData?) -> Void) -> ViewController {
|
||||
let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: theme, strings: strings)
|
||||
let theme = TGPassportScanControllerTheme(backgroundColor: theme.list.plainBackgroundColor, textColor: theme.list.itemPrimaryTextColor)
|
||||
let controller = TGPassportScanController(context: legacyController.context, theme: theme)!
|
||||
controller.finishedWithMRZ = { value in
|
||||
if let value = value {
|
||||
var issuingCountry: String? = nil
|
||||
if let issuingCountryValue = value.issuingCountry {
|
||||
issuingCountry = countryCodeAlpha3ToAlpha2(issuingCountryValue)
|
||||
}
|
||||
var nationality: String? = nil
|
||||
if let nationalityValue = value.nationality {
|
||||
nationality = countryCodeAlpha3ToAlpha2(nationalityValue)
|
||||
}
|
||||
finished(SecureIdRecognizedDocumentData(documentType: value.documentType, documentSubtype: value.documentSubtype, issuingCountry: issuingCountry, nationality: nationality, lastName: value.lastName.capitalized, firstName: value.firstName.capitalized, documentNumber: value.documentNumber, birthDate: value.birthDate, gender: value.gender, expiryDate: value.expiryDate))
|
||||
} else {
|
||||
finished(nil)
|
||||
}
|
||||
}
|
||||
|
||||
let navigationController = TGNavigationController(controllers: [controller])!
|
||||
controller.navigation_setDismiss({ [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
}, rootController: nil)
|
||||
|
||||
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
return legacyController
|
||||
}
|
||||
@ -22,9 +22,11 @@ final class ListMessageHoleItem: ListViewItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
}
|
||||
}
|
||||
if Thread.isMainThread {
|
||||
async {
|
||||
@ -35,15 +37,15 @@ final class ListMessageHoleItem: ListViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ListMessageHoleItemNode {
|
||||
Queue.mainQueue().async {
|
||||
node.updateSelectionState(animated: false)
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ListMessageHoleItemNode {
|
||||
nodeValue.updateSelectionState(animated: false)
|
||||
|
||||
let nodeLayout = node.asyncLayout()
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (top, bottom, dateAtBottom) = (false, false, false) //self.mergedWithItems(top: previousItem, bottom: nextItem)
|
||||
let (top, bottom, dateAtBottom) = (false, false, false)
|
||||
|
||||
let (layout, apply) = nodeLayout(self, params, top, bottom, dateAtBottom)
|
||||
Queue.mainQueue().async {
|
||||
@ -52,9 +54,9 @@ final class ListMessageHoleItem: ListViewItem {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,9 +57,11 @@ final class ListMessageItem: ListViewItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
}
|
||||
}
|
||||
if Thread.isMainThread {
|
||||
async {
|
||||
@ -70,14 +72,14 @@ final class ListMessageItem: ListViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ListMessageNode {
|
||||
Queue.mainQueue().async {
|
||||
node.setupItem(self)
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ListMessageNode {
|
||||
nodeValue.setupItem(self)
|
||||
|
||||
node.updateSelectionState(animated: false)
|
||||
nodeValue.updateSelectionState(animated: false)
|
||||
|
||||
let nodeLayout = node.asyncLayout()
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (top, bottom, dateAtBottom) = (previousItem != nil, nextItem != nil, self.getDateAtBottom(top: previousItem, bottom: nextItem))
|
||||
@ -89,9 +91,9 @@ final class ListMessageItem: ListViewItem {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,16 +34,18 @@ class MediaInputPaneTrendingItem: ListViewItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? MediaInputPaneTrendingItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? MediaInputPaneTrendingItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params)
|
||||
|
||||
@ -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)))
|
||||
|
||||
@ -33,9 +33,11 @@ final class MentionChatInputPanelItem: ListViewItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
}
|
||||
}
|
||||
if Thread.isMainThread {
|
||||
async {
|
||||
@ -46,10 +48,10 @@ final class MentionChatInputPanelItem: ListViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? MentionChatInputPanelItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let nodeLayout = node.asyncLayout()
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? MentionChatInputPanelItemNode {
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (top, bottom) = (previousItem != nil, nextItem != nil)
|
||||
@ -61,9 +63,9 @@ final class MentionChatInputPanelItem: ListViewItem {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
@ -235,8 +238,8 @@ public func openExternalUrl(account: Account, url: String, presentationData: Pre
|
||||
valid = true
|
||||
}
|
||||
|
||||
if valid && GlobalExperimentalSettings.enablePassport {
|
||||
if let botId = botId, let scope = scope, let publicKey = publicKey {
|
||||
if valid {
|
||||
if let botId = botId, let scope = scope, let publicKey = publicKey, let callbackUrl = callbackUrl {
|
||||
if scope.hasPrefix("{") && scope.hasSuffix("}") {
|
||||
opaquePayload = Data()
|
||||
if opaqueNonce.isEmpty {
|
||||
@ -245,13 +248,14 @@ 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)
|
||||
|
||||
navigationController.view.window?.endEditing(true)
|
||||
(navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root), with: nil)
|
||||
|
||||
(navigationController as? TelegramRootController)?.window?.present(controller, on: .root)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
@ -148,10 +148,16 @@ final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||
self.countryCodeField.textField.textAlignment = .center
|
||||
self.countryCodeField.textField.keyboardType = .numberPad
|
||||
self.countryCodeField.textField.returnKeyType = .next
|
||||
if #available(iOSApplicationExtension 10.0, *) {
|
||||
self.countryCodeField.textField.textContentType = .telephoneNumber
|
||||
}
|
||||
|
||||
self.numberField = TextFieldNode()
|
||||
self.numberField.textField.font = Font.regular(fontSize)
|
||||
self.numberField.textField.keyboardType = .numberPad
|
||||
if #available(iOSApplicationExtension 10.0, *) {
|
||||
self.numberField.textField.textContentType = .telephoneNumber
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
@ -176,7 +182,14 @@ final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||
}
|
||||
|
||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
return self.enableEditing
|
||||
if !self.enableEditing {
|
||||
return false
|
||||
}
|
||||
if range.length == 0, string.count > 1 {
|
||||
self.updateNumber(cleanPhoneNumber(string), tryRestoringInputPosition: false)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
@ -191,7 +204,7 @@ final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||
self.updateNumber(inputText)
|
||||
}
|
||||
|
||||
private func updateNumber(_ inputText: String) {
|
||||
private func updateNumber(_ inputText: String, tryRestoringInputPosition: Bool = true) {
|
||||
let (regionPrefix, text) = self.phoneFormatter.updateText(inputText)
|
||||
var realRegionPrefix: String
|
||||
let numberText: String
|
||||
@ -245,7 +258,7 @@ final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||
restorePosition = restoreIndex
|
||||
}
|
||||
self.numberField.textField.text = numberText
|
||||
if let restorePosition = restorePosition {
|
||||
if tryRestoringInputPosition, let restorePosition = restorePosition {
|
||||
if let startPosition = self.numberField.textField.position(from: self.numberField.textField.beginningOfDocument, offset: restorePosition) {
|
||||
let selectionRange = self.numberField.textField.textRange(from: startPosition, to: startPosition)
|
||||
self.numberField.textField.selectedTextRange = selectionRange
|
||||
|
||||
@ -13,53 +13,6 @@ public enum PresentationCallState: Equatable {
|
||||
case active(Double, Data)
|
||||
case terminating
|
||||
case terminated(CallSessionTerminationReason?)
|
||||
|
||||
public static func ==(lhs: PresentationCallState, rhs: PresentationCallState) -> Bool {
|
||||
switch lhs {
|
||||
case .waiting:
|
||||
if case .waiting = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .ringing:
|
||||
if case .ringing = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .requesting(ringing):
|
||||
if case .requesting(ringing) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .connecting:
|
||||
if case .connecting = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .active(timestamp, keyVisualHash):
|
||||
if case .active(timestamp, keyVisualHash) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .terminating:
|
||||
if case .terminating = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .terminated(lhsReason):
|
||||
if case let .terminated(rhsReason) = rhs, lhsReason == rhsReason {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class PresentationCallToneRenderer {
|
||||
@ -177,6 +130,10 @@ public final class PresentationCall {
|
||||
private let audioSession: ManagedAudioSession
|
||||
private let callSessionManager: CallSessionManager
|
||||
private let callKitIntegration: CallKitIntegration?
|
||||
public var isIntegratedWithCallKit: Bool {
|
||||
return self.callKitIntegration != nil
|
||||
}
|
||||
|
||||
private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
|
||||
|
||||
public let internalId: CallSessionInternalId
|
||||
|
||||
@ -112,10 +112,17 @@ public final class PresentationCallManager {
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
self.ringingStatesDisposable = (combineLatest(callSessionManager.ringingStates(), enableCallKit)
|
||||
|> mapToSignal { ringingStates, enableCallKit -> Signal<([(Peer, CallSessionRingingState, Bool)], Bool), NoError> in
|
||||
let enabledMicrophoneAccess = Signal<Bool, NoError> { subscriber in
|
||||
subscriber.putNext(DeviceAccess.isMicrophoneAccessAuthorized() == true)
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|
||||
self.ringingStatesDisposable = (combineLatest(callSessionManager.ringingStates(), enableCallKit, enabledMicrophoneAccess)
|
||||
|> mapToSignal { ringingStates, enableCallKit, enabledMicrophoneAccess -> Signal<([(Peer, CallSessionRingingState, Bool)], Bool), NoError> in
|
||||
if ringingStates.isEmpty {
|
||||
return .single(([], enableCallKit))
|
||||
return .single(([], enableCallKit && enabledMicrophoneAccess))
|
||||
} else {
|
||||
return postbox.transaction { transaction -> ([(Peer, CallSessionRingingState, Bool)], Bool) in
|
||||
var result: [(Peer, CallSessionRingingState, Bool)] = []
|
||||
@ -124,7 +131,7 @@ public final class PresentationCallManager {
|
||||
result.append((peer, state, transaction.isPeerContact(peerId: state.peerId)))
|
||||
}
|
||||
}
|
||||
return (result, enableCallKit)
|
||||
return (result, enableCallKit && enabledMicrophoneAccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -26,16 +26,18 @@ class ProxySettingsActionItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ProxySettingsActionItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ProxySettingsActionItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
var animated = true
|
||||
if case .None = animation {
|
||||
|
||||
@ -54,16 +54,18 @@ final class ProxySettingsServerItem: ListViewItem, ItemListItem {
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply(false) })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
if let node = node as? ProxySettingsServerItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let makeLayout = node.asyncLayout()
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ProxySettingsServerItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
var animated = true
|
||||
if case .None = animation {
|
||||
|
||||
@ -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,13 +87,15 @@ 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))
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
||||
|
||||
self.title = self.presentationData.strings.Passport_Title
|
||||
@ -90,6 +116,7 @@ final class SecureIdAuthController: ViewController {
|
||||
strongSelf.updateState(animated: true, { state in
|
||||
var state = state
|
||||
state.verificationState = .verified(context.context)
|
||||
state.twoStepEmail = !context.settings.email.isEmpty ? context.settings.email : nil
|
||||
switch state {
|
||||
case var .form(form):
|
||||
form.formData = form.encryptedFormData.flatMap({ decryptedSecureIdForm(context: context.context, form: $0.form) })
|
||||
@ -126,8 +153,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 +218,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 +249,8 @@ final class SecureIdAuthController: ViewController {
|
||||
return state
|
||||
}
|
||||
}
|
||||
}, error: { error in
|
||||
handleError(error, nil, nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -323,8 +384,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 +420,7 @@ final class SecureIdAuthController: ViewController {
|
||||
strongSelf.updateState(animated: !inBackground, { state in
|
||||
var state = state
|
||||
state.verificationState = .verified(context.context)
|
||||
state.twoStepEmail = !context.settings.email.isEmpty ? context.settings.email : nil
|
||||
switch state {
|
||||
case var .form(form):
|
||||
form.formData = form.encryptedFormData.flatMap({ decryptedSecureIdForm(context: context.context, form: $0.form) })
|
||||
@ -451,7 +523,7 @@ final class SecureIdAuthController: ViewController {
|
||||
} else {
|
||||
state = .setup(currentPassword: nil)
|
||||
}
|
||||
let controller = createPasswordController(account: self.account, state: state, completion: { password, hint, hasRecoveryEmail in
|
||||
let controller = createPasswordController(account: self.account, context: .secureId, state: state, completion: { password, hint, hasRecoveryEmail in
|
||||
completionImpl?(password, hint, hasRecoveryEmail)
|
||||
}, updatePasswordEmailConfirmation: { [weak self] pattern in
|
||||
guard let strongSelf = self else {
|
||||
@ -492,6 +564,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:
|
||||
|
||||
@ -174,7 +174,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
||||
headerLayout.apply(true)
|
||||
}
|
||||
|
||||
contentSpacing = max(minContentSpacing, min(contentSpacing, contentRect.height - (headerHeight + contentLayout.height + minContentSpacing - 14.0 - 16.0)))
|
||||
contentSpacing = max(minContentSpacing, min(contentSpacing, contentRect.height - (headerHeight + contentLayout.height + minContentSpacing + 14.0 + 16.0)))
|
||||
|
||||
let boundingHeight = headerHeight + contentLayout.height + contentSpacing
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -435,28 +435,32 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
||||
switch value.value {
|
||||
case .passport:
|
||||
hasValueType = .passport
|
||||
case .internalPassport:
|
||||
hasValueType = .internalPassport
|
||||
case .idCard:
|
||||
hasValueType = .idCard
|
||||
case .driversLicense:
|
||||
hasValueType = .driversLicense
|
||||
case .internalPassport:
|
||||
hasValueType = .internalPassport
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
case let .oneOf(types):
|
||||
for type in types {
|
||||
outer: for type in types {
|
||||
if let value = findValue(formData.values, key: type.valueKey)?.1 {
|
||||
switch value.value {
|
||||
case .passport:
|
||||
hasValueType = .passport
|
||||
case .internalPassport:
|
||||
hasValueType = .internalPassport
|
||||
break outer
|
||||
case .idCard:
|
||||
hasValueType = .idCard
|
||||
break outer
|
||||
case .driversLicense:
|
||||
hasValueType = .driversLicense
|
||||
break outer
|
||||
case .internalPassport:
|
||||
hasValueType = .internalPassport
|
||||
break outer
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -487,36 +491,39 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
||||
case let .just(type):
|
||||
if let value = findValue(formData.values, key: type.valueKey)?.1 {
|
||||
switch value.value {
|
||||
case .rentalAgreement:
|
||||
hasValueType = .rentalAgreement
|
||||
case .utilityBill:
|
||||
hasValueType = .utilityBill
|
||||
case .bankStatement:
|
||||
hasValueType = .bankStatement
|
||||
case .rentalAgreement:
|
||||
hasValueType = .rentalAgreement
|
||||
case .passportRegistration:
|
||||
hasValueType = .passportRegistration
|
||||
case .temporaryRegistration:
|
||||
hasValueType = .temporaryRegistration
|
||||
case .utilityBill:
|
||||
hasValueType = .utilityBill
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
case let .oneOf(types):
|
||||
for type in types {
|
||||
outer: for type in types {
|
||||
if let value = findValue(formData.values, key: type.valueKey)?.1 {
|
||||
switch value.value {
|
||||
case .rentalAgreement:
|
||||
hasValueType = .rentalAgreement
|
||||
case .bankStatement:
|
||||
hasValueType = .bankStatement
|
||||
case .passportRegistration:
|
||||
hasValueType = .passportRegistration
|
||||
case .temporaryRegistration:
|
||||
hasValueType = .temporaryRegistration
|
||||
case .utilityBill:
|
||||
hasValueType = .utilityBill
|
||||
|
||||
break outer
|
||||
case .bankStatement:
|
||||
hasValueType = .bankStatement
|
||||
break outer
|
||||
case .rentalAgreement:
|
||||
hasValueType = .rentalAgreement
|
||||
break outer
|
||||
case .passportRegistration:
|
||||
hasValueType = .passportRegistration
|
||||
break outer
|
||||
case .temporaryRegistration:
|
||||
hasValueType = .temporaryRegistration
|
||||
break outer
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -594,6 +601,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 +628,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
|
||||
}
|
||||
@ -728,7 +738,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
||||
case .temporaryRegistration:
|
||||
strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .temporaryRegistration, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil)
|
||||
case .address:
|
||||
strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: true, document: nil, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil)
|
||||
strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: true, document: nil, translations: false), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil)
|
||||
case .utilityBill:
|
||||
strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .utilityBill, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil)
|
||||
case .bankStatement:
|
||||
@ -742,6 +752,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 +844,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -503,6 +503,44 @@ private func placeholderForDocumentTypes(_ types: [SecureIdRequestedAddressDocum
|
||||
return strings.Passport_Address_UploadOneOfScan(string).0
|
||||
}
|
||||
|
||||
private func stringForDocumentValue(_ value: SecureIdValue, strings: PresentationStrings) -> String? {
|
||||
let stringForIdentityDocument: (String, SecureIdDate?) -> String = { identifier, date in
|
||||
var string = identifier
|
||||
if let date = date {
|
||||
string.append(", ")
|
||||
string.append(stringForDate(timestamp: date.timestamp, strings: strings))
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
let stringForAddressDocument: (Int) -> String = { count in
|
||||
return strings.Passport_Scans(Int32(count))
|
||||
}
|
||||
|
||||
switch value {
|
||||
case let .passport(value):
|
||||
return stringForIdentityDocument(value.identifier, value.expiryDate)
|
||||
case let .internalPassport(value):
|
||||
return stringForIdentityDocument(value.identifier, value.expiryDate)
|
||||
case let .idCard(value):
|
||||
return stringForIdentityDocument(value.identifier, value.expiryDate)
|
||||
case let .driversLicense(value):
|
||||
return stringForIdentityDocument(value.identifier, value.expiryDate)
|
||||
case let .utilityBill(value):
|
||||
return stringForAddressDocument(value.verificationDocuments.count)
|
||||
case let .rentalAgreement(value):
|
||||
return stringForAddressDocument(value.verificationDocuments.count)
|
||||
case let .bankStatement(value):
|
||||
return stringForAddressDocument(value.verificationDocuments.count)
|
||||
case let .temporaryRegistration(value):
|
||||
return stringForAddressDocument(value.verificationDocuments.count)
|
||||
case let .passportRegistration(value):
|
||||
return stringForAddressDocument(value.verificationDocuments.count)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings: PresentationStrings, values: [SecureIdValueWithContext]) -> (String, String) {
|
||||
var title: String
|
||||
var placeholder: String
|
||||
@ -510,6 +548,9 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
|
||||
|
||||
switch field {
|
||||
case let .identity(personalDetails, document, _, _):
|
||||
var isOneOf = false
|
||||
var filledDocument: (SecureIdRequestedIdentityDocument, SecureIdValue)?
|
||||
|
||||
if let document = document {
|
||||
title = strings.Passport_FieldIdentity
|
||||
placeholder = strings.Passport_FieldIdentityUploadHelp
|
||||
@ -518,29 +559,55 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
|
||||
case let .just(type):
|
||||
title = stringForDocumentType(type, strings: strings)
|
||||
placeholder = placeholderForDocumentType(type, strings: strings)
|
||||
break
|
||||
if let value = findValue(values, key: type.valueKey)?.1.value {
|
||||
filledDocument = (type, value)
|
||||
}
|
||||
case let .oneOf(types):
|
||||
isOneOf = true
|
||||
let typesArray = Array(types)
|
||||
if typesArray.count == 2 {
|
||||
title = strings.Passport_FieldOneOf_Or(stringForDocumentType(typesArray[0], strings: strings), stringForDocumentType(typesArray[1], strings: strings)).0
|
||||
}
|
||||
placeholder = placeholderForDocumentTypes(typesArray, strings: strings)
|
||||
break
|
||||
for type in types {
|
||||
if let value = findValue(values, key: type.valueKey)?.1.value {
|
||||
filledDocument = (type, value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
title = strings.Passport_Identity_TypePersonalDetails
|
||||
placeholder = strings.Passport_FieldIdentityDetailsHelp
|
||||
}
|
||||
|
||||
if personalDetails != nil {
|
||||
if let filledDocument = filledDocument, isOneOf {
|
||||
text = stringForDocumentType(filledDocument.0, strings: strings)
|
||||
}
|
||||
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)))
|
||||
}
|
||||
}
|
||||
if let filledDocument = filledDocument, let string = stringForDocumentValue(filledDocument.1, strings: strings) {
|
||||
if !text.isEmpty {
|
||||
text.append(", ")
|
||||
}
|
||||
text.append(string)
|
||||
}
|
||||
case let .address(addressDetails, document, _):
|
||||
var isOneOf = false
|
||||
var filledDocument: (SecureIdRequestedAddressDocument, SecureIdValue)?
|
||||
|
||||
if let document = document {
|
||||
title = strings.Passport_FieldAddress
|
||||
placeholder = strings.Passport_FieldAddressUploadHelp
|
||||
@ -548,27 +615,43 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
|
||||
case let .just(type):
|
||||
title = stringForDocumentType(type, strings: strings)
|
||||
placeholder = placeholderForDocumentType(type, strings: strings)
|
||||
break
|
||||
if let value = findValue(values, key: type.valueKey)?.1.value {
|
||||
filledDocument = (type, value)
|
||||
}
|
||||
case let .oneOf(types):
|
||||
isOneOf = true
|
||||
let typesArray = Array(types)
|
||||
if typesArray.count == 2 {
|
||||
title = strings.Passport_FieldOneOf_Or(stringForDocumentType(typesArray[0], strings: strings), stringForDocumentType(typesArray[1], strings: strings)).0
|
||||
}
|
||||
placeholder = placeholderForDocumentTypes(typesArray, strings: strings)
|
||||
break
|
||||
for type in types {
|
||||
if let value = findValue(values, key: type.valueKey)?.1.value {
|
||||
filledDocument = (type, value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
title = strings.Passport_FieldAddress
|
||||
placeholder = strings.Passport_FieldAddressHelp
|
||||
}
|
||||
|
||||
if let filledDocument = filledDocument, isOneOf {
|
||||
text = stringForDocumentType(filledDocument.0, strings: strings)
|
||||
}
|
||||
if addressDetails {
|
||||
if let value = findValue(values, key: .address), case let .address(addressValue) = value.1.value {
|
||||
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)))
|
||||
}
|
||||
} else if let filledDocument = filledDocument, let string = stringForDocumentValue(filledDocument.1, strings: strings) {
|
||||
if !text.isEmpty {
|
||||
text.append(", ")
|
||||
}
|
||||
text.append(string)
|
||||
}
|
||||
case .phone:
|
||||
title = strings.Passport_FieldPhone
|
||||
@ -596,6 +679,7 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
|
||||
}
|
||||
|
||||
private struct ValueAdditionalData {
|
||||
var nativeNames: Bool = false
|
||||
var selfie: Bool = false
|
||||
var translation: Bool = false
|
||||
}
|
||||
@ -603,6 +687,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 +810,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,15 +820,20 @@ 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
|
||||
text = strings.Passport_FieldIdentityDetailsHelp
|
||||
}
|
||||
}
|
||||
if let document = document {
|
||||
@ -752,9 +843,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
|
||||
@ -785,6 +878,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
|
||||
if addressDetails {
|
||||
if findValue(values, key: .address) == nil {
|
||||
filled = false
|
||||
text = strings.Passport_FieldAddressHelp
|
||||
}
|
||||
}
|
||||
if let document = document {
|
||||
@ -794,6 +888,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 +922,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
|
||||
|
||||
|
||||
@ -85,6 +85,12 @@ final class SecureIdAuthHeaderNode: ASDisplayNode {
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: width - 20.0, height: 1000.0))
|
||||
|
||||
if let verificationState = self.verificationState, case .noChallenge = verificationState {
|
||||
self.serviceAvatarNode.isHidden = true
|
||||
} else {
|
||||
self.serviceAvatarNode.isHidden = false
|
||||
}
|
||||
|
||||
var expandedHeight: CGFloat = titleSize.height
|
||||
if !self.serviceAvatarNode.isHidden {
|
||||
expandedHeight += avatarSize.height + avatarTitleSpacing
|
||||
@ -109,12 +115,6 @@ final class SecureIdAuthHeaderNode: ASDisplayNode {
|
||||
|
||||
let serviceAvatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize.width) / 2.0), y: titleFrame.minY - avatarTitleSpacing - avatarSize.height), size: avatarSize)
|
||||
transition.updateFrame(node: strongSelf.serviceAvatarNode, frame: serviceAvatarFrame)
|
||||
|
||||
if let verificationState = strongSelf.verificationState, case .noChallenge = verificationState {
|
||||
strongSelf.serviceAvatarNode.isHidden = true
|
||||
} else {
|
||||
strongSelf.serviceAvatarNode.isHidden = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user