From aa60d76fd879335dcfbc16046ec3eda9796e0e41 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Tue, 27 Mar 2018 12:03:25 +0400 Subject: [PATCH] no message --- TelegramUI.xcodeproj/project.pbxproj | 40 ++- .../AuthorizationSequenceController.swift | 3 +- ...rizationSequencePhoneEntryController.swift | 3 + TelegramUI/BotPaymentDateItemNode.swift | 23 ++ TelegramUI/BotPaymentDisclosureItemNode.swift | 2 +- TelegramUI/BotPaymentFieldItemNode.swift | 4 +- TelegramUI/ChatDocumentGalleryItem.swift | 7 +- TelegramUI/FindSecureIdValue.swift | 38 ++ TelegramUI/FormController.swift | 54 +++ TelegramUI/FormControllerHeaderItem.swift | 49 +++ TelegramUI/FormControllerItem.swift | 22 ++ TelegramUI/FormControllerNode.swift | 280 +++++++++++++++ TelegramUI/FormControllerScrollerNode.swift | 60 ++++ TelegramUI/OpenUrl.swift | 11 +- TelegramUI/SecureIdAuthController.swift | 23 +- TelegramUI/SecureIdAuthControllerNode.swift | 84 +++-- TelegramUI/SecureIdAuthControllerState.swift | 19 +- TelegramUI/SecureIdAuthFormContentNode.swift | 8 +- TelegramUI/SecureIdAuthFormFieldNode.swift | 8 +- TelegramUI/SecureIdAuthHeaderNode.swift | 16 +- .../SecureIdIdentityFormController.swift | 65 ++-- .../SecureIdIdentityFormControllerNode.swift | 336 ++++-------------- 22 files changed, 776 insertions(+), 379 deletions(-) create mode 100644 TelegramUI/BotPaymentDateItemNode.swift create mode 100644 TelegramUI/FindSecureIdValue.swift create mode 100644 TelegramUI/FormController.swift create mode 100644 TelegramUI/FormControllerHeaderItem.swift create mode 100644 TelegramUI/FormControllerItem.swift create mode 100644 TelegramUI/FormControllerNode.swift create mode 100644 TelegramUI/FormControllerScrollerNode.swift diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index 0ce07e6ed2..9a577c6078 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -175,6 +175,12 @@ D093D7DF2062F3F000BC3599 /* SecureIdIdentityFormController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7DE2062F3F000BC3599 /* SecureIdIdentityFormController.swift */; }; D093D7E22062F40100BC3599 /* SecureIdIdentityFormControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7E12062F40100BC3599 /* SecureIdIdentityFormControllerNode.swift */; }; D093D7E72063E57F00BC3599 /* BotPaymentActionItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7E62063E57F00BC3599 /* BotPaymentActionItemNode.swift */; }; + D093D80820654A2900BC3599 /* BotPaymentDateItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D80720654A2900BC3599 /* BotPaymentDateItemNode.swift */; }; + D093D81D206994FD00BC3599 /* FindSecureIdValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D81C206994FD00BC3599 /* FindSecureIdValue.swift */; }; + D093D82020699A7300BC3599 /* FormController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D81F20699A7300BC3599 /* FormController.swift */; }; + D093D82220699A7C00BC3599 /* FormControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D82120699A7C00BC3599 /* FormControllerNode.swift */; }; + D093D8242069A06600BC3599 /* FormControllerScrollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D8232069A06600BC3599 /* FormControllerScrollerNode.swift */; }; + D093D8262069A31700BC3599 /* FormControllerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D8252069A31700BC3599 /* FormControllerItem.swift */; }; D0943AF61FDAAE7E001522CC /* MultipleAvatarsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0943AF51FDAAE7E001522CC /* MultipleAvatarsNode.swift */; }; D0943AFE1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0943AFD1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift */; }; D0943B001FDAE852001522CC /* ChatFeedNavigationInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0943AFF1FDAE852001522CC /* ChatFeedNavigationInputPanelNode.swift */; }; @@ -263,6 +269,7 @@ D0DE66061F9A51E200EF4AE9 /* GalleryHiddenMediaManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DE66051F9A51E200EF4AE9 /* GalleryHiddenMediaManager.swift */; }; D0DFD5E21FCE2BA50039B3B1 /* CalculatingCacheSizeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DFD5E11FCE2BA50039B3B1 /* CalculatingCacheSizeItem.swift */; }; D0E266FD1F66706500BFC79F /* ChatBubbleVideoDecoration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E266FC1F66706500BFC79F /* ChatBubbleVideoDecoration.swift */; }; + D0E412C62069B60600BEE4A2 /* FormControllerHeaderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E412C52069B60600BEE4A2 /* FormControllerHeaderItem.swift */; }; D0E817472010E62F00B82BBB /* MergeLists.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E817462010E62E00B82BBB /* MergeLists.swift */; }; D0E8174C2011F8A300B82BBB /* ChatMessageEventLogPreviousMessageContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8174B2011F8A300B82BBB /* ChatMessageEventLogPreviousMessageContentNode.swift */; }; D0E8174E2011FC3800B82BBB /* ChatMessageEventLogPreviousDescriptionContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8174D2011FC3800B82BBB /* ChatMessageEventLogPreviousDescriptionContentNode.swift */; }; @@ -1359,6 +1366,12 @@ D093D7DE2062F3F000BC3599 /* SecureIdIdentityFormController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdIdentityFormController.swift; sourceTree = ""; }; D093D7E12062F40100BC3599 /* SecureIdIdentityFormControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdIdentityFormControllerNode.swift; sourceTree = ""; }; D093D7E62063E57F00BC3599 /* BotPaymentActionItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BotPaymentActionItemNode.swift; sourceTree = ""; }; + D093D80720654A2900BC3599 /* BotPaymentDateItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BotPaymentDateItemNode.swift; sourceTree = ""; }; + D093D81C206994FD00BC3599 /* FindSecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindSecureIdValue.swift; sourceTree = ""; }; + D093D81F20699A7300BC3599 /* FormController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormController.swift; sourceTree = ""; }; + D093D82120699A7C00BC3599 /* FormControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormControllerNode.swift; sourceTree = ""; }; + D093D8232069A06600BC3599 /* FormControllerScrollerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormControllerScrollerNode.swift; sourceTree = ""; }; + D093D8252069A31700BC3599 /* FormControllerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormControllerItem.swift; sourceTree = ""; }; D0943AF51FDAAE7E001522CC /* MultipleAvatarsNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleAvatarsNode.swift; sourceTree = ""; }; D0943AFD1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMultipleAvatarsNavigationNode.swift; sourceTree = ""; }; D0943AFF1FDAE852001522CC /* ChatFeedNavigationInputPanelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatFeedNavigationInputPanelNode.swift; sourceTree = ""; }; @@ -1565,6 +1578,7 @@ D0E305AE1E5BA8E000D7A3A2 /* ItemListLoadingIndicatorEmptyStateItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListLoadingIndicatorEmptyStateItem.swift; sourceTree = ""; }; D0E35A061DE4803400BC6096 /* VerticalListContextResultsChatInputContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalListContextResultsChatInputContextPanelNode.swift; sourceTree = ""; }; D0E35A081DE4804900BC6096 /* VerticalListContextResultsChatInputPanelItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalListContextResultsChatInputPanelItem.swift; sourceTree = ""; }; + D0E412C52069B60600BEE4A2 /* FormControllerHeaderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormControllerHeaderItem.swift; sourceTree = ""; }; D0E7A1BC1D8C246D00C37A6F /* ChatHistoryListNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryListNode.swift; sourceTree = ""; }; D0E7A1BE1D8C24B900C37A6F /* ChatHistoryViewForLocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryViewForLocation.swift; sourceTree = ""; }; D0E7A1C01D8C258D00C37A6F /* ChatHistoryEntriesForView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryEntriesForView.swift; sourceTree = ""; }; @@ -1990,7 +2004,6 @@ D01B27931E38F3920022A4C0 /* Item List */ = { isa = PBXGroup; children = ( - D0E6521D1E3A2305004EEA91 /* Items */, D01B27981E39144C0022A4C0 /* ItemListController.swift */, D0E305AC1E5BA3E700D7A3A2 /* ItemListControllerEmptyStateItem.swift */, D01B27941E38F3BF0022A4C0 /* ItemListControllerNode.swift */, @@ -2478,6 +2491,7 @@ D0471B5B1EFEB4F30074D609 /* BotPaymentFieldItemNode.swift */, D0471B611EFEB5B70074D609 /* BotPaymentSwitchItemNode.swift */, D0E9B9E71EFEFB9500F079A4 /* BotPaymentDisclosureItemNode.swift */, + D093D80720654A2900BC3599 /* BotPaymentDateItemNode.swift */, D093D7E62063E57F00BC3599 /* BotPaymentActionItemNode.swift */, D0E9BA621F055AD200F079A4 /* BotPaymentCardInputItemNode.swift */, D0E9B9F31F018A6700F079A4 /* BotCheckoutPaymentMethodSheet.swift */, @@ -2686,6 +2700,18 @@ name = Identity; sourceTree = ""; }; + D093D81E20699A6000BC3599 /* Form */ = { + isa = PBXGroup; + children = ( + D093D81F20699A7300BC3599 /* FormController.swift */, + D093D82120699A7C00BC3599 /* FormControllerNode.swift */, + D093D8232069A06600BC3599 /* FormControllerScrollerNode.swift */, + D093D8252069A31700BC3599 /* FormControllerItem.swift */, + D0E412C52069B60600BEE4A2 /* FormControllerHeaderItem.swift */, + ); + name = Form; + sourceTree = ""; + }; D096A4601EA681720000A7AE /* Presentation Data */ = { isa = PBXGroup; children = ( @@ -2855,6 +2881,7 @@ D093D7DA2062CFF500BC3599 /* SecureIdAuthFormContentNode.swift */, D093D7DC2062D09A00BC3599 /* SecureIdAuthFormFieldNode.swift */, D093D7E02062F3F400BC3599 /* Identity */, + D093D81C206994FD00BC3599 /* FindSecureIdValue.swift */, ); name = "Secure ID"; sourceTree = ""; @@ -3465,7 +3492,9 @@ D0F69DD31D6B8A160046BCD6 /* Controllers */ = { isa = PBXGroup; children = ( + D01B27931E38F3920022A4C0 /* Item List */, D0736F261DF4D2F300F2C02A /* Telegram Controller */, + D093D81E20699A6000BC3599 /* Form */, ); name = Controllers; sourceTree = ""; @@ -3483,7 +3512,6 @@ D0F69E4E1D6B8BB90046BCD6 /* Media */, D0F69E6C1D6B8C220046BCD6 /* Contacts */, D0BC38681E3FB92B0044D6FE /* Compose */, - D01B27931E38F3920022A4C0 /* Item List */, D0EE97131D88BB1A006C18E1 /* Peer Info */, D0D2689B1D79D31500C422DA /* Peer Selection */, D0754D251EEE10A100884F6E /* Bot Payments */, @@ -3717,6 +3745,7 @@ D0F69E671D6B8C030046BCD6 /* Map Input */ = { isa = PBXGroup; children = ( + D0E6521D1E3A2305004EEA91 /* Items */, D0F69E681D6B8C160046BCD6 /* MapInputController.swift */, D0F69E691D6B8C160046BCD6 /* MapInputControllerNode.swift */, ); @@ -4240,6 +4269,7 @@ D0BE30452061C09000FBE6D8 /* SecureIdAuthContentNode.swift in Sources */, D0471B5A1EFE70400074D609 /* BotCheckoutInfoControllerNode.swift in Sources */, D0EC6CDA1EB9F58800EBF1C3 /* NumericFormat.swift in Sources */, + D093D82220699A7C00BC3599 /* FormControllerNode.swift in Sources */, D0EC6CDB1EB9F58800EBF1C3 /* Markdown.swift in Sources */, D0471B641EFEB5CB0074D609 /* BotPaymentItemNode.swift in Sources */, D0380DB8204EE0A5000414AB /* ChatInstantVideoMessageDurationNode.swift in Sources */, @@ -4575,6 +4605,7 @@ D0E9B9E81EFEFB9500F079A4 /* BotPaymentDisclosureItemNode.swift in Sources */, D0EC6DA01EB9F58900EBF1C3 /* ChatHoleItem.swift in Sources */, D09E63A21F0FA723003444CD /* EmbedVideoNode.swift in Sources */, + D093D82020699A7300BC3599 /* FormController.swift in Sources */, D0EC6DA11EB9F58900EBF1C3 /* ChatMessageSelectionNode.swift in Sources */, D0EC6DA21EB9F58900EBF1C3 /* ChatMessageBubbleImages.swift in Sources */, D0EC6DA31EB9F58900EBF1C3 /* ChatMessageDateHeader.swift in Sources */, @@ -4797,6 +4828,7 @@ D0EC6E2F1EB9F58900EBF1C3 /* ContactMultiselectionControllerNode.swift in Sources */, D0EC6E301EB9F58900EBF1C3 /* ContactSelectionController.swift in Sources */, D0EC6E311EB9F58900EBF1C3 /* ContactSelectionControllerNode.swift in Sources */, + D093D81D206994FD00BC3599 /* FindSecureIdValue.swift in Sources */, D0EC6E321EB9F58900EBF1C3 /* CreateGroupController.swift in Sources */, D00BED221F73F82400922292 /* SharePeersContainerNode.swift in Sources */, D0EC6E331EB9F58900EBF1C3 /* CreateChannelController.swift in Sources */, @@ -4830,6 +4862,7 @@ D0EC6E461EB9F58900EBF1C3 /* ItemListLoadingIndicatorEmptyStateItem.swift in Sources */, D01A21AF1F39EA2E00DDA104 /* InstantPageTheme.swift in Sources */, D0EC6E471EB9F58900EBF1C3 /* ItemListTextEmptyStateItem.swift in Sources */, + D0E412C62069B60600BEE4A2 /* FormControllerHeaderItem.swift in Sources */, D0EC6E481EB9F58900EBF1C3 /* ItemListController.swift in Sources */, D0EC6E491EB9F58900EBF1C3 /* ItemListControllerEmptyStateItem.swift in Sources */, D0EC6E4A1EB9F58900EBF1C3 /* ItemListControllerNode.swift in Sources */, @@ -4848,6 +4881,7 @@ D0EC6E511EB9F58900EBF1C3 /* ChannelBlacklistController.swift in Sources */, D0EC6E521EB9F58900EBF1C3 /* ChannelInfoController.swift in Sources */, D0EC6E531EB9F58900EBF1C3 /* ChannelMembersController.swift in Sources */, + D093D8242069A06600BC3599 /* FormControllerScrollerNode.swift in Sources */, D093D7E72063E57F00BC3599 /* BotPaymentActionItemNode.swift in Sources */, D01C06BA1FBBB076001561AB /* ItemListSelectableControlNode.swift in Sources */, D0EC6E541EB9F58900EBF1C3 /* ConvertToSupergroupController.swift in Sources */, @@ -4903,6 +4937,7 @@ D0EC6E7A1EB9F58900EBF1C3 /* DebugController.swift in Sources */, D07ABBAB202A1BD1003671DE /* LegacyWallpaperEditor.swift in Sources */, D0EC6E7B1EB9F58900EBF1C3 /* DebugAccountsController.swift in Sources */, + D093D8262069A31700BC3599 /* FormControllerItem.swift in Sources */, D0EC6E7C1EB9F58900EBF1C3 /* UsernameSetupController.swift in Sources */, D0471B621EFEB5B70074D609 /* BotPaymentSwitchItemNode.swift in Sources */, D09250041FE5363D003F693F /* ExperimentalSettings.swift in Sources */, @@ -4922,6 +4957,7 @@ D0EC6E851EB9F58900EBF1C3 /* ChatMessageNotificationItem.swift in Sources */, D0EC6E861EB9F58900EBF1C3 /* UIImage+WebP.m in Sources */, D0EC6E871EB9F58900EBF1C3 /* FastBlur.m in Sources */, + D093D80820654A2900BC3599 /* BotPaymentDateItemNode.swift in Sources */, D04281FE200E639A009DDE36 /* ChatRecentActionsTitleView.swift in Sources */, D0ACCB1C1EC5FF4B0079D8BF /* ChatMessageCallBubbleContentNode.swift in Sources */, D046142E2004DB3700EC0EF2 /* LiveLocationManager.swift in Sources */, diff --git a/TelegramUI/AuthorizationSequenceController.swift b/TelegramUI/AuthorizationSequenceController.swift index 4b25a40e8c..ba2e153556 100644 --- a/TelegramUI/AuthorizationSequenceController.swift +++ b/TelegramUI/AuthorizationSequenceController.swift @@ -75,8 +75,9 @@ public final class AuthorizationSequenceController: NavigationController { var countryCode: Int32 = 1 if let countryId = countryId { + let normalizedId = countryId.uppercased() for (code, idAndName) in countryCodeToIdAndName { - if idAndName.0 == countryId { + if idAndName.0 == normalizedId { countryCode = Int32(code) break } diff --git a/TelegramUI/AuthorizationSequencePhoneEntryController.swift b/TelegramUI/AuthorizationSequencePhoneEntryController.swift index f2806be9d0..647e3a0071 100644 --- a/TelegramUI/AuthorizationSequencePhoneEntryController.swift +++ b/TelegramUI/AuthorizationSequencePhoneEntryController.swift @@ -55,6 +55,9 @@ final class AuthorizationSequencePhoneEntryController: ViewController { override public func loadDisplayNode() { self.displayNode = AuthorizationSequencePhoneEntryControllerNode(strings: self.strings, theme: self.theme) + if let (code, number) = self.currentData { + self.controllerNode.codeAndNumber = (code, number) + } self.displayNodeDidLoad() self.controllerNode.selectCountryCode = { [weak self] in if let strongSelf = self { diff --git a/TelegramUI/BotPaymentDateItemNode.swift b/TelegramUI/BotPaymentDateItemNode.swift new file mode 100644 index 0000000000..55a443d551 --- /dev/null +++ b/TelegramUI/BotPaymentDateItemNode.swift @@ -0,0 +1,23 @@ +import Foundation +import AsyncDisplayKit +import Display + +private let titleFont = Font.regular(17.0) + +private func formatDate(_ value: Int32) -> String { + let formatter = DateFormatter() + formatter.dateFormat = "dd.MM.YYYY" + return formatter.string(from: Date(timeIntervalSince1970: Double(value))) +} + +final class BotPaymentDateItemNode: BotPaymentDisclosureItemNode { + var timestamp: Int32? { + didSet { + self.text = timestamp.flatMap({ formatDate($0) }) ?? "" + } + } + + init(title: String, placeholder: String, timestamp: Int32?, strings: PresentationStrings) { + super.init(title: title, placeholder: placeholder, text: timestamp.flatMap({ formatDate($0) }) ?? "") + } +} diff --git a/TelegramUI/BotPaymentDisclosureItemNode.swift b/TelegramUI/BotPaymentDisclosureItemNode.swift index 8eb0ac45c6..9345c1b38a 100644 --- a/TelegramUI/BotPaymentDisclosureItemNode.swift +++ b/TelegramUI/BotPaymentDisclosureItemNode.swift @@ -4,7 +4,7 @@ import Display private let titleFont = Font.regular(17.0) -final class BotPaymentDisclosureItemNode: BotPaymentItemNode { +class BotPaymentDisclosureItemNode: BotPaymentItemNode { private let title: String private let placeholder: String var text: String { diff --git a/TelegramUI/BotPaymentFieldItemNode.swift b/TelegramUI/BotPaymentFieldItemNode.swift index e3c013f51b..9359e6be38 100644 --- a/TelegramUI/BotPaymentFieldItemNode.swift +++ b/TelegramUI/BotPaymentFieldItemNode.swift @@ -22,7 +22,7 @@ final class BotPaymentFieldItemNode: BotPaymentItemNode { var textUpdated: (() -> Void)? - init(title: String, placeholder: String) { + init(title: String, placeholder: String, text: String = "") { self.title = title self.placeholder = placeholder @@ -32,6 +32,8 @@ final class BotPaymentFieldItemNode: BotPaymentItemNode { self.textField = TextFieldNode() self.textField.textField.font = titleFont self.textField.textField.returnKeyType = .next + + self.textField.textField.text = text super.init(needsBackground: true) diff --git a/TelegramUI/ChatDocumentGalleryItem.swift b/TelegramUI/ChatDocumentGalleryItem.swift index b14cc4ba13..b29605a9f0 100644 --- a/TelegramUI/ChatDocumentGalleryItem.swift +++ b/TelegramUI/ChatDocumentGalleryItem.swift @@ -74,12 +74,17 @@ class ChatDocumentGalleryItemNode: GalleryItemNode { init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { if #available(iOS 9.0, *) { - let webView = WKWebView() + let preferences = WKPreferences() + preferences.javaScriptEnabled = false + let configuration = WKWebViewConfiguration() + configuration.preferences = preferences + let webView = WKWebView(frame: CGRect(), configuration: configuration) webView.allowsLinkPreview = false webView.allowsBackForwardNavigationGestures = false self.webView = webView } else { let webView = UIWebView() + webView.scalesPageToFit = true self.webView = webView } diff --git a/TelegramUI/FindSecureIdValue.swift b/TelegramUI/FindSecureIdValue.swift new file mode 100644 index 0000000000..2bb7b4ea60 --- /dev/null +++ b/TelegramUI/FindSecureIdValue.swift @@ -0,0 +1,38 @@ +import Foundation +import TelegramCore + +func findIdentity(_ values: [SecureIdValue]) -> (Int, SecureIdIdentityValue)? { + for i in 0 ..< values.count { + switch values[i] { + case let .identity(identity): + return (i, identity) + default: + break + } + } + return nil +} + +func findPhone(_ values: [SecureIdValue]) -> (Int, SecureIdPhoneValue)? { + for i in 0 ..< values.count { + switch values[i] { + case let .phone(phone): + return (i, phone) + default: + break + } + } + return nil +} + +func findEmail(_ values: [SecureIdValue]) -> (Int, SecureIdEmailValue)? { + for i in 0 ..< values.count { + switch values[i] { + case let .email(email): + return (i, email) + default: + break + } + } + return nil +} diff --git a/TelegramUI/FormController.swift b/TelegramUI/FormController.swift new file mode 100644 index 0000000000..f5d8d846b9 --- /dev/null +++ b/TelegramUI/FormController.swift @@ -0,0 +1,54 @@ +import Foundation +import SwiftSignalKit +import AsyncDisplayKit +import Display + +class FormController>: ViewController { + var controllerNode: Node { + return self.displayNode as! Node + } + + private var presentationData: PresentationData + + private var didPlayPresentationAnimation = false + + init(presentationData: PresentationData) { + self.presentationData = presentationData + + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData)) + + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !self.didPlayPresentationAnimation { + self.didPlayPresentationAnimation = true + self.controllerNode.animateIn() + } + } + + override func dismiss(completion: (() -> Void)? = nil) { + self.controllerNode.animateOut(completion: { [weak self] in + self?.presentingViewController?.dismiss(animated: false, completion: nil) + completion?() + }) + } + + override func loadDisplayNode() { + self.displayNode = Node(theme: self.presentationData.theme, strings: self.presentationData.strings) + + self.displayNodeDidLoad() + } + + override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, navigationHeight: self.navigationHeight, transition: transition) + } +} diff --git a/TelegramUI/FormControllerHeaderItem.swift b/TelegramUI/FormControllerHeaderItem.swift new file mode 100644 index 0000000000..3909e3d9bb --- /dev/null +++ b/TelegramUI/FormControllerHeaderItem.swift @@ -0,0 +1,49 @@ +import Foundation +import AsyncDisplayKit +import Display + +private let titleFont = Font.regular(14.0) + +final class FormControllerHeaderItem: FormControllerItem { + let text: String + + init(text: String) { + self.text = text + } + + func node() -> ASDisplayNode & FormControllerItemNode { + return FormControllerHeaderItemNode() + } + + func update(node: ASDisplayNode & FormControllerItemNode, theme: PresentationTheme, strings: PresentationStrings, width: CGFloat, previousNeighbor: FormControllerItemNeighbor, nextNeighbor: FormControllerItemNeighbor, transition: ContainedViewLayoutTransition) -> CGFloat { + guard let node = node as? FormControllerHeaderItemNode else { + assertionFailure() + return 0.0 + } + return node.update(item: self, width: width, theme: theme, transition: transition) + } +} + +final class FormControllerHeaderItemNode: ASDisplayNode, FormControllerItemNode { + private let textNode: ImmediateTextNode + + override init() { + self.textNode = ImmediateTextNode() + self.textNode.maximumNumberOfLines = 1 + + super.init() + + self.addSubnode(self.textNode) + } + + func update(item: FormControllerHeaderItem, width: CGFloat, theme: PresentationTheme, transition: ContainedViewLayoutTransition) -> CGFloat { + self.textNode.attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: theme.list.sectionHeaderTextColor) + let leftInset: CGFloat = 16.0 + + let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - 10.0, height: CGFloat.greatestFiniteMagnitude)) + + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 7.0), size: textSize)) + + return 30.0 + } +} diff --git a/TelegramUI/FormControllerItem.swift b/TelegramUI/FormControllerItem.swift new file mode 100644 index 0000000000..9d4f03a913 --- /dev/null +++ b/TelegramUI/FormControllerItem.swift @@ -0,0 +1,22 @@ +import Foundation +import AsyncDisplayKit +import Display + +protocol FormControllerEntry: Identifiable { + func isEqual(to: Self) -> Bool + func item(strings: PresentationStrings) -> FormControllerItem +} + +enum FormControllerItemNeighbor { + case none + case spacer + case item(FormControllerItem) +} + +protocol FormControllerItem { + func node() -> ASDisplayNode & FormControllerItemNode + func update(node: ASDisplayNode & FormControllerItemNode, theme: PresentationTheme, strings: PresentationStrings, width: CGFloat, previousNeighbor: FormControllerItemNeighbor, nextNeighbor: FormControllerItemNeighbor, transition: ContainedViewLayoutTransition) -> CGFloat +} + +protocol FormControllerItemNode { +} diff --git a/TelegramUI/FormControllerNode.swift b/TelegramUI/FormControllerNode.swift new file mode 100644 index 0000000000..5fefa43ff8 --- /dev/null +++ b/TelegramUI/FormControllerNode.swift @@ -0,0 +1,280 @@ +import Foundation +import SwiftSignalKit +import AsyncDisplayKit +import Display + +struct FormControllerLayoutState { + var layout: ContainerViewLayout + var navigationHeight: CGFloat + + func isEqual(to: FormControllerLayoutState) -> Bool { + if self.layout != to.layout { + return false + } + if self.navigationHeight != to.navigationHeight { + return false + } + return true + } +} + +struct FormControllerPresentationState { + var theme: PresentationTheme + var strings: PresentationStrings + + func isEqual(to: FormControllerPresentationState) -> Bool { + if self.theme !== to.theme { + return false + } + if self.strings !== to.strings { + return false + } + return true + } +} + +struct FormControllerInternalState { + var layoutState: FormControllerLayoutState? + var presentationState: FormControllerPresentationState + var innerState: InnerState? + + func isEqual(to: FormControllerInternalState) -> Bool { + if let lhsLayoutState = self.layoutState, let rhsLayoutState = to.layoutState { + if !lhsLayoutState.isEqual(to: rhsLayoutState) { + return false + } + } else if (self.layoutState != nil) != (to.layoutState != nil) { + return false + } + + if !self.presentationState.isEqual(to: to.presentationState) { + return false + } + + if let lhsInnerState = self.innerState, let rhsInnerState = to.innerState { + if !lhsInnerState.isEqual(to: rhsInnerState) { + return false + } + } else if (self.innerState != nil) != (to.innerState != nil) { + return false + } + + return true + } +} + +struct FormControllerState { + let layoutState: FormControllerLayoutState + let presentationState: FormControllerPresentationState + let innerState: InnerState +} + +enum FormControllerItemEntry { + case entry(Entry) + case spacer +} + +protocol FormControllerInnerState { + associatedtype Entry: FormControllerEntry + + func isEqual(to: Self) -> Bool + func entries() -> [FormControllerItemEntry] +} + +private enum FilteredItemNeighbor { + case spacer + case item(FormControllerItem) +} + +class FormControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { + private typealias InternalState = FormControllerInternalState + typealias State = FormControllerState + typealias Entry = InnerState.Entry + + private var internalState: InternalState + + private let scrollNode: FormControllerScrollerNode + + private var appliedLayout: FormControllerLayoutState? + private var appliedEntries: [Entry] = [] + private var itemNodes: [ASDisplayNode & FormControllerItemNode] = [] + + required init(theme: PresentationTheme, strings: PresentationStrings) { + self.internalState = FormControllerInternalState(layoutState: nil, presentationState: FormControllerPresentationState(theme: theme, strings: strings), innerState: nil) + + self.scrollNode = FormControllerScrollerNode() + + super.init() + + self.backgroundColor = theme.list.blocksBackgroundColor + + self.scrollNode.backgroundColor = nil + self.scrollNode.isOpaque = false + self.scrollNode.delegate = self + + self.addSubnode(self.scrollNode) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.updateInternalState(transition: transition, { state in + var state = state + state.layoutState = FormControllerLayoutState(layout: layout, navigationHeight: navigationHeight) + return state + }) + } + + func updateInnerState(transition: ContainedViewLayoutTransition, with innerState: InnerState) { + self.updateInternalState(transition: transition, { state in + var state = state + state.innerState = innerState + return state + }) + } + + private func updateInternalState(transition: ContainedViewLayoutTransition, _ f: (InternalState) -> InternalState) { + let updated = f(self.internalState) + if !updated.isEqual(to: self.internalState) { + self.internalState = updated + if let layoutState = updated.layoutState, let innerState = updated.innerState { + self.stateUpdated(state: FormControllerState(layoutState: layoutState, presentationState: updated.presentationState, innerState: innerState), transition: transition) + } + } + } + + func stateUpdated(state: State, transition: ContainedViewLayoutTransition) { + let previousLayout = self.appliedLayout + self.appliedLayout = state.layoutState + + let layout = state.layoutState.layout + var insets = layout.insets(options: [.input]) + insets.top += max(state.layoutState.navigationHeight, layout.insets(options: [.statusBar]).top) + + let entries = state.innerState.entries() + var filteredEntries: [Entry] = [] + var filteredItemNeighbors: [FilteredItemNeighbor] = [] + var itemNodes: [ASDisplayNode & FormControllerItemNode] = [] + var insertedItemNodeIndices = Set() + + for i in 0 ..< entries.count { + if case let .entry(entry) = entries[i] { + let item = entry.item(strings: state.presentationState.strings) + + filteredEntries.append(entry) + filteredItemNeighbors.append(.item(item)) + + var found = false + inner: for j in 0 ..< self.appliedEntries.count { + if entry.stableId == self.appliedEntries[j].stableId { + itemNodes.append(self.itemNodes[j]) + found = true + break inner + } + } + if !found { + let itemNode = item.node() + insertedItemNodeIndices.insert(itemNodes.count) + itemNodes.append(itemNode) + self.scrollNode.addSubnode(itemNode) + } + } else { + filteredItemNeighbors.append(.spacer) + } + } + + for itemNode in self.itemNodes { + var found = false + inner: for updated in itemNodes { + if updated === itemNode { + found = true + break inner + } + } + if !found { + transition.updateAlpha(node: itemNode, alpha: 0.0, completion: { [weak itemNode] _ in + itemNode?.removeFromSupernode() + }) + } + } + + self.appliedEntries = filteredEntries + self.itemNodes = itemNodes + + var contentHeight: CGFloat = 0.0 + + var itemNodeIndex = 0 + for i in 0 ..< filteredItemNeighbors.count { + if case let .item(item) = filteredItemNeighbors[i] { + let previousNeighbor: FormControllerItemNeighbor + let nextNeighbor: FormControllerItemNeighbor + if i != 0 { + switch filteredItemNeighbors[i - 1] { + case .spacer: + previousNeighbor = .spacer + case let .item(item): + previousNeighbor = .item(item) + } + } else { + previousNeighbor = .none + } + if i != filteredItemNeighbors.count - 1 { + switch filteredItemNeighbors[i + 1] { + case .spacer: + nextNeighbor = .spacer + case let .item(item): + nextNeighbor = .item(item) + } + } else { + nextNeighbor = .none + } + + let itemTransition: ContainedViewLayoutTransition + if insertedItemNodeIndices.contains(i) { + itemTransition = .immediate + } else { + itemTransition = transition + } + + let itemHeight = item.update(node: itemNodes[itemNodeIndex], theme: state.presentationState.theme, strings: state.presentationState.strings, width: layout.size.width, previousNeighbor: previousNeighbor, nextNeighbor: nextNeighbor, transition: itemTransition) + itemTransition.updateFrame(node: itemNodes[itemNodeIndex], frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: itemHeight))) + contentHeight += itemHeight + + itemNodeIndex += 1 + } + } + + let scrollContentSize = CGSize(width: layout.size.width, height: contentHeight) + + let previousBoundsOrigin = self.scrollNode.bounds.origin + self.scrollNode.view.ignoreUpdateBounds = true + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + self.scrollNode.view.contentSize = scrollContentSize + self.scrollNode.view.contentInset = insets + self.scrollNode.view.scrollIndicatorInsets = insets + self.scrollNode.view.ignoreUpdateBounds = false + + if let previousLayout = previousLayout { + var previousInsets = previousLayout.layout.insets(options: [.input]) + previousInsets.top += max(previousLayout.navigationHeight, previousLayout.layout.insets(options: [.statusBar]).top) + let insetsScrollOffset = insets.top - previousInsets.top + + var contentOffset = CGPoint(x: 0.0, y: previousBoundsOrigin.y + insetsScrollOffset) + contentOffset.y = min(contentOffset.y, scrollContentSize.height + insets.bottom - layout.size.height) + contentOffset.y = max(contentOffset.y, -insets.top) + + transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: contentOffset.y), size: layout.size)) + } else { + 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)) + } + } + + func animateIn() { + self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + } + + func animateOut(completion: (() -> Void)? = nil) { + self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { _ in + completion?() + }) + } +} diff --git a/TelegramUI/FormControllerScrollerNode.swift b/TelegramUI/FormControllerScrollerNode.swift new file mode 100644 index 0000000000..bcb09efa89 --- /dev/null +++ b/TelegramUI/FormControllerScrollerNode.swift @@ -0,0 +1,60 @@ +import Foundation +import AsyncDisplayKit + +final class FormControllerScrollerNodeView: UIScrollView { + var ignoreUpdateBounds = false + + override init(frame: CGRect) { + super.init(frame: frame) + + if #available(iOSApplicationExtension 11.0, *) { + self.contentInsetAdjustmentBehavior = .never + } + + self.alwaysBounceVertical = true + self.showsVerticalScrollIndicator = false + self.showsHorizontalScrollIndicator = false + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var bounds: CGRect { + get { + return super.bounds + } set(value) { + if !self.ignoreUpdateBounds { + super.bounds = value + } + } + } + + override func scrollRectToVisible(_ rect: CGRect, animated: Bool) { + } +} + +final class FormControllerScrollerNode: ASDisplayNode, UIScrollViewDelegate { + override var view: FormControllerScrollerNodeView { + return super.view as! FormControllerScrollerNodeView + } + + weak var delegate: UIScrollViewDelegate? + + override init() { + super.init() + + self.setViewBlock({ + return FormControllerScrollerNodeView(frame: CGRect()) + }) + } + + override func didLoad() { + super.didLoad() + self.view.delegate = self + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.delegate?.scrollViewDidScroll?(scrollView) + } +} diff --git a/TelegramUI/OpenUrl.swift b/TelegramUI/OpenUrl.swift index 8550e7c36f..023496dc83 100644 --- a/TelegramUI/OpenUrl.swift +++ b/TelegramUI/OpenUrl.swift @@ -225,12 +225,10 @@ public func openExternalUrl(account: Account, url: String, presentationData: Pre convertedUrl = result } } - } else if parsedUrl.host == "auth" { - //http://tg//auth?bot_id=443863171&scope=write%2Cidentity%2Caddress%2Cphone%2Cemail&callback_url=https%3A%2F%2Fkolnogorov.me%2Fsamples%2Fsecure_id_callback.php&public_key=-----BEGIN%20PUBLIC%20KEY-----%0AMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzmgKr0fPP4rB%2FTsNEweC%0AhoG3ntUxuBTmHsFBW6CpABGdaTmKZSjAI%2FcTofhBgtRQIOdX0YRGHHHhwyLf49Wv%0A9l%2BXexbJOa0lTsJSNMj8Y%2F9sZbqUl5ur8ZOTM0sxbXC0XKexu1tM9YavH%2BLbrobk%0Ajt0%2Bcmo%2FzEYZWNtLVihnR2IDv%2B7tSgiDoFWi%2FkoAUdfJ1VMw%2BhReUaLg3vE9CmPK%0AtQiTy%2BNvmrYaBPb75I0Jz3Lrz1%2BmZSjLKO25iT84RIsxarBDd8iYh2avWkCmvtiR%0ALcif8wLxi2QWC1rZoCA3Ip%2BHg9J9vxHlzl6xT01WjUStMhfwrUW6QBpur7FJ%2BaKM%0AoaMoHieFNCG4qIkWVEHHSsUpLum4SYuEnyNH3tkjbrdldZanCvanGq%2BTZyX0buRt%0A4zk7FGcu8iulUkAP%2Fo%2FWZM0HKinFN%2FvuzNVA8iqcO%2FBBhewhzpqmmTMnWmAO8WPP%0ADJMABRtXJnVuPh1CI5pValzomLJM4%2FYvnJGppzI1QiHHNA9JtxVmj2xf8jaXa1LJ%0AWUNJK%2BRvUWkRUxpWiKQQO9FAyTPLRtDQGN9eUeDR1U0jqRk%2FgNT8smHGN6I4H%2BNR%0A3X3%2F1lMfcm1dvk654ql8mxjCA54IpTPr%2FicUMc7cSzyIiQ7Tp9PZTl1gHh281ZWf%0AP7d2%2BfuJMlkjtM7oAwf%2BtI8CAwEAAQ%3D%3D%0A-----END%20PUBLIC%20KEY----- + } else if parsedUrl.host == "secureid" { if let components = URLComponents(string: "/?" + query) { var botId: Int32? var scope: String? - var callbackUrl: String? var publicKey: String? if let queryItems = components.queryItems { for queryItem in queryItems { @@ -239,8 +237,6 @@ public func openExternalUrl(account: Account, url: String, presentationData: Pre botId = Int32(value) } else if queryItem.name == "scope" { scope = value - } else if queryItem.name == "callback_url" { - callbackUrl = value } else if queryItem.name == "public_key" { publicKey = value } @@ -248,9 +244,8 @@ public func openExternalUrl(account: Account, url: String, presentationData: Pre } } - if let botId = botId, let scope = scope { - let scopes = scope.split(separator: ",").map(String.init).map { $0.trimmingCharacters(in: .whitespaces) } - let controller = SecureIdAuthController(account: account, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: botId), scope: scopes, callbackUrl: callbackUrl, publicKey: publicKey) + if let botId = botId, let scope = scope, let publicKey = publicKey { + let controller = SecureIdAuthController(account: account, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: botId), scope: scope, publicKey: publicKey) if let navigationController = navigationController { (navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root), with: nil) diff --git a/TelegramUI/SecureIdAuthController.swift b/TelegramUI/SecureIdAuthController.swift index ab691d755e..6cbb8fa91c 100644 --- a/TelegramUI/SecureIdAuthController.swift +++ b/TelegramUI/SecureIdAuthController.swift @@ -6,10 +6,12 @@ import Postbox import TelegramCore final class SecureIdAuthControllerInteraction { + let updateState: ((SecureIdAuthControllerState) -> SecureIdAuthControllerState) -> Void let present: (ViewController, Any?) -> Void let checkPassword: (String) -> Void - fileprivate init(present: @escaping (ViewController, Any?) -> Void, checkPassword: @escaping (String) -> Void) { + fileprivate init(updateState: @escaping ((SecureIdAuthControllerState) -> SecureIdAuthControllerState) -> Void, present: @escaping (ViewController, Any?) -> Void, checkPassword: @escaping (String) -> Void) { + self.updateState = updateState self.present = present self.checkPassword = checkPassword } @@ -32,11 +34,11 @@ final class SecureIdAuthController: ViewController { private let hapticFeedback = HapticFeedback() - init(account: Account, peerId: PeerId, scope: [String], callbackUrl: String?, publicKey: String?) { + init(account: Account, peerId: PeerId, scope: String, publicKey: String) { self.account = account self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - self.state = SecureIdAuthControllerState(formData: nil, verificationState: nil) + self.state = SecureIdAuthControllerState(encryptedFormData: nil, formData: nil, verificationState: nil) super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) @@ -60,13 +62,13 @@ final class SecureIdAuthController: ViewController { } })) - self.formDisposable = (requestSecureIdForm(postbox: account.postbox, network: account.network, peerId: peerId, scope: scope, origin: callbackUrl, packageName: nil, bundleId: nil, publicKey: publicKey) - |> mapToSignal { form -> Signal in - return account.postbox.modify { modifier -> Signal in + self.formDisposable = (requestSecureIdForm(postbox: account.postbox, network: account.network, peerId: peerId, scope: scope, publicKey: publicKey) + |> mapToSignal { form -> Signal in + return account.postbox.modify { modifier -> Signal in guard let accountPeer = modifier.getPeer(account.peerId), let servicePeer = modifier.getPeer(form.peerId) else { return .fail(.generic) } - return .single(SecureIdFormData(form: form, accountPeer: accountPeer, servicePeer: servicePeer)) + return .single(SecureIdEncryptedFormData(form: form, accountPeer: accountPeer, servicePeer: servicePeer)) } |> mapError { _ in return RequestSecureIdFormError.generic } |> switchToLatest @@ -75,7 +77,7 @@ final class SecureIdAuthController: ViewController { if let strongSelf = self { strongSelf.updateState { state in var state = state - state.formData = formData + state.encryptedFormData = formData return state } } @@ -113,7 +115,9 @@ final class SecureIdAuthController: ViewController { } override func loadDisplayNode() { - let interaction = SecureIdAuthControllerInteraction(present: { [weak self] c, a in + let interaction = SecureIdAuthControllerInteraction(updateState: { [weak self] f in + self?.updateState(f) + }, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) }, checkPassword: { [weak self] password in if let strongSelf = self { @@ -135,6 +139,7 @@ final class SecureIdAuthController: ViewController { strongSelf.updateState { state in var state = state state.verificationState = .verified(context) + state.formData = state.encryptedFormData.flatMap({ decryptedSecureIdForm(context: context, form: $0.form) }) return state } } diff --git a/TelegramUI/SecureIdAuthControllerNode.swift b/TelegramUI/SecureIdAuthControllerNode.swift index 6e90954484..2b70361383 100644 --- a/TelegramUI/SecureIdAuthControllerNode.swift +++ b/TelegramUI/SecureIdAuthControllerNode.swift @@ -18,6 +18,8 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { private var scheduledLayoutTransitionRequestId: Int = 0 private var scheduledLayoutTransitionRequest: (Int, ContainedViewLayoutTransition)? + private var state: SecureIdAuthControllerState? + init(account: Account, presentationData: PresentationData, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, interaction: SecureIdAuthControllerInteraction) { self.account = account self.presentationData = presentationData @@ -54,7 +56,8 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { let headerHeight = self.headerNode.updateLayout(width: layout.size.width, transition: headerNodeTransition) if let contentNode = self.contentNode { - let contentNodeTransition: ContainedViewLayoutTransition = contentNode.bounds.isEmpty ? .immediate : transition + let contentFirstTime = contentNode.bounds.isEmpty + let contentNodeTransition: ContainedViewLayoutTransition = contentFirstTime ? .immediate : transition let contentLayout = contentNode.updateLayout(width: layout.size.width, transition: contentNodeTransition) let contentSpacing: CGFloat = 70.0 @@ -66,12 +69,23 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { headerNodeTransition.updateFrame(node: self.headerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: boundingRect.minY), size: CGSize(width: boundingRect.width, height: headerHeight))) contentNodeTransition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: boundingRect.minY + headerHeight + contentSpacing), size: CGSize(width: boundingRect.width, height: contentLayout.height))) + + if contentFirstTime { + contentNode.didAppear() + if transition.isAnimated { + contentNode.animateIn() + if !(contentNode is SecureIdAuthPasswordOptionContentNode) { + transition.animateOffsetAdditive(node: contentNode, offset: -layout.size.height) + } + } + } } } func transitionToContentNode(_ contentNode: (ASDisplayNode & SecureIdAuthContentNode)?, transition: ContainedViewLayoutTransition) { if let current = self.contentNode { current.willDisappear() + current.layer.animateBoundsOriginYAdditive(from: 0.0, to: -self.bounds.height, duration: 0.3, removeOnCompletion: false) current.animateOut { [weak current] in current?.removeFromSupernode() } @@ -81,52 +95,74 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { if let contentNode = self.contentNode { self.addSubnode(contentNode) - if let (layout, navigationBarHeight) = self.validLayout { + if let _ = self.validLayout { self.scheduleLayoutTransitionRequest(.animated(duration: 0.5, curve: .spring)) - contentNode.didAppear() - contentNode.animateIn() } } } func updateState(_ state: SecureIdAuthControllerState, transition: ContainedViewLayoutTransition) { - if let formData = state.formData, let verificationState = state.verificationState { + self.state = state + + if let encryptedFormData = state.encryptedFormData, let verificationState = state.verificationState { if self.headerNode.supernode == nil { self.addSubnode(self.headerNode) self.headerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } - self.headerNode.updateState(formData: formData, verificationState: verificationState) + self.headerNode.updateState(formData: encryptedFormData, verificationState: verificationState) + + var contentNode: (ASDisplayNode & SecureIdAuthContentNode)? switch verificationState { case let .passwordChallenge(challengeState): - if let contentNode = self.contentNode as? SecureIdAuthPasswordOptionContentNode { - contentNode.updateIsChecking(challengeState == .checking) + if let current = self.contentNode as? SecureIdAuthPasswordOptionContentNode { + current.updateIsChecking(challengeState == .checking) + contentNode = current } else { - let contentNode = SecureIdAuthPasswordOptionContentNode(theme: presentationData.theme, strings: presentationData.strings, checkPassword: { [weak self] password in + let current = SecureIdAuthPasswordOptionContentNode(theme: presentationData.theme, strings: presentationData.strings, checkPassword: { [weak self] password in if let strongSelf = self { strongSelf.interaction.checkPassword(password) } }) - contentNode.updateIsChecking(challengeState == .checking) - self.transitionToContentNode(contentNode, transition: transition) - + current.updateIsChecking(challengeState == .checking) + contentNode = current } case .noChallenge: - if self.contentNode != nil { - self.transitionToContentNode(nil, transition: transition) - } + contentNode = nil case .verified: - if let contentNode = self.contentNode as? SecureIdAuthFormContentNode { - - } else { - let contentNode = SecureIdAuthFormContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, form: formData.form, openField: { [weak self] type in - if let strongSelf = self { - strongSelf.interaction.present(SecureIdIdentityFormController(account: strongSelf.account, data: nil), nil) - } - }) - self.transitionToContentNode(contentNode, transition: transition) + if let formData = state.formData { + if let current = self.contentNode as? SecureIdAuthFormContentNode { + contentNode = current + } else { + let current = SecureIdAuthFormContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, form: formData, openField: { [weak self] type in + if let strongSelf = self, let state = strongSelf.state, let verificationState = state.verificationState, case let .verified(context) = verificationState, let formData = state.formData { + strongSelf.interaction.present(SecureIdIdentityFormController(account: strongSelf.account, context: context, type: .passport, value: findIdentity(formData.values)?.1, updatedValue: { value in + if let strongSelf = self { + strongSelf.interaction.updateState { state in + if let formData = state.formData { + var values = formData.values + while let index = findIdentity(formData.values)?.0 { + values.remove(at: index) + } + if let value = value { + values.append(.identity(value)) + } + return SecureIdAuthControllerState(encryptedFormData: state.encryptedFormData, formData: SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: values), verificationState: state.verificationState) + } + return state + } + } + }), nil) + } + }) + contentNode = current + } } } + + if self.contentNode !== contentNode { + self.transitionToContentNode(contentNode, transition: transition) + } } } diff --git a/TelegramUI/SecureIdAuthControllerState.swift b/TelegramUI/SecureIdAuthControllerState.swift index f04d45a14b..45a8e03e41 100644 --- a/TelegramUI/SecureIdAuthControllerState.swift +++ b/TelegramUI/SecureIdAuthControllerState.swift @@ -2,8 +2,8 @@ import Foundation import Postbox import TelegramCore -struct SecureIdFormData { - let form: SecureIdForm +struct SecureIdEncryptedFormData { + let form: EncryptedSecureIdForm let accountPeer: Peer let servicePeer: Peer } @@ -44,7 +44,8 @@ enum SecureIdAuthControllerVerificationState: Equatable { } struct SecureIdAuthControllerState: Equatable { - var formData: SecureIdFormData? + var encryptedFormData: SecureIdEncryptedFormData? + var formData: SecureIdForm? var verificationState: SecureIdAuthControllerVerificationState? static func ==(lhs: SecureIdAuthControllerState, rhs: SecureIdAuthControllerState) -> Bool { @@ -52,6 +53,18 @@ struct SecureIdAuthControllerState: Equatable { return false } + if (lhs.encryptedFormData != nil) != (rhs.encryptedFormData != nil) { + return false + } + + if let lhsFormData = lhs.formData, let rhsFormData = rhs.formData { + if lhsFormData != rhsFormData { + return false + } + } else if (lhs.formData != nil) != (rhs.formData != nil) { + return false + } + if lhs.verificationState != rhs.verificationState { return false } diff --git a/TelegramUI/SecureIdAuthFormContentNode.swift b/TelegramUI/SecureIdAuthFormContentNode.swift index 45ae90e441..90ff5ba776 100644 --- a/TelegramUI/SecureIdAuthFormContentNode.swift +++ b/TelegramUI/SecureIdAuthFormContentNode.swift @@ -13,16 +13,16 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode, private var validLayout: CGFloat? - init(theme: PresentationTheme, strings: PresentationStrings, form: SecureIdForm, openField: @escaping (SecureIdFieldType) -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, form: SecureIdForm, openField: @escaping (SecureIdRequestedFormField) -> Void) { self.fieldBackgroundNode = ASDisplayNode() self.fieldBackgroundNode.isLayerBacked = true self.fieldBackgroundNode.backgroundColor = theme.list.itemBlocksBackgroundColor var fieldNodes: [SecureIdAuthFormFieldNode] = [] - for field in form.fields { - fieldNodes.append(SecureIdAuthFormFieldNode(theme: theme, strings: strings, field: field, selected: { - openField(field.type) + for type in form.requestedFields { + fieldNodes.append(SecureIdAuthFormFieldNode(theme: theme, strings: strings, type: type, values: form.values, selected: { + openField(type) })) } diff --git a/TelegramUI/SecureIdAuthFormFieldNode.swift b/TelegramUI/SecureIdAuthFormFieldNode.swift index f8f7679239..876f8941cf 100644 --- a/TelegramUI/SecureIdAuthFormFieldNode.swift +++ b/TelegramUI/SecureIdAuthFormFieldNode.swift @@ -6,11 +6,11 @@ import TelegramCore private let titleFont = Font.regular(17.0) private let textFont = Font.regular(15.0) -private func fieldTitleAndText(field: SecureIdField, strings: PresentationStrings) -> (String, String) { +private func fieldTitleAndText(type: SecureIdRequestedFormField, strings: PresentationStrings) -> (String, String) { let title: String let placeholder: String - switch field.type { + switch type { case .identity: title = strings.SecureId_FormFieldIdentity placeholder = strings.SecureId_FormFieldIdentityPlaceholder @@ -40,7 +40,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode { private let buttonNode: HighlightableButtonNode - init(theme: PresentationTheme, strings: PresentationStrings, field: SecureIdField, selected: @escaping () -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, type: SecureIdRequestedFormField, values: [SecureIdValue], selected: @escaping () -> Void) { self.selected = selected self.topSeparatorNode = ASDisplayNode() @@ -77,7 +77,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode { self.addSubnode(self.textNode) self.addSubnode(self.buttonNode) - let (title, text) = fieldTitleAndText(field: field, strings: strings) + let (title, text) = fieldTitleAndText(type: type, strings: strings) self.titleNode.attributedText = NSAttributedString(string: title, font: titleFont, textColor: theme.list.itemPrimaryTextColor) self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: theme.list.itemSecondaryTextColor) diff --git a/TelegramUI/SecureIdAuthHeaderNode.swift b/TelegramUI/SecureIdAuthHeaderNode.swift index 08560c8992..01531fb4fd 100644 --- a/TelegramUI/SecureIdAuthHeaderNode.swift +++ b/TelegramUI/SecureIdAuthHeaderNode.swift @@ -47,7 +47,7 @@ final class SecureIdAuthHeaderNode: ASDisplayNode { self.addSubnode(self.textNode) } - func updateState(formData: SecureIdFormData, verificationState: SecureIdAuthControllerVerificationState) { + func updateState(formData: SecureIdEncryptedFormData, verificationState: SecureIdAuthControllerVerificationState) { self.accountAvatarNode.setPeer(account: self.account, peer: formData.accountPeer) self.serviceAvatarNode.setPeer(account: self.account, peer: formData.servicePeer) @@ -56,15 +56,15 @@ final class SecureIdAuthHeaderNode: ASDisplayNode { self.titleNode.attributedText = NSAttributedString(string: self.strings.SecureId_RequestTitle(formData.servicePeer.displayTitle, formData.servicePeer.displayTitle).0, font: titleFont, textColor: self.theme.list.freeTextColor) var scopeText = "" - for i in 0 ..< formData.form.fields.count { + for i in 0 ..< formData.form.requestedFields.count { if !scopeText.isEmpty { - if i == formData.form.fields.count - 1 { + if i == formData.form.requestedFields.count - 1 { scopeText.append(self.strings.SecureId_RequestScopeLastJoiner) } else { scopeText.append(", ") } } - switch formData.form.fields[i].type { + switch formData.form.requestedFields[i] { case .identity: scopeText.append(self.strings.SecureId_RequestScopeIdentity) case .address: @@ -88,12 +88,12 @@ final class SecureIdAuthHeaderNode: ASDisplayNode { let avatarSize = CGSize(width: 70.0, height: 70.0) if isVerified { - transition.updateAlpha(node: self.accountAvatarNode, alpha: 0.0) transition.updateAlpha(node: self.accountAvatarContainerNode, alpha: 0.0) - transition.updateSublayerTransformScale(node: self.accountAvatarContainerNode, scale: 0.1) - transition.updateFrame(node: self.accountAvatarContainerNode, frame: CGRect(origin: CGPoint(x: -avatarSize.width, y: 0.0), size: avatarSize)) + transition.updateSublayerTransformScale(node: self.accountAvatarContainerNode, scale: 0.3) transition.updateFrame(node: self.accountAvatarNode, frame: CGRect(origin: CGPoint(), size: avatarSize)) - transition.updateFrame(node: self.serviceAvatarNode, frame: CGRect(origin: CGPoint(x: floor((width - avatarSize.width) / 2.0), y: 0.0), size: avatarSize)) + let serviceAvatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize.width) / 2.0), y: 0.0), size: avatarSize) + transition.updateFrame(node: self.serviceAvatarNode, frame: serviceAvatarFrame) + transition.updateFrame(node: self.accountAvatarContainerNode, frame: serviceAvatarFrame) } else { transition.updateAlpha(node: self.accountAvatarContainerNode, alpha: 1.0) transition.updateSublayerTransformScale(node: self.accountAvatarContainerNode, scale: 1.0) diff --git a/TelegramUI/SecureIdIdentityFormController.swift b/TelegramUI/SecureIdIdentityFormController.swift index a65a8ab184..40acb59bbc 100644 --- a/TelegramUI/SecureIdIdentityFormController.swift +++ b/TelegramUI/SecureIdIdentityFormController.swift @@ -5,29 +5,36 @@ import SwiftSignalKit import Postbox import TelegramCore -final class SecureIdIdentityFormController: ViewController { - private var controllerNode: SecureIdIdentityFormControllerNode { - return self.displayNode as! SecureIdIdentityFormControllerNode - } - +enum SecureIdIdentityFormType { + case passport +} + +final class SecureIdIdentityFormController: FormController { private let account: Account private var presentationData: PresentationData + private let updatedValue: (SecureIdIdentityValue?) -> Void - private var data: SecureIdIdentityData? + private let context: SecureIdAccessContext + private let type: SecureIdIdentityFormType + private var value: SecureIdIdentityValue? - private var didPlayPresentationAnimation = false + private var doneItem: UIBarButtonItem? - init(account: Account, data: SecureIdIdentityData?) { + init(account: Account, context: SecureIdAccessContext, type: SecureIdIdentityFormType, value: SecureIdIdentityValue?, updatedValue: @escaping (SecureIdIdentityValue?) -> Void) { self.account = account self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - self.data = data + self.context = context + self.type = type + self.value = value + self.updatedValue = updatedValue - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) - - self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style + super.init(presentationData: self.presentationData) self.title = self.presentationData.strings.SecureId_Title self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + + self.doneItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) + self.navigationItem.rightBarButtonItem = doneItem } required init(coder aDecoder: NSCoder) { @@ -37,37 +44,11 @@ final class SecureIdIdentityFormController: ViewController { deinit { } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if !self.didPlayPresentationAnimation { - self.didPlayPresentationAnimation = true - self.controllerNode.animateIn() - } - } - - override func dismiss(completion: (() -> Void)? = nil) { - self.controllerNode.animateOut(completion: { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: nil) - completion?() - }) - } - - override func loadDisplayNode() { - self.displayNode = SecureIdIdentityFormControllerNode(account: self.account, data: self.data, theme: self.presentationData.theme, strings: self.presentationData.strings, dismiss: { [weak self] in - self?.dismiss() - }, present: { [weak self] c, a in - self?.present(c, in: .window(.root), with: a) - }) - } - - override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition) - } - @objc private func cancelPressed() { self.dismiss() } + + @objc private func donePressed() { + self.controllerNode.verify() + } } diff --git a/TelegramUI/SecureIdIdentityFormControllerNode.swift b/TelegramUI/SecureIdIdentityFormControllerNode.swift index 253100ae3d..2b7b445b34 100644 --- a/TelegramUI/SecureIdIdentityFormControllerNode.swift +++ b/TelegramUI/SecureIdIdentityFormControllerNode.swift @@ -5,291 +5,85 @@ import TelegramCore import Postbox import SwiftSignalKit -enum SecureIdIdentityFormFocus { - case name - case surname -} - -private final class SecureIdIdentityFormItems { - let header: BotPaymentHeaderItemNode - let name: BotPaymentFieldItemNode - let surname: BotPaymentFieldItemNode - let birthdate: BotPaymentDisclosureItemNode - let gender: BotPaymentDisclosureItemNode - let citizenship: BotPaymentDisclosureItemNode - - let documentsHeader: BotPaymentHeaderItemNode - let uploadDocumentItem: BotPaymentActionItemNode - let documentsInfoItem: BotPaymentTextItemNode - - var items: [[BotPaymentItemNode]] { - return [[ - self.header, - self.name, - self.surname, - self.birthdate, - self.gender, - self.citizenship - ], [ - self.documentsHeader, - self.uploadDocumentItem, - self.documentsInfoItem - ]] +final class SecureIdIdentityFormState: FormControllerInnerState { + func isEqual(to: SecureIdIdentityFormState) -> Bool { + return false } - init(strings: PresentationStrings, openBirthdateSelection: @escaping () -> Void, openGenderSelection: @escaping () -> Void, openCitizenshipSelection: @escaping () -> Void, openUploadDocument: @escaping () -> Void) { - self.header = BotPaymentHeaderItemNode(text: "PERSONAL DETAILS") - self.name = BotPaymentFieldItemNode(title: "Name", placeholder: "Name") - self.surname = BotPaymentFieldItemNode(title: "Surname", placeholder: "Surname") - self.birthdate = BotPaymentDisclosureItemNode(title: "Date of Birth", placeholder: "Date of Birth", text: "") - self.gender = BotPaymentDisclosureItemNode(title: "Gender", placeholder: "Gender", text: "") - self.citizenship = BotPaymentDisclosureItemNode(title: "Nationality", placeholder: "Nationality", text: "") - - self.documentsHeader = BotPaymentHeaderItemNode(text: "DOCUMENTS") - self.uploadDocumentItem = BotPaymentActionItemNode(title: "Upload New Document") - self.documentsInfoItem = BotPaymentTextItemNode(text: "To confirm your identity you need to upload a photograph or scan of your Passport, ID card, or Driver's license.\n\nYour Document must contain:\n• Your photograph;\n• Your legal name (as in profile);\n• Your date of birth;\n• Your nationality;\n• Date of issue;\n• Document number.") - - self.birthdate.action = { - openBirthdateSelection() - } - - self.gender.action = { - openGenderSelection() - } - - self.citizenship.action = { - openCitizenshipSelection() - } - - self.uploadDocumentItem.action = { - openUploadDocument() - } + func entries() -> [FormControllerItemEntry] { + return [.entry(SecureIdIdentityFormEntry.scansHeader)] } } -private final class SecureIdIdentityFormScrollerNodeView: UIScrollView { - var ignoreUpdateBounds = false +enum SecureIdIdentityFormEntryId: Hashable { + case scansHeader + case scan(Int) - override init(frame: CGRect) { - super.init(frame: frame) - - if #available(iOSApplicationExtension 11.0, *) { - self.contentInsetAdjustmentBehavior = .never - } - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override var bounds: CGRect { - get { - return super.bounds - } set(value) { - if !self.ignoreUpdateBounds { - super.bounds = value - } - } - } - - override func scrollRectToVisible(_ rect: CGRect, animated: Bool) { - } -} - -private final class SecureIdIdentityFormScrollerNode: ASDisplayNode { - override var view: SecureIdIdentityFormScrollerNodeView { - return super.view as! SecureIdIdentityFormScrollerNodeView - } - - override init() { - super.init() - - self.setViewBlock({ - return SecureIdIdentityFormScrollerNodeView(frame: CGRect()) - }) - } -} - -final class SecureIdIdentityFormControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { - private let account: Account - - private var focus: SecureIdIdentityFormFocus? - - private let dismiss: () -> Void - private let present: (ViewController, Any?) -> Void - - private var theme: PresentationTheme - private var strings: PresentationStrings - - private var containerLayout: (ContainerViewLayout, CGFloat)? - - private let scrollNode: SecureIdIdentityFormScrollerNode - private let itemNodes: [[BotPaymentItemNode]] - - private let formItems: SecureIdIdentityFormItems - private var data: SecureIdIdentityData? - - private let verifyDisposable = MetaDisposable() - private var isVerifying = false - - init(account: Account, data: SecureIdIdentityData?, theme: PresentationTheme, strings: PresentationStrings, dismiss: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void) { - self.account = account - self.data = data - //self.focus = .name - self.dismiss = dismiss - self.present = present - - self.theme = theme - self.strings = strings - - self.scrollNode = SecureIdIdentityFormScrollerNode() - - self.formItems = SecureIdIdentityFormItems(strings: strings, openBirthdateSelection: { - }, openGenderSelection: { - }, openCitizenshipSelection: { - }, openUploadDocument: { - - }) - - self.itemNodes = self.formItems.items - - for items in itemNodes { - for item in items { - self.scrollNode.addSubnode(item) - } - } - - super.init() - - self.backgroundColor = self.theme.list.blocksBackgroundColor - self.scrollNode.backgroundColor = nil - self.scrollNode.isOpaque = false - self.scrollNode.view.alwaysBounceVertical = true - self.scrollNode.view.showsVerticalScrollIndicator = false - self.scrollNode.view.showsHorizontalScrollIndicator = false - self.scrollNode.view.delegate = self - - self.addSubnode(self.scrollNode) - - for items in itemNodes { - for item in items { - if let item = item as? BotPaymentFieldItemNode { - item.textUpdated = { [weak self] in - self?.updateDone() - } + static func ==(lhs: SecureIdIdentityFormEntryId, rhs: SecureIdIdentityFormEntryId) -> Bool { + switch lhs { + case .scansHeader: + if case .scansHeader = rhs { + return true + } else { + return false + } + case let .scan(index): + if case .scan(index) = rhs { + return true + } else { + return false } - } } - - self.updateDone() } - deinit { - self.verifyDisposable.dispose() + var hashValue: Int { + switch self { + case .scansHeader: + return 0 + case let .scan(index): + return index.hashValue + } + } +} + +enum SecureIdIdentityFormEntry: FormControllerEntry { + case scansHeader + + var stableId: SecureIdIdentityFormEntryId { + switch self { + case .scansHeader: + return .scansHeader + } + } + + func isEqual(to: SecureIdIdentityFormEntry) -> Bool { + switch self { + case .scansHeader: + if case .scansHeader = to { + return true + } else { + return false + } + } + } + + func item(strings: PresentationStrings) -> FormControllerItem { + switch self { + case .scansHeader: + return FormControllerHeaderItem(text: "SCANS") + } + } +} + +final class SecureIdIdentityFormControllerNode: FormControllerNode { + required init(theme: PresentationTheme, strings: PresentationStrings) { + super.init(theme: theme, strings: strings) + + self.updateInnerState(transition: .immediate, with: SecureIdIdentityFormState()) } func verify() { - self.isVerifying = true - self.updateDone() - } - - private func updateDone() { - var enabled = true - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - let previousLayout = self.containerLayout - self.containerLayout = (layout, navigationBarHeight) - var insets = layout.insets(options: [.input]) - insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top) - - var contentHeight: CGFloat = 0.0 - - var commonInset: CGFloat = 0.0 - for items in self.itemNodes { - for item in items { - commonInset = max(commonInset, item.measureInset(theme: self.theme, width: layout.size.width)) - } - } - - for items in self.itemNodes { - if !items.isEmpty && items[0] is BotPaymentHeaderItemNode { - contentHeight += 24.0 - } else { - contentHeight += 32.0 - } - - for i in 0 ..< items.count { - let item = items[i] - let itemHeight = item.updateLayout(theme: self.theme, width: layout.size.width, measuredInset: commonInset, previousItemNode: i == 0 ? nil : items[i - 1], nextItemNode: i == (items.count - 1) ? nil : items[i + 1], transition: transition) - transition.updateFrame(node: item, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: itemHeight))) - contentHeight += itemHeight - } - } - - contentHeight += 24.0 - - let scrollContentSize = CGSize(width: layout.size.width, height: contentHeight) - - let previousBoundsOrigin = self.scrollNode.bounds.origin - self.scrollNode.view.ignoreUpdateBounds = true - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - self.scrollNode.view.contentSize = scrollContentSize - self.scrollNode.view.contentInset = insets - self.scrollNode.view.scrollIndicatorInsets = insets - self.scrollNode.view.ignoreUpdateBounds = false - - if let focus = self.focus { - var focusItem: ASDisplayNode? - switch focus { - case .name: - focusItem = self.formItems.name - case .surname: - focusItem = self.formItems.surname - } - if let focusItem = focusItem { - let scrollVisibleSize = CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom) - var contentOffset = CGPoint(x: 0.0, y: -insets.top + floor(focusItem.frame.midY - scrollVisibleSize.height / 2.0)) - contentOffset.y = min(contentOffset.y, scrollContentSize.height + insets.bottom - layout.size.height) - contentOffset.y = max(contentOffset.y, -insets.top) - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: contentOffset.y), size: layout.size)) - - if previousLayout == nil, let focusItem = focusItem as? BotPaymentFieldItemNode { - focusItem.activateInput() - } - } - } else if let previousLayout = previousLayout { - var previousInsets = previousLayout.0.insets(options: [.input]) - previousInsets.top += max(previousLayout.1, previousLayout.0.insets(options: [.statusBar]).top) - let insetsScrollOffset = insets.top - previousInsets.top - - var contentOffset = CGPoint(x: 0.0, y: previousBoundsOrigin.y + insetsScrollOffset) - contentOffset.y = min(contentOffset.y, scrollContentSize.height + insets.bottom - layout.size.height) - contentOffset.y = max(contentOffset.y, -insets.top) - - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: contentOffset.y), size: layout.size)) - } else { - 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)) - } - } - - func animateIn() { - self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) - } - - func animateOut(completion: (() -> Void)? = nil) { - self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { [weak self] _ in - if let strongSelf = self { - strongSelf.dismiss() - } - completion?() - }) - } - - func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - self.focus = nil } }