mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
no message
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
D093D7E12062F40100BC3599 /* SecureIdIdentityFormControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdIdentityFormControllerNode.swift; sourceTree = "<group>"; };
|
||||
D093D7E62063E57F00BC3599 /* BotPaymentActionItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BotPaymentActionItemNode.swift; sourceTree = "<group>"; };
|
||||
D093D80720654A2900BC3599 /* BotPaymentDateItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BotPaymentDateItemNode.swift; sourceTree = "<group>"; };
|
||||
D093D81C206994FD00BC3599 /* FindSecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindSecureIdValue.swift; sourceTree = "<group>"; };
|
||||
D093D81F20699A7300BC3599 /* FormController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormController.swift; sourceTree = "<group>"; };
|
||||
D093D82120699A7C00BC3599 /* FormControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormControllerNode.swift; sourceTree = "<group>"; };
|
||||
D093D8232069A06600BC3599 /* FormControllerScrollerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormControllerScrollerNode.swift; sourceTree = "<group>"; };
|
||||
D093D8252069A31700BC3599 /* FormControllerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormControllerItem.swift; sourceTree = "<group>"; };
|
||||
D0943AF51FDAAE7E001522CC /* MultipleAvatarsNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleAvatarsNode.swift; sourceTree = "<group>"; };
|
||||
D0943AFD1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMultipleAvatarsNavigationNode.swift; sourceTree = "<group>"; };
|
||||
D0943AFF1FDAE852001522CC /* ChatFeedNavigationInputPanelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatFeedNavigationInputPanelNode.swift; sourceTree = "<group>"; };
|
||||
@@ -1565,6 +1578,7 @@
|
||||
D0E305AE1E5BA8E000D7A3A2 /* ItemListLoadingIndicatorEmptyStateItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListLoadingIndicatorEmptyStateItem.swift; sourceTree = "<group>"; };
|
||||
D0E35A061DE4803400BC6096 /* VerticalListContextResultsChatInputContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalListContextResultsChatInputContextPanelNode.swift; sourceTree = "<group>"; };
|
||||
D0E35A081DE4804900BC6096 /* VerticalListContextResultsChatInputPanelItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalListContextResultsChatInputPanelItem.swift; sourceTree = "<group>"; };
|
||||
D0E412C52069B60600BEE4A2 /* FormControllerHeaderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormControllerHeaderItem.swift; sourceTree = "<group>"; };
|
||||
D0E7A1BC1D8C246D00C37A6F /* ChatHistoryListNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryListNode.swift; sourceTree = "<group>"; };
|
||||
D0E7A1BE1D8C24B900C37A6F /* ChatHistoryViewForLocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryViewForLocation.swift; sourceTree = "<group>"; };
|
||||
D0E7A1C01D8C258D00C37A6F /* ChatHistoryEntriesForView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryEntriesForView.swift; sourceTree = "<group>"; };
|
||||
@@ -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 = "<group>";
|
||||
};
|
||||
D093D81E20699A6000BC3599 /* Form */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D093D81F20699A7300BC3599 /* FormController.swift */,
|
||||
D093D82120699A7C00BC3599 /* FormControllerNode.swift */,
|
||||
D093D8232069A06600BC3599 /* FormControllerScrollerNode.swift */,
|
||||
D093D8252069A31700BC3599 /* FormControllerItem.swift */,
|
||||
D0E412C52069B60600BEE4A2 /* FormControllerHeaderItem.swift */,
|
||||
);
|
||||
name = Form;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
@@ -3465,7 +3492,9 @@
|
||||
D0F69DD31D6B8A160046BCD6 /* Controllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D01B27931E38F3920022A4C0 /* Item List */,
|
||||
D0736F261DF4D2F300F2C02A /* Telegram Controller */,
|
||||
D093D81E20699A6000BC3599 /* Form */,
|
||||
);
|
||||
name = Controllers;
|
||||
sourceTree = "<group>";
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
23
TelegramUI/BotPaymentDateItemNode.swift
Normal file
23
TelegramUI/BotPaymentDateItemNode.swift
Normal file
@@ -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) }) ?? "")
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -33,6 +33,8 @@ final class BotPaymentFieldItemNode: BotPaymentItemNode {
|
||||
self.textField.textField.font = titleFont
|
||||
self.textField.textField.returnKeyType = .next
|
||||
|
||||
self.textField.textField.text = text
|
||||
|
||||
super.init(needsBackground: true)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
38
TelegramUI/FindSecureIdValue.swift
Normal file
38
TelegramUI/FindSecureIdValue.swift
Normal file
@@ -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
|
||||
}
|
||||
54
TelegramUI/FormController.swift
Normal file
54
TelegramUI/FormController.swift
Normal file
@@ -0,0 +1,54 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
class FormController<InnerState, Node: FormControllerNode<InnerState>>: 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)
|
||||
}
|
||||
}
|
||||
49
TelegramUI/FormControllerHeaderItem.swift
Normal file
49
TelegramUI/FormControllerHeaderItem.swift
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
22
TelegramUI/FormControllerItem.swift
Normal file
22
TelegramUI/FormControllerItem.swift
Normal file
@@ -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 {
|
||||
}
|
||||
280
TelegramUI/FormControllerNode.swift
Normal file
280
TelegramUI/FormControllerNode.swift
Normal file
@@ -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<InnerState: FormControllerInnerState> {
|
||||
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<InnerState: FormControllerInnerState> {
|
||||
let layoutState: FormControllerLayoutState
|
||||
let presentationState: FormControllerPresentationState
|
||||
let innerState: InnerState
|
||||
}
|
||||
|
||||
enum FormControllerItemEntry<Entry: FormControllerEntry> {
|
||||
case entry(Entry)
|
||||
case spacer
|
||||
}
|
||||
|
||||
protocol FormControllerInnerState {
|
||||
associatedtype Entry: FormControllerEntry
|
||||
|
||||
func isEqual(to: Self) -> Bool
|
||||
func entries() -> [FormControllerItemEntry<Entry>]
|
||||
}
|
||||
|
||||
private enum FilteredItemNeighbor {
|
||||
case spacer
|
||||
case item(FormControllerItem)
|
||||
}
|
||||
|
||||
class FormControllerNode<InnerState: FormControllerInnerState>: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||
private typealias InternalState = FormControllerInternalState<InnerState>
|
||||
typealias State = FormControllerState<InnerState>
|
||||
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<Int>()
|
||||
|
||||
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?()
|
||||
})
|
||||
}
|
||||
}
|
||||
60
TelegramUI/FormControllerScrollerNode.swift
Normal file
60
TelegramUI/FormControllerScrollerNode.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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<SecureIdFormData, RequestSecureIdFormError> in
|
||||
return account.postbox.modify { modifier -> Signal<SecureIdFormData, RequestSecureIdFormError> in
|
||||
self.formDisposable = (requestSecureIdForm(postbox: account.postbox, network: account.network, peerId: peerId, scope: scope, publicKey: publicKey)
|
||||
|> mapToSignal { form -> Signal<SecureIdEncryptedFormData, RequestSecureIdFormError> in
|
||||
return account.postbox.modify { modifier -> Signal<SecureIdEncryptedFormData, RequestSecureIdFormError> 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,53 +95,75 @@ 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 {
|
||||
|
||||
if let formData = state.formData {
|
||||
if let current = self.contentNode as? SecureIdAuthFormContentNode {
|
||||
contentNode = current
|
||||
} else {
|
||||
let contentNode = SecureIdAuthFormContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, form: formData.form, openField: { [weak self] type in
|
||||
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.present(SecureIdIdentityFormController(account: strongSelf.account, data: nil), nil)
|
||||
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)
|
||||
}
|
||||
})
|
||||
self.transitionToContentNode(contentNode, transition: transition)
|
||||
contentNode = current
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.contentNode !== contentNode {
|
||||
self.transitionToContentNode(contentNode, transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<SecureIdIdentityFormState, SecureIdIdentityFormControllerNode> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,291 +5,85 @@ import TelegramCore
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
enum SecureIdIdentityFormFocus {
|
||||
case name
|
||||
case surname
|
||||
final class SecureIdIdentityFormState: FormControllerInnerState {
|
||||
func isEqual(to: SecureIdIdentityFormState) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func entries() -> [FormControllerItemEntry<SecureIdIdentityFormEntry>] {
|
||||
return [.entry(SecureIdIdentityFormEntry.scansHeader)]
|
||||
}
|
||||
}
|
||||
|
||||
private final class SecureIdIdentityFormItems {
|
||||
let header: BotPaymentHeaderItemNode
|
||||
let name: BotPaymentFieldItemNode
|
||||
let surname: BotPaymentFieldItemNode
|
||||
let birthdate: BotPaymentDisclosureItemNode
|
||||
let gender: BotPaymentDisclosureItemNode
|
||||
let citizenship: BotPaymentDisclosureItemNode
|
||||
enum SecureIdIdentityFormEntryId: Hashable {
|
||||
case scansHeader
|
||||
case scan(Int)
|
||||
|
||||
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
|
||||
]]
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
var hashValue: Int {
|
||||
switch self {
|
||||
case .scansHeader:
|
||||
return 0
|
||||
case let .scan(index):
|
||||
return index.hashValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class SecureIdIdentityFormScrollerNodeView: UIScrollView {
|
||||
var ignoreUpdateBounds = false
|
||||
enum SecureIdIdentityFormEntry: FormControllerEntry {
|
||||
case scansHeader
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
self.contentInsetAdjustmentBehavior = .never
|
||||
var stableId: SecureIdIdentityFormEntryId {
|
||||
switch self {
|
||||
case .scansHeader:
|
||||
return .scansHeader
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
func isEqual(to: SecureIdIdentityFormEntry) -> Bool {
|
||||
switch self {
|
||||
case .scansHeader:
|
||||
if case .scansHeader = to {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func scrollRectToVisible(_ rect: CGRect, animated: Bool) {
|
||||
func item(strings: PresentationStrings) -> FormControllerItem {
|
||||
switch self {
|
||||
case .scansHeader:
|
||||
return FormControllerHeaderItem(text: "SCANS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class SecureIdIdentityFormScrollerNode: ASDisplayNode {
|
||||
override var view: SecureIdIdentityFormScrollerNodeView {
|
||||
return super.view as! SecureIdIdentityFormScrollerNodeView
|
||||
}
|
||||
final class SecureIdIdentityFormControllerNode: FormControllerNode<SecureIdIdentityFormState> {
|
||||
required init(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
super.init(theme: theme, strings: strings)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.updateDone()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.verifyDisposable.dispose()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user