mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 19:30:29 +00:00
no message
This commit is contained in:
parent
3a414eeea2
commit
0648840a26
@ -4,10 +4,14 @@ import AVFoundation
|
|||||||
import Postbox
|
import Postbox
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
final class CallKitIntegration {
|
public final class CallKitIntegration {
|
||||||
private let providerDelegate: AnyObject
|
private let providerDelegate: AnyObject
|
||||||
|
|
||||||
public static var isAvailable: Bool {
|
public static var isAvailable: Bool {
|
||||||
|
#if (arch(i386) || arch(x86_64)) && os(iOS)
|
||||||
|
return false
|
||||||
|
#endif
|
||||||
|
|
||||||
if #available(iOSApplicationExtension 10.0, *) {
|
if #available(iOSApplicationExtension 10.0, *) {
|
||||||
return Locale.current.regionCode?.lowercased() != "cn"
|
return Locale.current.regionCode?.lowercased() != "cn"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -406,7 +406,7 @@ private func universalServiceMessageString(theme: PresentationTheme?, strings: P
|
|||||||
typesString.append(strings.Notification_PassportValueEmail)
|
typesString.append(strings.Notification_PassportValueEmail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
attributedString = NSAttributedString(string: strings.Notification_PassportValuesSentMessage(message.author?.compactDisplayTitle ?? "", typesString).0, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: strings.Notification_PassportValuesSentMessage(message.peers[message.id.peerId]?.compactDisplayTitle ?? "", typesString).0, font: titleFont, textColor: primaryTextColor)
|
||||||
case .unknown:
|
case .unknown:
|
||||||
attributedString = nil
|
attributedString = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import LegacyComponents
|
|||||||
public enum DeviceAccessMicrophoneSubject {
|
public enum DeviceAccessMicrophoneSubject {
|
||||||
case audio
|
case audio
|
||||||
case video
|
case video
|
||||||
|
case voiceCall
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DeviceAccessMediaLibrarySubject {
|
public enum DeviceAccessMediaLibrarySubject {
|
||||||
@ -48,7 +49,7 @@ public final class DeviceAccess {
|
|||||||
return self.contactsPromise.get()
|
return self.contactsPromise.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func authorizeAccess(to subject: DeviceAccessSubject, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void, openSettings: @escaping () -> Void, _ completion: @escaping (Bool) -> Void) {
|
public static func authorizeAccess(to subject: DeviceAccessSubject, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void, openSettings: @escaping () -> Void, displayNotificatoinFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void) {
|
||||||
switch subject {
|
switch subject {
|
||||||
case .camera:
|
case .camera:
|
||||||
let status = PGCamera.cameraAuthorizationStatus()
|
let status = PGCamera.cameraAuthorizationStatus()
|
||||||
@ -96,10 +97,15 @@ public final class DeviceAccess {
|
|||||||
text = presentationData.strings.AccessDenied_VoiceMicrophone
|
text = presentationData.strings.AccessDenied_VoiceMicrophone
|
||||||
case .video:
|
case .video:
|
||||||
text = presentationData.strings.AccessDenied_VideoMicrophone
|
text = presentationData.strings.AccessDenied_VideoMicrophone
|
||||||
|
case .voiceCall:
|
||||||
|
text = presentationData.strings.AccessDenied_CallMicrophone
|
||||||
}
|
}
|
||||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||||
openSettings()
|
openSettings()
|
||||||
})]), nil)
|
})]), nil)
|
||||||
|
if case .voiceCall = microphoneSubject {
|
||||||
|
displayNotificatoinFromBackground(text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ private var telegramUIDeclaredEncodables: Void = {
|
|||||||
declareEncodable(ApplicationSpecificVariantNotice.self, f: { ApplicationSpecificVariantNotice(decoder: $0) })
|
declareEncodable(ApplicationSpecificVariantNotice.self, f: { ApplicationSpecificVariantNotice(decoder: $0) })
|
||||||
declareEncodable(ApplicationSpecificCounterNotice.self, f: { ApplicationSpecificCounterNotice(decoder: $0) })
|
declareEncodable(ApplicationSpecificCounterNotice.self, f: { ApplicationSpecificCounterNotice(decoder: $0) })
|
||||||
declareEncodable(CallListSettings.self, f: { CallListSettings(decoder: $0) })
|
declareEncodable(CallListSettings.self, f: { CallListSettings(decoder: $0) })
|
||||||
|
declareEncodable(VoiceCallSettings.self, f: { VoiceCallSettings(decoder: $0) })
|
||||||
declareEncodable(ExperimentalSettings.self, f: { ExperimentalSettings(decoder: $0) })
|
declareEncodable(ExperimentalSettings.self, f: { ExperimentalSettings(decoder: $0) })
|
||||||
declareEncodable(ExperimentalUISettings.self, f: { ExperimentalUISettings(decoder: $0) })
|
declareEncodable(ExperimentalUISettings.self, f: { ExperimentalUISettings(decoder: $0) })
|
||||||
declareEncodable(MusicPlaybackSettings.self, f: { MusicPlaybackSettings(decoder: $0) })
|
declareEncodable(MusicPlaybackSettings.self, f: { MusicPlaybackSettings(decoder: $0) })
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
|
||||||
func findValue(_ values: [SecureIdValueWithContext], key: SecureIdValueKey) -> (Int, SecureIdValue)? {
|
func findValue(_ values: [SecureIdValueWithContext], key: SecureIdValueKey) -> (Int, SecureIdValueWithContext)? {
|
||||||
for i in 0 ..< values.count {
|
for i in 0 ..< values.count {
|
||||||
if values[i].value.key == key {
|
if values[i].value.key == key {
|
||||||
return (i, values[i].value)
|
return (i, values[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -419,8 +419,9 @@ class ItemListController<Entry: ItemListNodeEntry>: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.didAppear?(!self.didAppearOnce)
|
let firstTime = !self.didAppearOnce
|
||||||
self.didAppearOnce = true
|
self.didAppearOnce = true
|
||||||
|
self.didAppear?(firstTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
|||||||
@ -331,19 +331,23 @@ class ItemListControllerNode<Entry: ItemListNodeEntry>: ViewControllerTracingNod
|
|||||||
updatedFocusItemTag = true
|
updatedFocusItemTag = true
|
||||||
}
|
}
|
||||||
if updatedFocusItemTag {
|
if updatedFocusItemTag {
|
||||||
strongSelf.appliedFocusItemTag = focusItemTag
|
|
||||||
if let focusItemTag = focusItemTag {
|
if let focusItemTag = focusItemTag {
|
||||||
|
var applied = false
|
||||||
strongSelf.listNode.forEachItemNode { itemNode in
|
strongSelf.listNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? ItemListItemNode {
|
if let itemNode = itemNode as? ItemListItemNode {
|
||||||
if let itemTag = itemNode.tag {
|
if let itemTag = itemNode.tag {
|
||||||
if itemTag.isEqual(to: focusItemTag) {
|
if itemTag.isEqual(to: focusItemTag) {
|
||||||
if let focusableNode = itemNode as? ItemListItemFocusableNode {
|
if let focusableNode = itemNode as? ItemListItemFocusableNode {
|
||||||
|
applied = true
|
||||||
focusableNode.focus()
|
focusableNode.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if applied {
|
||||||
|
strongSelf.appliedFocusItemTag = focusItemTag
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -420,6 +420,7 @@ private final class AudioPlayerRendererContext {
|
|||||||
var maximumFramesPerSlice: UInt32 = 4096
|
var maximumFramesPerSlice: UInt32 = 4096
|
||||||
AudioUnitSetProperty(converterAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maximumFramesPerSlice, 4)
|
AudioUnitSetProperty(converterAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maximumFramesPerSlice, 4)
|
||||||
AudioUnitSetProperty(timePitchAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maximumFramesPerSlice, 4)
|
AudioUnitSetProperty(timePitchAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maximumFramesPerSlice, 4)
|
||||||
|
AudioUnitSetProperty(outputAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maximumFramesPerSlice, 4)
|
||||||
|
|
||||||
guard AUGraphInitialize(audioGraph) == noErr else {
|
guard AUGraphInitialize(audioGraph) == noErr else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import Foundation
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
import Display
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
|
||||||
public enum PresentationCallState: Equatable {
|
public enum PresentationCallState: Equatable {
|
||||||
@ -176,11 +177,12 @@ public final class PresentationCall {
|
|||||||
private let audioSession: ManagedAudioSession
|
private let audioSession: ManagedAudioSession
|
||||||
private let callSessionManager: CallSessionManager
|
private let callSessionManager: CallSessionManager
|
||||||
private let callKitIntegration: CallKitIntegration?
|
private let callKitIntegration: CallKitIntegration?
|
||||||
|
private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
|
||||||
|
|
||||||
let internalId: CallSessionInternalId
|
public let internalId: CallSessionInternalId
|
||||||
let peerId: PeerId
|
public let peerId: PeerId
|
||||||
let isOutgoing: Bool
|
public let isOutgoing: Bool
|
||||||
let peer: Peer?
|
public let peer: Peer?
|
||||||
|
|
||||||
private var sessionState: CallSession?
|
private var sessionState: CallSession?
|
||||||
private var callContextState: OngoingCallContextState?
|
private var callContextState: OngoingCallContextState?
|
||||||
@ -231,10 +233,11 @@ public final class PresentationCall {
|
|||||||
private var droppedCall = false
|
private var droppedCall = false
|
||||||
private var dropCallKitCallTimer: SwiftSignalKit.Timer?
|
private var dropCallKitCallTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
init(audioSession: ManagedAudioSession, callSessionManager: CallSessionManager, callKitIntegration: CallKitIntegration?, internalId: CallSessionInternalId, peerId: PeerId, isOutgoing: Bool, peer: Peer?, allowP2P: Bool, proxyServer: ProxyServerSettings?, currentNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>) {
|
init(audioSession: ManagedAudioSession, callSessionManager: CallSessionManager, callKitIntegration: CallKitIntegration?, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), internalId: CallSessionInternalId, peerId: PeerId, isOutgoing: Bool, peer: Peer?, allowP2P: Bool, proxyServer: ProxyServerSettings?, currentNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>) {
|
||||||
self.audioSession = audioSession
|
self.audioSession = audioSession
|
||||||
self.callSessionManager = callSessionManager
|
self.callSessionManager = callSessionManager
|
||||||
self.callKitIntegration = callKitIntegration
|
self.callKitIntegration = callKitIntegration
|
||||||
|
self.getDeviceAccessData = getDeviceAccessData
|
||||||
|
|
||||||
self.internalId = internalId
|
self.internalId = internalId
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
@ -542,8 +545,23 @@ public final class PresentationCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func answer() {
|
func answer() {
|
||||||
self.callSessionManager.accept(internalId: self.internalId)
|
let (presentationData, present, openSettings) = self.getDeviceAccessData()
|
||||||
self.callKitIntegration?.answerCall(uuid: self.internalId)
|
|
||||||
|
DeviceAccess.authorizeAccess(to: .microphone(.voiceCall), presentationData: presentationData, present: { c, a in
|
||||||
|
present(c, a)
|
||||||
|
}, openSettings: {
|
||||||
|
openSettings()
|
||||||
|
}, { [weak self] value in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if value {
|
||||||
|
strongSelf.callSessionManager.accept(internalId: strongSelf.internalId)
|
||||||
|
strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId)
|
||||||
|
} else {
|
||||||
|
let _ = strongSelf.hangUp().start()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func hangUp() -> Signal<Bool, NoError> {
|
func hangUp() -> Signal<Bool, NoError> {
|
||||||
|
|||||||
@ -2,10 +2,14 @@ import Foundation
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
import Display
|
||||||
|
|
||||||
private func p2pAllowed(settings: VoiceCallSettings?, isContact: Bool) -> Bool {
|
private func p2pAllowed(settings: (VoiceCallSettings, VoipConfiguration)?, isContact: Bool) -> Bool {
|
||||||
let mode = settings?.p2pMode ?? .contacts
|
var mode: VoiceCallP2PMode? = settings?.0.p2pMode
|
||||||
switch (mode, isContact) {
|
if mode == nil {
|
||||||
|
mode = settings?.1.defaultP2PMode
|
||||||
|
}
|
||||||
|
switch (mode ?? .contacts, isContact) {
|
||||||
case (.always, _), (.contacts, true):
|
case (.always, _), (.contacts, true):
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
@ -42,6 +46,7 @@ public enum RequestCallResult {
|
|||||||
|
|
||||||
public final class PresentationCallManager {
|
public final class PresentationCallManager {
|
||||||
private let postbox: Postbox
|
private let postbox: Postbox
|
||||||
|
private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
|
||||||
private let networkType: Signal<NetworkType, NoError>
|
private let networkType: Signal<NetworkType, NoError>
|
||||||
private let audioSession: ManagedAudioSession
|
private let audioSession: ManagedAudioSession
|
||||||
private let callSessionManager: CallSessionManager
|
private let callSessionManager: CallSessionManager
|
||||||
@ -67,11 +72,12 @@ public final class PresentationCallManager {
|
|||||||
private var proxyServer: ProxyServerSettings?
|
private var proxyServer: ProxyServerSettings?
|
||||||
private var proxyServerDisposable: Disposable?
|
private var proxyServerDisposable: Disposable?
|
||||||
|
|
||||||
private var callSettings: VoiceCallSettings?
|
private var callSettings: (VoiceCallSettings, VoipConfiguration)?
|
||||||
private var callSettingsDisposable: Disposable?
|
private var callSettingsDisposable: Disposable?
|
||||||
|
|
||||||
public init(postbox: Postbox, networkType: Signal<NetworkType, NoError>, audioSession: ManagedAudioSession, callSessionManager: CallSessionManager) {
|
public init(postbox: Postbox, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), networkType: Signal<NetworkType, NoError>, audioSession: ManagedAudioSession, callSessionManager: CallSessionManager) {
|
||||||
self.postbox = postbox
|
self.postbox = postbox
|
||||||
|
self.getDeviceAccessData = getDeviceAccessData
|
||||||
self.networkType = networkType
|
self.networkType = networkType
|
||||||
self.audioSession = audioSession
|
self.audioSession = audioSession
|
||||||
self.callSessionManager = callSessionManager
|
self.callSessionManager = callSessionManager
|
||||||
@ -99,40 +105,47 @@ public final class PresentationCallManager {
|
|||||||
audioSessionActivationChangedImpl?(value)
|
audioSessionActivationChangedImpl?(value)
|
||||||
})
|
})
|
||||||
|
|
||||||
self.ringingStatesDisposable = (callSessionManager.ringingStates()
|
let enableCallKit = postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.voiceCallSettings])
|
||||||
|> mapToSignal { ringingStates -> Signal<[(Peer, CallSessionRingingState, Bool)], NoError> in
|
|> map { preferences -> Bool in
|
||||||
|
let settings = preferences.values[ApplicationSpecificPreferencesKeys.voiceCallSettings] as? VoiceCallSettings ?? .defaultSettings
|
||||||
|
return settings.enableSystemIntegration
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|
||||||
|
self.ringingStatesDisposable = (combineLatest(callSessionManager.ringingStates(), enableCallKit)
|
||||||
|
|> mapToSignal { ringingStates, enableCallKit -> Signal<([(Peer, CallSessionRingingState, Bool)], Bool), NoError> in
|
||||||
if ringingStates.isEmpty {
|
if ringingStates.isEmpty {
|
||||||
return .single([])
|
return .single(([], enableCallKit))
|
||||||
} else {
|
} else {
|
||||||
return postbox.transaction { transaction -> [(Peer, CallSessionRingingState, Bool)] in
|
return postbox.transaction { transaction -> ([(Peer, CallSessionRingingState, Bool)], Bool) in
|
||||||
var result: [(Peer, CallSessionRingingState, Bool)] = []
|
var result: [(Peer, CallSessionRingingState, Bool)] = []
|
||||||
for state in ringingStates {
|
for state in ringingStates {
|
||||||
if let peer = transaction.getPeer(state.peerId) {
|
if let peer = transaction.getPeer(state.peerId) {
|
||||||
result.append((peer, state, transaction.isPeerContact(peerId: state.peerId)))
|
result.append((peer, state, transaction.isPeerContact(peerId: state.peerId)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return (result, enableCallKit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> mapToSignal { states -> Signal<([(Peer, CallSessionRingingState, Bool)], NetworkType), NoError> in
|
|> mapToSignal { states, enableCallKit -> Signal<([(Peer, CallSessionRingingState, Bool)], NetworkType, Bool), NoError> in
|
||||||
return networkType
|
return networkType
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> map { currentNetworkType -> ([(Peer, CallSessionRingingState, Bool)], NetworkType) in
|
|> map { currentNetworkType -> ([(Peer, CallSessionRingingState, Bool)], NetworkType, Bool) in
|
||||||
return (states, currentNetworkType)
|
return (states, currentNetworkType, enableCallKit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] ringingStates, currentNetworkType in
|
|> deliverOnMainQueue).start(next: { [weak self] ringingStates, currentNetworkType, enableCallKit in
|
||||||
self?.ringingStatesUpdated(ringingStates, currentNetworkType: currentNetworkType)
|
self?.ringingStatesUpdated(ringingStates, currentNetworkType: currentNetworkType, enableCallKit: enableCallKit)
|
||||||
})
|
})
|
||||||
|
|
||||||
startCallImpl = { [weak self] uuid, handle in
|
startCallImpl = { [weak self] uuid, handle in
|
||||||
if let strongSelf = self, let userId = Int32(handle) {
|
if let strongSelf = self, let userId = Int32(handle) {
|
||||||
return strongSelf.startCall(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), internalId: uuid)
|
return strongSelf.startCall(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), internalId: uuid)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> map { _ -> Bool in
|
|> map { result -> Bool in
|
||||||
return true
|
return result
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single(false)
|
return .single(false)
|
||||||
}
|
}
|
||||||
@ -171,10 +184,12 @@ public final class PresentationCallManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.callSettingsDisposable = (postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.voiceCallSettings])
|
self.callSettingsDisposable = (postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.voiceCallSettings, PreferencesKeys.voipConfiguration])
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] preferences in
|
|> deliverOnMainQueue).start(next: { [weak self] preferences in
|
||||||
if let strongSelf = self, let settings = preferences.values[ApplicationSpecificPreferencesKeys.voiceCallSettings] as? VoiceCallSettings {
|
let callSettings = preferences.values[ApplicationSpecificPreferencesKeys.voiceCallSettings] as? VoiceCallSettings ?? .defaultSettings
|
||||||
strongSelf.callSettings = settings
|
let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.callSettings = (callSettings, configuration)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -187,10 +202,10 @@ public final class PresentationCallManager {
|
|||||||
self.callSettingsDisposable?.dispose()
|
self.callSettingsDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func ringingStatesUpdated(_ ringingStates: [(Peer, CallSessionRingingState, Bool)], currentNetworkType: NetworkType) {
|
private func ringingStatesUpdated(_ ringingStates: [(Peer, CallSessionRingingState, Bool)], currentNetworkType: NetworkType, enableCallKit: Bool) {
|
||||||
if let firstState = ringingStates.first {
|
if let firstState = ringingStates.first {
|
||||||
if self.currentCall == nil {
|
if self.currentCall == nil {
|
||||||
let call = PresentationCall(audioSession: self.audioSession, callSessionManager: self.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(self.callKitIntegration, settings: self.callSettings), internalId: firstState.1.id, peerId: firstState.1.peerId, isOutgoing: false, peer: firstState.0, allowP2P: p2pAllowed(settings: self.callSettings, isContact: firstState.2), proxyServer: self.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: self.networkType)
|
let call = PresentationCall(audioSession: self.audioSession, callSessionManager: self.callSessionManager, callKitIntegration: enableCallKit ? self.callKitIntegration : nil, getDeviceAccessData: self.getDeviceAccessData, internalId: firstState.1.id, peerId: firstState.1.peerId, isOutgoing: false, peer: firstState.0, allowP2P: p2pAllowed(settings: self.callSettings, isContact: firstState.2), proxyServer: self.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: self.networkType)
|
||||||
self.currentCall = call
|
self.currentCall = call
|
||||||
self.currentCallPromise.set(.single(call))
|
self.currentCallPromise.set(.single(call))
|
||||||
self.hasActiveCallsPromise.set(true)
|
self.hasActiveCallsPromise.set(true)
|
||||||
@ -213,12 +228,35 @@ public final class PresentationCallManager {
|
|||||||
return .alreadyInProgress(call.peerId)
|
return .alreadyInProgress(call.peerId)
|
||||||
}
|
}
|
||||||
if let _ = self.callKitIntegration {
|
if let _ = self.callKitIntegration {
|
||||||
startCallDisposable.set((postbox.loadedPeerWithId(peerId)
|
let (presentationData, present, openSettings) = self.getDeviceAccessData()
|
||||||
|> take(1)
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
let accessEnabledSignal: Signal<Bool, NoError> = Signal { subscriber in
|
||||||
if let strongSelf = self {
|
DeviceAccess.authorizeAccess(to: .microphone(.voiceCall), presentationData: presentationData, present: { c, a in
|
||||||
strongSelf.callKitIntegration?.startCall(peerId: peerId, displayTitle: peer.displayTitle)
|
present(c, a)
|
||||||
|
}, openSettings: {
|
||||||
|
openSettings()
|
||||||
|
}, { value in
|
||||||
|
subscriber.putNext(value)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
})
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|> runOn(Queue.mainQueue())
|
||||||
|
let postbox = self.postbox
|
||||||
|
self.startCallDisposable.set((accessEnabledSignal
|
||||||
|
|> mapToSignal { accessEnabled -> Signal<Peer?, NoError> in
|
||||||
|
if !accessEnabled {
|
||||||
|
return .single(nil)
|
||||||
}
|
}
|
||||||
|
return postbox.loadedPeerWithId(peerId)
|
||||||
|
|> take(1)
|
||||||
|
|> map(Optional.init)
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
|
guard let strongSelf = self, let peer = peer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.callKitIntegration?.startCall(peerId: peerId, displayTitle: peer.displayTitle)
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
let _ = self.startCall(peerId: peerId).start()
|
let _ = self.startCall(peerId: peerId).start()
|
||||||
@ -227,34 +265,58 @@ public final class PresentationCallManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func startCall(peerId: PeerId, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal<Bool, NoError> {
|
private func startCall(peerId: PeerId, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal<Bool, NoError> {
|
||||||
return (combineLatest(self.callSessionManager.request(peerId: peerId, internalId: internalId), self.networkType |> take(1), postbox.peerView(id: peerId) |> take(1) |> map({ peerView -> Bool in
|
let (presentationData, present, openSettings) = self.getDeviceAccessData()
|
||||||
return peerView.peerIsContact
|
|
||||||
}))
|
let accessEnabledSignal: Signal<Bool, NoError> = Signal { subscriber in
|
||||||
|> deliverOnMainQueue
|
DeviceAccess.authorizeAccess(to: .microphone(.voiceCall), presentationData: presentationData, present: { c, a in
|
||||||
|> beforeNext { [weak self] internalId, currentNetworkType, isContact in
|
present(c, a)
|
||||||
if let strongSelf = self {
|
}, openSettings: {
|
||||||
if let currentCall = strongSelf.currentCall {
|
openSettings()
|
||||||
currentCall.rejectBusy()
|
}, { value in
|
||||||
}
|
subscriber.putNext(value)
|
||||||
|
subscriber.putCompletion()
|
||||||
let call = PresentationCall(audioSession: strongSelf.audioSession, callSessionManager: strongSelf.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings), internalId: internalId, peerId: peerId, isOutgoing: true, peer: nil, allowP2P: p2pAllowed(settings: strongSelf.callSettings, isContact: isContact), proxyServer: strongSelf.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: strongSelf.networkType)
|
})
|
||||||
strongSelf.currentCall = call
|
return EmptyDisposable
|
||||||
strongSelf.currentCallPromise.set(.single(call))
|
}
|
||||||
strongSelf.hasActiveCallsPromise.set(true)
|
|> runOn(Queue.mainQueue())
|
||||||
strongSelf.removeCurrentCallDisposable.set((call.canBeRemoved
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak call] value in
|
let postbox = self.postbox
|
||||||
if value, let strongSelf = self, let call = call {
|
let callSessionManager = self.callSessionManager
|
||||||
if strongSelf.currentCall === call {
|
let networkType = self.networkType
|
||||||
strongSelf.currentCall = nil
|
return accessEnabledSignal
|
||||||
strongSelf.currentCallPromise.set(.single(nil))
|
|> mapToSignal { [weak self] accessEnabled -> Signal<Bool, NoError> in
|
||||||
strongSelf.hasActiveCallsPromise.set(false)
|
if !accessEnabled {
|
||||||
}
|
return .single(false)
|
||||||
}
|
}
|
||||||
}))
|
return (combineLatest(callSessionManager.request(peerId: peerId, internalId: internalId), networkType |> take(1), postbox.peerView(id: peerId) |> take(1) |> map({ peerView -> Bool in
|
||||||
|
return peerView.peerIsContact
|
||||||
|
}) |> take(1))
|
||||||
|
|> deliverOnMainQueue
|
||||||
|
|> beforeNext { internalId, currentNetworkType, isContact in
|
||||||
|
if let strongSelf = self, accessEnabled {
|
||||||
|
if let currentCall = strongSelf.currentCall {
|
||||||
|
currentCall.rejectBusy()
|
||||||
|
}
|
||||||
|
|
||||||
|
let call = PresentationCall(audioSession: strongSelf.audioSession, callSessionManager: strongSelf.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings?.0), getDeviceAccessData: strongSelf.getDeviceAccessData, internalId: internalId, peerId: peerId, isOutgoing: true, peer: nil, allowP2P: p2pAllowed(settings: strongSelf.callSettings, isContact: isContact), proxyServer: strongSelf.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: strongSelf.networkType)
|
||||||
|
strongSelf.currentCall = call
|
||||||
|
strongSelf.currentCallPromise.set(.single(call))
|
||||||
|
strongSelf.hasActiveCallsPromise.set(true)
|
||||||
|
strongSelf.removeCurrentCallDisposable.set((call.canBeRemoved
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak call] value in
|
||||||
|
if value, let strongSelf = self, let call = call {
|
||||||
|
if strongSelf.currentCall === call {
|
||||||
|
strongSelf.currentCall = nil
|
||||||
|
strongSelf.currentCallPromise.set(.single(nil))
|
||||||
|
strongSelf.hasActiveCallsPromise.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> mapToSignal { value -> Signal<Bool, NoError> in
|
||||||
|
return .single(true)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|> mapToSignal { _ -> Signal<Bool, NoError> in
|
|
||||||
return .single(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -412,22 +412,19 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign
|
|||||||
let privacySignal = privacySettingsPromise.get()
|
let privacySignal = privacySettingsPromise.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|
|
||||||
let callsSignal = account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.voiceCallSettings])
|
let callsSignal = account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.voiceCallSettings, PreferencesKeys.voipConfiguration])
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> map { view -> VoiceCallSettings in
|
|> map { view -> (VoiceCallSettings, VoipConfiguration) in
|
||||||
let voiceCallSettings: VoiceCallSettings
|
let voiceCallSettings: VoiceCallSettings = view.values[ApplicationSpecificPreferencesKeys.voiceCallSettings] as? VoiceCallSettings ?? .defaultSettings
|
||||||
if let value = view.values[ApplicationSpecificPreferencesKeys.voiceCallSettings] as? VoiceCallSettings {
|
let voipConfiguration = view.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue
|
||||||
voiceCallSettings = value
|
|
||||||
} else {
|
return (voiceCallSettings, voipConfiguration)
|
||||||
voiceCallSettings = VoiceCallSettings.defaultSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
return voiceCallSettings
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentInfoDisposable.set((combineLatest(privacySignal, callsSignal) |> deliverOnMainQueue).start(next: { [weak currentInfoDisposable] info, callSettings in
|
currentInfoDisposable.set((combineLatest(privacySignal, callsSignal)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak currentInfoDisposable] info, callSettings in
|
||||||
if let info = info {
|
if let info = info {
|
||||||
pushControllerImpl?(selectivePrivacySettingsController(account: account, kind: .voiceCalls, current: info.voiceCalls, callSettings: callSettings, callIntegrationAvailable: CallKitIntegration.isAvailable, updated: { updated, updatedCallSettings in
|
pushControllerImpl?(selectivePrivacySettingsController(account: account, kind: .voiceCalls, current: info.voiceCalls, callSettings: callSettings.0, voipConfiguration: callSettings.1, callIntegrationAvailable: CallKitIntegration.isAvailable, updated: { updated, updatedCallSettings in
|
||||||
if let currentInfoDisposable = currentInfoDisposable, let updatedCallSettings = updatedCallSettings {
|
if let currentInfoDisposable = currentInfoDisposable, let updatedCallSettings = updatedCallSettings {
|
||||||
let _ = updateVoiceCallSettingsSettingsInteractively(postbox: account.postbox, { _ in
|
let _ = updateVoiceCallSettingsSettingsInteractively(postbox: account.postbox, { _ in
|
||||||
return updatedCallSettings
|
return updatedCallSettings
|
||||||
|
|||||||
@ -46,6 +46,7 @@ final class SecureIdAuthController: ViewController {
|
|||||||
private var didPlayPresentationAnimation = false
|
private var didPlayPresentationAnimation = false
|
||||||
|
|
||||||
private let challengeDisposable = MetaDisposable()
|
private let challengeDisposable = MetaDisposable()
|
||||||
|
private let authenthicateDisposable = MetaDisposable()
|
||||||
private var formDisposable: Disposable?
|
private var formDisposable: Disposable?
|
||||||
private let deleteDisposable = MetaDisposable()
|
private let deleteDisposable = MetaDisposable()
|
||||||
private let recoveryDisposable = MetaDisposable()
|
private let recoveryDisposable = MetaDisposable()
|
||||||
@ -62,9 +63,9 @@ final class SecureIdAuthController: ViewController {
|
|||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case .form:
|
case .form:
|
||||||
self.state = .form(SecureIdAuthControllerFormState(encryptedFormData: nil, formData: nil, verificationState: nil))
|
self.state = .form(SecureIdAuthControllerFormState(encryptedFormData: nil, formData: nil, verificationState: nil, removingValues: false))
|
||||||
case .list:
|
case .list:
|
||||||
self.state = .list(SecureIdAuthControllerListState(verificationState: nil, encryptedValues: nil, primaryLanguageByCountry: [:], values: nil))
|
self.state = .list(SecureIdAuthControllerListState(verificationState: nil, encryptedValues: nil, primaryLanguageByCountry: [:], values: nil, removingValues: false))
|
||||||
}
|
}
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||||
@ -78,14 +79,49 @@ final class SecureIdAuthController: ViewController {
|
|||||||
self.challengeDisposable.set((twoStepAuthData(account.network)
|
self.challengeDisposable.set((twoStepAuthData(account.network)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.updateState { state in
|
let storedPassword = strongSelf.account.telegramApplicationContext.getStoredSecureIdPassword()
|
||||||
var state = state
|
if data.currentPasswordDerivation != nil, let storedPassword = storedPassword {
|
||||||
if data.currentPasswordDerivation != nil {
|
strongSelf.authenthicateDisposable.set((accessSecureId(network: strongSelf.account.network, password: storedPassword)
|
||||||
state.verificationState = .passwordChallenge(hint: data.currentHint ?? "", state: .none, hasRecoveryEmail: data.hasRecovery)
|
|> deliverOnMainQueue).start(next: { context in
|
||||||
} else {
|
guard let strongSelf = self, strongSelf.state.verificationState == nil else {
|
||||||
state.verificationState = .noChallenge(data.unconfirmedEmailPattern)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.updateState(animated: true, { state in
|
||||||
|
var state = state
|
||||||
|
state.verificationState = .verified(context.context)
|
||||||
|
switch state {
|
||||||
|
case var .form(form):
|
||||||
|
form.formData = form.encryptedFormData.flatMap({ decryptedSecureIdForm(context: context.context, form: $0.form) })
|
||||||
|
state = .form(form)
|
||||||
|
case var .list(list):
|
||||||
|
list.values = list.encryptedValues.flatMap({ decryptedAllSecureIdValues(context: context.context, encryptedValues: $0) })
|
||||||
|
state = .list(list)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}, error: { [weak self] error in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strongSelf.state.verificationState == nil {
|
||||||
|
strongSelf.updateState(animated: true, { state in
|
||||||
|
var state = state
|
||||||
|
state.verificationState = .passwordChallenge(hint: data.currentHint ?? "", state: .none, hasRecoveryEmail: data.hasRecovery)
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
strongSelf.updateState { state in
|
||||||
|
var state = state
|
||||||
|
if data.currentPasswordDerivation != nil {
|
||||||
|
state.verificationState = .passwordChallenge(hint: data.currentHint ?? "", state: .none, hasRecoveryEmail: data.hasRecovery)
|
||||||
|
} else {
|
||||||
|
state.verificationState = .noChallenge(data.unconfirmedEmailPattern)
|
||||||
|
}
|
||||||
|
return state
|
||||||
}
|
}
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@ -123,6 +159,7 @@ final class SecureIdAuthController: ViewController {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let errorText = strongSelf.presentationData.strings.Login_UnknownError
|
let errorText = strongSelf.presentationData.strings.Login_UnknownError
|
||||||
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
strongSelf.dismiss()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
case .list:
|
case .list:
|
||||||
@ -154,6 +191,7 @@ final class SecureIdAuthController: ViewController {
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.challengeDisposable.dispose()
|
self.challengeDisposable.dispose()
|
||||||
|
self.authenthicateDisposable.dispose()
|
||||||
self.formDisposable?.dispose()
|
self.formDisposable?.dispose()
|
||||||
self.deleteDisposable.dispose()
|
self.deleteDisposable.dispose()
|
||||||
self.recoveryDisposable.dispose()
|
self.recoveryDisposable.dispose()
|
||||||
@ -249,10 +287,16 @@ final class SecureIdAuthController: ViewController {
|
|||||||
if let verificationState = self.state.verificationState, case .passwordChallenge(_, .checking, _) = verificationState {
|
if let verificationState = self.state.verificationState, case .passwordChallenge(_, .checking, _) = verificationState {
|
||||||
previousHadProgress = true
|
previousHadProgress = true
|
||||||
}
|
}
|
||||||
|
if self.state.removingValues {
|
||||||
|
previousHadProgress = true
|
||||||
|
}
|
||||||
var updatedHasProgress = false
|
var updatedHasProgress = false
|
||||||
if let verificationState = state.verificationState, case .passwordChallenge(_, .checking, _) = verificationState {
|
if let verificationState = state.verificationState, case .passwordChallenge(_, .checking, _) = verificationState {
|
||||||
updatedHasProgress = true
|
updatedHasProgress = true
|
||||||
}
|
}
|
||||||
|
if state.removingValues {
|
||||||
|
updatedHasProgress = true
|
||||||
|
}
|
||||||
|
|
||||||
self.state = state
|
self.state = state
|
||||||
if self.isNodeLoaded {
|
if self.isNodeLoaded {
|
||||||
@ -292,6 +336,7 @@ final class SecureIdAuthController: ViewController {
|
|||||||
guard let strongSelf = self, let verificationState = strongSelf.state.verificationState, case .passwordChallenge(_, .checking, _) = verificationState else {
|
guard let strongSelf = self, let verificationState = strongSelf.state.verificationState, case .passwordChallenge(_, .checking, _) = verificationState else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
strongSelf.account.telegramApplicationContext.storeSecureIdPassword(password: password)
|
||||||
strongSelf.updateState(animated: !inBackground, { state in
|
strongSelf.updateState(animated: !inBackground, { state in
|
||||||
var state = state
|
var state = state
|
||||||
state.verificationState = .verified(context.context)
|
state.verificationState = .verified(context.context)
|
||||||
@ -341,9 +386,15 @@ final class SecureIdAuthController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func openPasswordHelp() {
|
private func openPasswordHelp() {
|
||||||
guard let verificationState = self.state.verificationState, case let .passwordChallenge(passwordChallenge) = verificationState, case .none = passwordChallenge.state else {
|
guard let verificationState = self.state.verificationState, case let .passwordChallenge(passwordChallenge) = verificationState else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
switch passwordChallenge.state {
|
||||||
|
case .checking:
|
||||||
|
return
|
||||||
|
case .none, .invalid:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
if passwordChallenge.hasRecoveryEmail {
|
if passwordChallenge.hasRecoveryEmail {
|
||||||
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: self.presentationData.theme), title: self.presentationData.strings.Passport_ForgottenPassword, text: self.presentationData.strings.Passport_PasswordReset, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Login_ResetAccountProtected_Reset, action: { [weak self] in
|
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: self.presentationData.theme), title: self.presentationData.strings.Passport_ForgottenPassword, text: self.presentationData.strings.Passport_PasswordReset, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Login_ResetAccountProtected_Reset, action: { [weak self] in
|
||||||
@ -427,7 +478,9 @@ final class SecureIdAuthController: ViewController {
|
|||||||
switch self.state {
|
switch self.state {
|
||||||
case let .form(form):
|
case let .form(form):
|
||||||
if case let .form(reqForm) = self.mode, let encryptedFormData = form.encryptedFormData, let formData = form.formData {
|
if case let .form(reqForm) = self.mode, let encryptedFormData = form.encryptedFormData, let formData = form.formData {
|
||||||
let _ = (grantSecureIdAccess(network: self.account.network, peerId: encryptedFormData.servicePeer.id, publicKey: reqForm.publicKey, scope: reqForm.scope, opaquePayload: reqForm.opaquePayload, opaqueNonce: reqForm.opaqueNonce, values: formData.values, requestedFields: formData.requestedFields)
|
let values = parseRequestedFormFields(formData.requestedFields, values: formData.values).map({ $0.1 }).flatMap({ $0 })
|
||||||
|
|
||||||
|
let _ = (grantSecureIdAccess(network: self.account.network, peerId: encryptedFormData.servicePeer.id, publicKey: reqForm.publicKey, scope: reqForm.scope, opaquePayload: reqForm.opaquePayload, opaqueNonce: reqForm.opaqueNonce, values: values, requestedFields: formData.requestedFields)
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import SwiftSignalKit
|
||||||
import Display
|
import Display
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Postbox
|
import Postbox
|
||||||
@ -10,8 +11,11 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
private let requestLayout: (ContainedViewLayoutTransition) -> Void
|
private let requestLayout: (ContainedViewLayoutTransition) -> Void
|
||||||
private let interaction: SecureIdAuthControllerInteraction
|
private let interaction: SecureIdAuthControllerInteraction
|
||||||
|
|
||||||
|
private var hapticFeedback: HapticFeedback?
|
||||||
|
|
||||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
|
private let activityIndicator: ActivityIndicator
|
||||||
private let scrollNode: ASScrollNode
|
private let scrollNode: ASScrollNode
|
||||||
private let headerNode: SecureIdAuthHeaderNode
|
private let headerNode: SecureIdAuthHeaderNode
|
||||||
private var contentNode: (ASDisplayNode & SecureIdAuthContentNode)?
|
private var contentNode: (ASDisplayNode & SecureIdAuthContentNode)?
|
||||||
@ -23,40 +27,83 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
private var state: SecureIdAuthControllerState?
|
private var state: SecureIdAuthControllerState?
|
||||||
|
|
||||||
|
private let deleteValueDisposable = MetaDisposable()
|
||||||
|
|
||||||
init(account: Account, presentationData: PresentationData, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, interaction: SecureIdAuthControllerInteraction) {
|
init(account: Account, presentationData: PresentationData, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, interaction: SecureIdAuthControllerInteraction) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.requestLayout = requestLayout
|
self.requestLayout = requestLayout
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
|
|
||||||
|
self.activityIndicator = ActivityIndicator(type: .custom(presentationData.theme.list.freeMonoIcon, 40.0, 2.0))
|
||||||
|
self.activityIndicator.isHidden = true
|
||||||
|
|
||||||
self.scrollNode = ASScrollNode()
|
self.scrollNode = ASScrollNode()
|
||||||
self.headerNode = SecureIdAuthHeaderNode(account: account, theme: presentationData.theme, strings: presentationData.strings)
|
self.headerNode = SecureIdAuthHeaderNode(account: account, theme: presentationData.theme, strings: presentationData.strings)
|
||||||
self.acceptNode = SecureIdAuthAcceptNode(title: presentationData.strings.Passport_Authorize, theme: presentationData.theme)
|
self.acceptNode = SecureIdAuthAcceptNode(title: presentationData.strings.Passport_Authorize, theme: presentationData.theme)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.activityIndicator)
|
||||||
|
|
||||||
self.scrollNode.view.alwaysBounceVertical = true
|
self.scrollNode.view.alwaysBounceVertical = true
|
||||||
self.addSubnode(self.scrollNode)
|
self.addSubnode(self.scrollNode)
|
||||||
|
|
||||||
self.backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
self.backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
self.acceptNode.pressed = { [weak self] in
|
self.acceptNode.pressed = { [weak self] in
|
||||||
self?.interaction.grant()
|
guard let strongSelf = self, let state = strongSelf.state, case let .form(form) = state, let formData = form.formData else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (field, _, filled) in parseRequestedFormFields(formData.requestedFields, values: formData.values) {
|
||||||
|
if !filled {
|
||||||
|
if let contentNode = strongSelf.contentNode as? SecureIdAuthFormContentNode {
|
||||||
|
if let rect = contentNode.frameForField(field) {
|
||||||
|
strongSelf.scrollNode.view.scrollRectToVisible(rect, animated: true)
|
||||||
|
}
|
||||||
|
contentNode.highlightField(field)
|
||||||
|
}
|
||||||
|
if strongSelf.hapticFeedback == nil {
|
||||||
|
strongSelf.hapticFeedback = HapticFeedback()
|
||||||
|
}
|
||||||
|
strongSelf.hapticFeedback?.error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.interaction.grant()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.deleteValueDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
func animateIn() {
|
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)
|
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) {
|
func animateOut(completion: (() -> Void)? = nil) {
|
||||||
|
self.isDisappearing = true
|
||||||
self.view.endEditing(true)
|
self.view.endEditing(true)
|
||||||
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
|
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?()
|
completion?()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isDisappearing = false
|
||||||
|
|
||||||
|
private var previousHeaderNodeAlpha: CGFloat = 0.0
|
||||||
|
private var hadContentNode = false
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
self.validLayout = (layout, navigationBarHeight)
|
self.validLayout = (layout, navigationBarHeight)
|
||||||
|
if self.isDisappearing {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let previousHadContentNode = self.hadContentNode
|
||||||
|
self.hadContentNode = self.contentNode != nil
|
||||||
|
|
||||||
var insetOptions: ContainerViewLayoutInsetOptions = []
|
var insetOptions: ContainerViewLayoutInsetOptions = []
|
||||||
if self.contentNode is SecureIdAuthPasswordOptionContentNode {
|
if self.contentNode is SecureIdAuthPasswordOptionContentNode {
|
||||||
@ -66,19 +113,31 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
var insets = layout.insets(options: insetOptions)
|
var insets = layout.insets(options: insetOptions)
|
||||||
insets.bottom = max(insets.bottom, layout.safeInsets.bottom)
|
insets.bottom = max(insets.bottom, layout.safeInsets.bottom)
|
||||||
|
|
||||||
let headerNodeTransition: ContainedViewLayoutTransition = headerNode.bounds.isEmpty ? .immediate : transition
|
transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - 40.0) / 2.0), y: insets.top + floor((layout.size.height - insets.top - insets.bottom - 40.0) / 2.0)), size: CGSize(width: 40.0, height: 40.0)))
|
||||||
let headerHeight: CGFloat
|
|
||||||
|
var headerNodeTransition: ContainedViewLayoutTransition = self.headerNode.bounds.height.isZero ? .immediate : transition
|
||||||
|
if self.previousHeaderNodeAlpha.isZero && !self.headerNode.alpha.isZero {
|
||||||
|
headerNodeTransition = .immediate
|
||||||
|
}
|
||||||
|
self.previousHeaderNodeAlpha = self.headerNode.alpha
|
||||||
|
let headerLayout: (compact: CGFloat, expanded: CGFloat, apply: (Bool) -> Void)
|
||||||
if self.headerNode.alpha.isZero {
|
if self.headerNode.alpha.isZero {
|
||||||
headerHeight = 0.0
|
headerLayout = (0.0, 0.0, { _ in })
|
||||||
} else {
|
} else {
|
||||||
headerHeight = self.headerNode.updateLayout(width: layout.size.width, transition: headerNodeTransition)
|
headerLayout = self.headerNode.updateLayout(width: layout.size.width, transition: headerNodeTransition)
|
||||||
}
|
}
|
||||||
|
|
||||||
let acceptHeight = self.acceptNode.updateLayout(width: layout.size.width, bottomInset: layout.intrinsicInsets.bottom, transition: transition)
|
let acceptHeight = self.acceptNode.updateLayout(width: layout.size.width, bottomInset: layout.intrinsicInsets.bottom, transition: transition)
|
||||||
|
|
||||||
var footerHeight: CGFloat = 0.0
|
var footerHeight: CGFloat = 0.0
|
||||||
var contentSpacing: CGFloat
|
var contentSpacing: CGFloat
|
||||||
transition.updateFrame(node: self.acceptNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - acceptHeight), size: CGSize(width: layout.size.width, height: acceptHeight)))
|
|
||||||
|
var acceptNodeTransition = transition
|
||||||
|
if !previousHadContentNode {
|
||||||
|
acceptNodeTransition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptNodeTransition.updateFrame(node: self.acceptNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - acceptHeight), size: CGSize(width: layout.size.width, height: acceptHeight)))
|
||||||
if self.acceptNode.supernode != nil {
|
if self.acceptNode.supernode != nil {
|
||||||
footerHeight += acceptHeight
|
footerHeight += acceptHeight
|
||||||
contentSpacing = 25.0
|
contentSpacing = 25.0
|
||||||
@ -104,6 +163,17 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
let contentNodeTransition: ContainedViewLayoutTransition = contentFirstTime ? .immediate : transition
|
let contentNodeTransition: ContainedViewLayoutTransition = contentFirstTime ? .immediate : transition
|
||||||
let contentLayout = contentNode.updateLayout(width: layout.size.width, transition: contentNodeTransition)
|
let contentLayout = contentNode.updateLayout(width: layout.size.width, transition: contentNodeTransition)
|
||||||
|
|
||||||
|
let headerHeight: CGFloat
|
||||||
|
if self.contentNode is SecureIdAuthPasswordOptionContentNode && headerLayout.expanded + contentLayout.height + 10.0 + 14.0 + 16.0 > contentRect.height {
|
||||||
|
headerHeight = headerLayout.compact
|
||||||
|
headerLayout.apply(false)
|
||||||
|
} else {
|
||||||
|
headerHeight = headerLayout.expanded
|
||||||
|
headerLayout.apply(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentSpacing = max(10.0, min(contentSpacing, contentRect.height - (headerHeight + contentLayout.height + 10.0 - 14.0 - 16.0)))
|
||||||
|
|
||||||
let boundingHeight = headerHeight + contentLayout.height + contentSpacing
|
let boundingHeight = headerHeight + contentLayout.height + contentSpacing
|
||||||
|
|
||||||
var boundingRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: boundingHeight))
|
var boundingRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: boundingHeight))
|
||||||
@ -126,7 +196,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
contentNode.didAppear()
|
contentNode.didAppear()
|
||||||
if transition.isAnimated {
|
if transition.isAnimated {
|
||||||
contentNode.animateIn()
|
contentNode.animateIn()
|
||||||
if !(contentNode is SecureIdAuthPasswordOptionContentNode || contentNode is SecureIdAuthPasswordSetupContentNode) {
|
if !(contentNode is SecureIdAuthPasswordOptionContentNode || contentNode is SecureIdAuthPasswordSetupContentNode) && previousHadContentNode {
|
||||||
transition.animatePositionAdditive(node: contentNode, offset: CGPoint(x: layout.size.width, y: 0.0))
|
transition.animatePositionAdditive(node: contentNode, offset: CGPoint(x: layout.size.width, y: 0.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,6 +239,8 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
func updateState(_ state: SecureIdAuthControllerState, transition: ContainedViewLayoutTransition) {
|
func updateState(_ state: SecureIdAuthControllerState, transition: ContainedViewLayoutTransition) {
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
|
var displayActivity = false
|
||||||
|
|
||||||
switch state {
|
switch state {
|
||||||
case let .form(form):
|
case let .form(form):
|
||||||
if let encryptedFormData = form.encryptedFormData, let verificationState = form.verificationState {
|
if let encryptedFormData = form.encryptedFormData, let verificationState = form.verificationState {
|
||||||
@ -243,6 +315,8 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
if self.contentNode !== contentNode {
|
if self.contentNode !== contentNode {
|
||||||
self.transitionToContentNode(contentNode, transition: transition)
|
self.transitionToContentNode(contentNode, transition: transition)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
displayActivity = true
|
||||||
}
|
}
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
if let _ = list.encryptedValues, let verificationState = list.verificationState {
|
if let _ = list.encryptedValues, let verificationState = list.verificationState {
|
||||||
@ -268,13 +342,9 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
contentNode = current
|
contentNode = current
|
||||||
} else {
|
} else {
|
||||||
let current = SecureIdAuthPasswordOptionContentNode(theme: presentationData.theme, strings: presentationData.strings, hint: hint, checkPassword: { [weak self] password in
|
let current = SecureIdAuthPasswordOptionContentNode(theme: presentationData.theme, strings: presentationData.strings, hint: hint, checkPassword: { [weak self] password in
|
||||||
if let strongSelf = self {
|
self?.interaction.checkPassword(password)
|
||||||
strongSelf.interaction.checkPassword(password)
|
}, passwordHelp: { [weak self] in
|
||||||
}
|
self?.interaction.openPasswordHelp()
|
||||||
}, passwordHelp: { [weak self] in
|
|
||||||
if let strongSelf = self {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
current.updateIsChecking(challengeState == .checking)
|
current.updateIsChecking(challengeState == .checking)
|
||||||
contentNode = current
|
contentNode = current
|
||||||
@ -301,8 +371,13 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
if self.contentNode !== contentNode {
|
if self.contentNode !== contentNode {
|
||||||
self.transitionToContentNode(contentNode, transition: transition)
|
self.transitionToContentNode(contentNode, transition: transition)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
displayActivity = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if displayActivity != !self.activityIndicator.isHidden {
|
||||||
|
self.activityIndicator.isHidden = !displayActivity
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) {
|
private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) {
|
||||||
@ -336,7 +411,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
return !touchedKeys.contains(value.value.key)
|
return !touchedKeys.contains(value.value.key)
|
||||||
}
|
}
|
||||||
values.append(contentsOf: updatedValues)
|
values.append(contentsOf: updatedValues)
|
||||||
return .form(SecureIdAuthControllerFormState(encryptedFormData: form.encryptedFormData, formData: SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: values), verificationState: form.verificationState))
|
return .form(SecureIdAuthControllerFormState(encryptedFormData: form.encryptedFormData, formData: SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: values), verificationState: form.verificationState, removingValues: form.removingValues))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,7 +422,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
switch document {
|
switch document {
|
||||||
case let .just(type):
|
case let .just(type):
|
||||||
if let value = findValue(formData.values, key: type.valueKey)?.1 {
|
if let value = findValue(formData.values, key: type.valueKey)?.1 {
|
||||||
switch value {
|
switch value.value {
|
||||||
case .passport:
|
case .passport:
|
||||||
hasValueType = .passport
|
hasValueType = .passport
|
||||||
case .internalPassport:
|
case .internalPassport:
|
||||||
@ -363,7 +438,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
case let .oneOf(types):
|
case let .oneOf(types):
|
||||||
for type in types {
|
for type in types {
|
||||||
if let value = findValue(formData.values, key: type.valueKey)?.1 {
|
if let value = findValue(formData.values, key: type.valueKey)?.1 {
|
||||||
switch value {
|
switch value.value {
|
||||||
case .passport:
|
case .passport:
|
||||||
hasValueType = .passport
|
hasValueType = .passport
|
||||||
case .internalPassport:
|
case .internalPassport:
|
||||||
@ -401,7 +476,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
switch document {
|
switch document {
|
||||||
case let .just(type):
|
case let .just(type):
|
||||||
if let value = findValue(formData.values, key: type.valueKey)?.1 {
|
if let value = findValue(formData.values, key: type.valueKey)?.1 {
|
||||||
switch value {
|
switch value.value {
|
||||||
case .rentalAgreement:
|
case .rentalAgreement:
|
||||||
hasValueType = .rentalAgreement
|
hasValueType = .rentalAgreement
|
||||||
case .bankStatement:
|
case .bankStatement:
|
||||||
@ -420,7 +495,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
case let .oneOf(types):
|
case let .oneOf(types):
|
||||||
for type in types {
|
for type in types {
|
||||||
if let value = findValue(formData.values, key: type.valueKey)?.1 {
|
if let value = findValue(formData.values, key: type.valueKey)?.1 {
|
||||||
switch value {
|
switch value.value {
|
||||||
case .rentalAgreement:
|
case .rentalAgreement:
|
||||||
hasValueType = .rentalAgreement
|
hasValueType = .rentalAgreement
|
||||||
case .bankStatement:
|
case .bankStatement:
|
||||||
@ -489,43 +564,100 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentPlaintextSelection(type: SecureIdPlaintextFormType) {
|
private func presentPlaintextSelection(type: SecureIdPlaintextFormType) {
|
||||||
guard let state = self.state, case let .form(form) = state, let verificationState = form.verificationState, case let .verified(context) = verificationState else {
|
guard let state = self.state, case let .form(form) = state, let formData = form.formData, let verificationState = form.verificationState, case let .verified(context) = verificationState else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var immediatelyAvailableValue: SecureIdValue?
|
var immediatelyAvailableValue: SecureIdValue?
|
||||||
|
var currentValue: SecureIdValueWithContext?
|
||||||
switch type {
|
switch type {
|
||||||
case .phone:
|
case .phone:
|
||||||
if let peer = form.encryptedFormData?.accountPeer as? TelegramUser, let phone = peer.phone, !phone.isEmpty {
|
if let peer = form.encryptedFormData?.accountPeer as? TelegramUser, let phone = peer.phone, !phone.isEmpty {
|
||||||
immediatelyAvailableValue = .phone(SecureIdPhoneValue(phone: phone))
|
immediatelyAvailableValue = .phone(SecureIdPhoneValue(phone: phone))
|
||||||
}
|
}
|
||||||
default:
|
currentValue = findValue(formData.values, key: .phone)?.1
|
||||||
break
|
case .email:
|
||||||
|
currentValue = findValue(formData.values, key: .email)?.1
|
||||||
}
|
}
|
||||||
self.interaction.present(SecureIdPlaintextFormController(account: self.account, context: context, type: type, immediatelyAvailableValue: immediatelyAvailableValue, updatedValue: { [weak self] valueWithContext in
|
let openForm: () -> Void = { [weak self] in
|
||||||
if let strongSelf = self {
|
guard let strongSelf = self else {
|
||||||
strongSelf.interaction.updateState { state in
|
return
|
||||||
if case let .form(form) = state, let formData = form.formData {
|
|
||||||
var values = formData.values
|
|
||||||
switch type {
|
|
||||||
case .phone:
|
|
||||||
while let index = findValue(values, key: .phone)?.0 {
|
|
||||||
values.remove(at: index)
|
|
||||||
}
|
|
||||||
case .email:
|
|
||||||
while let index = findValue(values, key: .email)?.0 {
|
|
||||||
values.remove(at: index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let valueWithContext = valueWithContext {
|
|
||||||
values.append(valueWithContext)
|
|
||||||
}
|
|
||||||
return .form(SecureIdAuthControllerFormState(encryptedFormData: form.encryptedFormData, formData: SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: values), verificationState: form.verificationState))
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}), nil)
|
strongSelf.interaction.present(SecureIdPlaintextFormController(account: strongSelf.account, context: context, type: type, immediatelyAvailableValue: immediatelyAvailableValue, updatedValue: { valueWithContext in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.interaction.updateState { state in
|
||||||
|
if case let .form(form) = state, let formData = form.formData {
|
||||||
|
var values = formData.values
|
||||||
|
switch type {
|
||||||
|
case .phone:
|
||||||
|
while let index = findValue(values, key: .phone)?.0 {
|
||||||
|
values.remove(at: index)
|
||||||
|
}
|
||||||
|
case .email:
|
||||||
|
while let index = findValue(values, key: .email)?.0 {
|
||||||
|
values.remove(at: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let valueWithContext = valueWithContext {
|
||||||
|
values.append(valueWithContext)
|
||||||
|
}
|
||||||
|
return .form(SecureIdAuthControllerFormState(encryptedFormData: form.encryptedFormData, formData: SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: values), verificationState: form.verificationState, removingValues: form.removingValues))
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentValue = currentValue {
|
||||||
|
let controller = ActionSheetController(presentationTheme: self.presentationData.theme)
|
||||||
|
let dismissAction: () -> Void = { [weak controller] in
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
}
|
||||||
|
let text: String
|
||||||
|
switch currentValue.value {
|
||||||
|
case .phone:
|
||||||
|
text = self.presentationData.strings.Passport_Phone_Delete
|
||||||
|
default:
|
||||||
|
text = self.presentationData.strings.Passport_Email_Delete
|
||||||
|
}
|
||||||
|
controller.setItemGroups([
|
||||||
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: text, color: .destructive, action: { [weak self] in
|
||||||
|
dismissAction()
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.interaction.updateState { state in
|
||||||
|
if case var .form(form) = state {
|
||||||
|
form.removingValues = true
|
||||||
|
return .form(form)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
strongSelf.deleteValueDisposable.set((deleteSecureIdValues(network: strongSelf.account.network, keys: Set([currentValue.value.key]))
|
||||||
|
|> deliverOnMainQueue).start(completed: {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.interaction.updateState { state in
|
||||||
|
if case var .form(form) = state, let formData = form.formData {
|
||||||
|
form.removingValues = false
|
||||||
|
form.formData = SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: formData.values.filter {
|
||||||
|
$0.value.key != currentValue.value.key
|
||||||
|
})
|
||||||
|
return .form(form)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})]),
|
||||||
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||||
|
])
|
||||||
|
self.view.endEditing(true)
|
||||||
|
self.interaction.present(controller, nil)
|
||||||
|
} else {
|
||||||
|
openForm()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openListField(_ field: SecureIdAuthListContentField) {
|
private func openListField(_ field: SecureIdAuthListContentField) {
|
||||||
|
|||||||
@ -25,6 +25,7 @@ struct SecureIdAuthControllerFormState: Equatable {
|
|||||||
var encryptedFormData: SecureIdEncryptedFormData?
|
var encryptedFormData: SecureIdEncryptedFormData?
|
||||||
var formData: SecureIdForm?
|
var formData: SecureIdForm?
|
||||||
var verificationState: SecureIdAuthControllerVerificationState?
|
var verificationState: SecureIdAuthControllerVerificationState?
|
||||||
|
var removingValues: Bool = false
|
||||||
|
|
||||||
static func ==(lhs: SecureIdAuthControllerFormState, rhs: SecureIdAuthControllerFormState) -> Bool {
|
static func ==(lhs: SecureIdAuthControllerFormState, rhs: SecureIdAuthControllerFormState) -> Bool {
|
||||||
if (lhs.formData != nil) != (rhs.formData != nil) {
|
if (lhs.formData != nil) != (rhs.formData != nil) {
|
||||||
@ -47,6 +48,10 @@ struct SecureIdAuthControllerFormState: Equatable {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if lhs.removingValues != rhs.removingValues {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,6 +61,7 @@ struct SecureIdAuthControllerListState: Equatable {
|
|||||||
var encryptedValues: EncryptedAllSecureIdValues?
|
var encryptedValues: EncryptedAllSecureIdValues?
|
||||||
var primaryLanguageByCountry: [String: String]?
|
var primaryLanguageByCountry: [String: String]?
|
||||||
var values: [SecureIdValueWithContext]?
|
var values: [SecureIdValueWithContext]?
|
||||||
|
var removingValues: Bool = false
|
||||||
|
|
||||||
static func ==(lhs: SecureIdAuthControllerListState, rhs: SecureIdAuthControllerListState) -> Bool {
|
static func ==(lhs: SecureIdAuthControllerListState, rhs: SecureIdAuthControllerListState) -> Bool {
|
||||||
if lhs.verificationState != rhs.verificationState {
|
if lhs.verificationState != rhs.verificationState {
|
||||||
@ -70,6 +76,9 @@ struct SecureIdAuthControllerListState: Equatable {
|
|||||||
if lhs.values != rhs.values {
|
if lhs.values != rhs.values {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.removingValues != rhs.removingValues {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,4 +106,13 @@ enum SecureIdAuthControllerState: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var removingValues: Bool {
|
||||||
|
switch self {
|
||||||
|
case let .form(form):
|
||||||
|
return form.removingValues
|
||||||
|
case let .list(list):
|
||||||
|
return list.removingValues
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ private let passwordFont = Font.regular(16.0)
|
|||||||
private let buttonFont = Font.regular(17.0)
|
private let buttonFont = Font.regular(17.0)
|
||||||
|
|
||||||
final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode, UITextFieldDelegate {
|
final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode, UITextFieldDelegate {
|
||||||
|
private let requestedFields: [SecureIdRequestedFormField]
|
||||||
private let fieldBackgroundNode: ASDisplayNode
|
private let fieldBackgroundNode: ASDisplayNode
|
||||||
private let fieldNodes: [SecureIdAuthFormFieldNode]
|
private let fieldNodes: [SecureIdAuthFormFieldNode]
|
||||||
private let headerNode: ImmediateTextNode
|
private let headerNode: ImmediateTextNode
|
||||||
@ -17,14 +18,15 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode,
|
|||||||
private var validLayout: CGFloat?
|
private var validLayout: CGFloat?
|
||||||
|
|
||||||
init(theme: PresentationTheme, strings: PresentationStrings, peer: Peer, privacyPolicyUrl: String?, form: SecureIdForm, openField: @escaping (SecureIdParsedRequestedFormField) -> Void, openURL: @escaping (String) -> Void, openMention: @escaping (TelegramPeerMention) -> Void) {
|
init(theme: PresentationTheme, strings: PresentationStrings, peer: Peer, privacyPolicyUrl: String?, form: SecureIdForm, openField: @escaping (SecureIdParsedRequestedFormField) -> Void, openURL: @escaping (String) -> Void, openMention: @escaping (TelegramPeerMention) -> Void) {
|
||||||
|
self.requestedFields = form.requestedFields
|
||||||
self.fieldBackgroundNode = ASDisplayNode()
|
self.fieldBackgroundNode = ASDisplayNode()
|
||||||
self.fieldBackgroundNode.isLayerBacked = true
|
self.fieldBackgroundNode.isLayerBacked = true
|
||||||
self.fieldBackgroundNode.backgroundColor = theme.list.itemBlocksBackgroundColor
|
self.fieldBackgroundNode.backgroundColor = theme.list.itemBlocksBackgroundColor
|
||||||
|
|
||||||
var fieldNodes: [SecureIdAuthFormFieldNode] = []
|
var fieldNodes: [SecureIdAuthFormFieldNode] = []
|
||||||
|
|
||||||
for field in parseRequestedFormFields(form.requestedFields) {
|
for (field, fieldValues, _) in parseRequestedFormFields(self.requestedFields, values: form.values) {
|
||||||
fieldNodes.append(SecureIdAuthFormFieldNode(theme: theme, strings: strings, field: field, values: form.values, selected: {
|
fieldNodes.append(SecureIdAuthFormFieldNode(theme: theme, strings: strings, field: field, values: fieldValues, selected: {
|
||||||
openField(field)
|
openField(field)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -82,8 +84,12 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateValues(_ values: [SecureIdValueWithContext]) {
|
func updateValues(_ values: [SecureIdValueWithContext]) {
|
||||||
for fieldNode in self.fieldNodes {
|
var index = 0
|
||||||
fieldNode.updateValues(values)
|
for (_, fieldValues, _) in parseRequestedFormFields(self.requestedFields, values: values) {
|
||||||
|
if index < self.fieldNodes.count {
|
||||||
|
self.fieldNodes[index].updateValues(fieldValues)
|
||||||
|
}
|
||||||
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,5 +140,22 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode,
|
|||||||
|
|
||||||
func willDisappear() {
|
func willDisappear() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func frameForField(_ field: SecureIdParsedRequestedFormField) -> CGRect? {
|
||||||
|
for fieldNode in self.fieldNodes {
|
||||||
|
if fieldNode.field == field {
|
||||||
|
return fieldNode.frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func highlightField(_ field: SecureIdParsedRequestedFormField) {
|
||||||
|
for fieldNode in self.fieldNodes {
|
||||||
|
if fieldNode.field == field {
|
||||||
|
fieldNode.highlight()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import SwiftSignalKit
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
@ -46,23 +47,23 @@ enum SecureIdRequestedAddressDocument: Int32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ParsedRequestedPersonalDetails {
|
struct ParsedRequestedPersonalDetails: Equatable {
|
||||||
var nativeNames: Bool
|
var nativeNames: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SecureIdParsedRequestedFormField {
|
enum SecureIdParsedRequestedFormField: Equatable {
|
||||||
case identity(personalDetails: ParsedRequestedPersonalDetails?, document: ParsedRequestedIdentityDocument?, selfie: Bool, translation: Bool)
|
case identity(personalDetails: ParsedRequestedPersonalDetails?, document: ParsedRequestedIdentityDocument?, selfie: Bool, translation: Bool)
|
||||||
case address(addressDetails: Bool, document: ParsedRequestedAddressDocument?, translation: Bool)
|
case address(addressDetails: Bool, document: ParsedRequestedAddressDocument?, translation: Bool)
|
||||||
case phone
|
case phone
|
||||||
case email
|
case email
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ParsedRequestedIdentityDocument {
|
enum ParsedRequestedIdentityDocument: Equatable {
|
||||||
case just(SecureIdRequestedIdentityDocument)
|
case just(SecureIdRequestedIdentityDocument)
|
||||||
case oneOf(Set<SecureIdRequestedIdentityDocument>)
|
case oneOf(Set<SecureIdRequestedIdentityDocument>)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ParsedRequestedAddressDocument {
|
enum ParsedRequestedAddressDocument: Equatable {
|
||||||
case just(SecureIdRequestedAddressDocument)
|
case just(SecureIdRequestedAddressDocument)
|
||||||
case oneOf(Set<SecureIdRequestedAddressDocument>)
|
case oneOf(Set<SecureIdRequestedAddressDocument>)
|
||||||
}
|
}
|
||||||
@ -109,14 +110,14 @@ private struct RequestedFieldValues {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRequestedFormFields(_ types: [SecureIdRequestedFormField]) -> [SecureIdParsedRequestedFormField] {
|
func parseRequestedFormFields(_ types: [SecureIdRequestedFormField], values: [SecureIdValueWithContext]) -> [(SecureIdParsedRequestedFormField, [SecureIdValueWithContext], Bool)] {
|
||||||
var values = RequestedFieldValues()
|
var requestedValues = RequestedFieldValues()
|
||||||
|
|
||||||
for type in types {
|
for type in types {
|
||||||
switch type {
|
switch type {
|
||||||
case let .just(value):
|
case let .just(value):
|
||||||
let subResult = parseRequestedFieldValues(type: value)
|
let subResult = parseRequestedFieldValues(type: value)
|
||||||
values.merge(subResult)
|
requestedValues.merge(subResult)
|
||||||
case let .oneOf(subTypes):
|
case let .oneOf(subTypes):
|
||||||
var oneOfResult = RequestedFieldValues()
|
var oneOfResult = RequestedFieldValues()
|
||||||
var oneOfIdentity = Set<SecureIdRequestedIdentityDocument>()
|
var oneOfIdentity = Set<SecureIdRequestedIdentityDocument>()
|
||||||
@ -145,37 +146,165 @@ func parseRequestedFormFields(_ types: [SecureIdRequestedFormField]) -> [SecureI
|
|||||||
if !oneOfAddress.isEmpty {
|
if !oneOfAddress.isEmpty {
|
||||||
oneOfResult.address.documents.append(.oneOf(oneOfAddress))
|
oneOfResult.address.documents.append(.oneOf(oneOfAddress))
|
||||||
}
|
}
|
||||||
values.merge(oneOfResult)
|
requestedValues.merge(oneOfResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var result: [SecureIdParsedRequestedFormField] = []
|
var result: [SecureIdParsedRequestedFormField] = []
|
||||||
if values.identity.details || !values.identity.documents.isEmpty {
|
if requestedValues.identity.details || !requestedValues.identity.documents.isEmpty {
|
||||||
if values.identity.documents.isEmpty {
|
if requestedValues.identity.documents.isEmpty {
|
||||||
result.append(.identity(personalDetails: ParsedRequestedPersonalDetails(nativeNames: values.identity.nativeNames), document: nil, selfie: false, translation: false))
|
result.append(.identity(personalDetails: ParsedRequestedPersonalDetails(nativeNames: requestedValues.identity.nativeNames), document: nil, selfie: false, translation: false))
|
||||||
} else {
|
} else {
|
||||||
for document in values.identity.documents {
|
for document in requestedValues.identity.documents {
|
||||||
result.append(.identity(personalDetails: values.identity.details ? ParsedRequestedPersonalDetails(nativeNames: values.identity.nativeNames) : nil, document: document, selfie: values.identity.selfie, translation: values.identity.translation))
|
result.append(.identity(personalDetails: requestedValues.identity.details ? ParsedRequestedPersonalDetails(nativeNames: requestedValues.identity.nativeNames) : nil, document: document, selfie: requestedValues.identity.selfie, translation: requestedValues.identity.translation))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if values.address.details || !values.address.documents.isEmpty {
|
if requestedValues.address.details || !requestedValues.address.documents.isEmpty {
|
||||||
if values.address.documents.isEmpty {
|
if requestedValues.address.documents.isEmpty {
|
||||||
result.append(.address(addressDetails: true, document: nil, translation: false))
|
result.append(.address(addressDetails: true, document: nil, translation: false))
|
||||||
} else {
|
} else {
|
||||||
for document in values.address.documents {
|
for document in requestedValues.address.documents {
|
||||||
result.append(.address(addressDetails: values.address.details, document: document, translation: values.address.translation))
|
result.append(.address(addressDetails: requestedValues.address.details, document: document, translation: requestedValues.address.translation))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if values.phone {
|
if requestedValues.phone {
|
||||||
result.append(.phone)
|
result.append(.phone)
|
||||||
}
|
}
|
||||||
if values.email {
|
if requestedValues.email {
|
||||||
result.append(.email)
|
result.append(.email)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result.map { field in
|
||||||
|
let (fieldValues, filled) = findValuesForField(field: field, values: values)
|
||||||
|
return (field, fieldValues, filled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func findValuesForField(field: SecureIdParsedRequestedFormField, values: [SecureIdValueWithContext]) -> ([SecureIdValueWithContext], Bool) {
|
||||||
|
switch field {
|
||||||
|
case let .identity(personalDetails, document, selfie, translation):
|
||||||
|
var filled = true
|
||||||
|
var result: [SecureIdValueWithContext] = []
|
||||||
|
if personalDetails != nil {
|
||||||
|
if let value = findValue(values, key: .personalDetails)?.1 {
|
||||||
|
result.append(value)
|
||||||
|
} else {
|
||||||
|
filled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let document = document {
|
||||||
|
switch document {
|
||||||
|
case let .just(type):
|
||||||
|
if let value = findValue(values, key: type.valueKey)?.1 {
|
||||||
|
result.append(value)
|
||||||
|
let data = extractValueAdditionalData(value.value)
|
||||||
|
if selfie && !data.selfie {
|
||||||
|
filled = false
|
||||||
|
}
|
||||||
|
if translation && !data.translation {
|
||||||
|
filled = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filled = false
|
||||||
|
}
|
||||||
|
case let .oneOf(types):
|
||||||
|
var anyDocument = false
|
||||||
|
var bestMatchingValue: SecureIdValueWithContext?
|
||||||
|
inner: for type in types {
|
||||||
|
if let value = findValue(values, key: type.valueKey)?.1 {
|
||||||
|
if bestMatchingValue == nil {
|
||||||
|
bestMatchingValue = value
|
||||||
|
}
|
||||||
|
let data = extractValueAdditionalData(value.value)
|
||||||
|
var dataFilled = true
|
||||||
|
if selfie && !data.selfie {
|
||||||
|
dataFilled = false
|
||||||
|
}
|
||||||
|
if translation && !data.translation {
|
||||||
|
dataFilled = false
|
||||||
|
}
|
||||||
|
if dataFilled {
|
||||||
|
bestMatchingValue = value
|
||||||
|
anyDocument = true
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !anyDocument {
|
||||||
|
filled = false
|
||||||
|
}
|
||||||
|
if let bestMatchingValue = bestMatchingValue {
|
||||||
|
result.append(bestMatchingValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (result, filled)
|
||||||
|
case let .address(addressDetails, document, translation):
|
||||||
|
var filled = true
|
||||||
|
var result: [SecureIdValueWithContext] = []
|
||||||
|
if addressDetails {
|
||||||
|
if let value = findValue(values, key: .address)?.1 {
|
||||||
|
result.append(value)
|
||||||
|
} else {
|
||||||
|
filled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let document = document {
|
||||||
|
switch document {
|
||||||
|
case let .just(type):
|
||||||
|
if let value = findValue(values, key: type.valueKey)?.1 {
|
||||||
|
result.append(value)
|
||||||
|
let data = extractValueAdditionalData(value.value)
|
||||||
|
if translation && !data.translation {
|
||||||
|
filled = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filled = false
|
||||||
|
}
|
||||||
|
case let .oneOf(types):
|
||||||
|
var anyDocument = false
|
||||||
|
var bestMatchingValue: SecureIdValueWithContext?
|
||||||
|
inner: for type in types {
|
||||||
|
if let value = findValue(values, key: type.valueKey)?.1 {
|
||||||
|
if bestMatchingValue == nil {
|
||||||
|
bestMatchingValue = value
|
||||||
|
}
|
||||||
|
let data = extractValueAdditionalData(value.value)
|
||||||
|
var dataFilled = true
|
||||||
|
if translation && !data.translation {
|
||||||
|
dataFilled = false
|
||||||
|
}
|
||||||
|
if dataFilled {
|
||||||
|
bestMatchingValue = value
|
||||||
|
anyDocument = true
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !anyDocument {
|
||||||
|
filled = false
|
||||||
|
}
|
||||||
|
if let bestMatchingValue = bestMatchingValue {
|
||||||
|
result.append(bestMatchingValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (result, filled)
|
||||||
|
case .phone:
|
||||||
|
if let value = findValue(values, key: .phone)?.1 {
|
||||||
|
return ([value], true)
|
||||||
|
} else {
|
||||||
|
return ([], false)
|
||||||
|
}
|
||||||
|
case .email:
|
||||||
|
if let value = findValue(values, key: .email)?.1 {
|
||||||
|
return ([value], true)
|
||||||
|
} else {
|
||||||
|
return ([], false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func parseRequestedFieldValues(type: SecureIdRequestedFormFieldValue) -> RequestedFieldValues {
|
private func parseRequestedFieldValues(type: SecureIdRequestedFormFieldValue) -> RequestedFieldValues {
|
||||||
@ -268,7 +397,7 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if personalDetails != nil {
|
if personalDetails != nil {
|
||||||
if let value = findValue(values, key: .personalDetails), case let .personalDetails(personalDetailsValue) = value.1 {
|
if let value = findValue(values, key: .personalDetails), case let .personalDetails(personalDetailsValue) = value.1.value {
|
||||||
if !text.isEmpty {
|
if !text.isEmpty {
|
||||||
text.append(", ")
|
text.append(", ")
|
||||||
}
|
}
|
||||||
@ -291,7 +420,7 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if addressDetails {
|
if addressDetails {
|
||||||
if let value = findValue(values, key: .address), case let .address(addressValue) = value.1 {
|
if let value = findValue(values, key: .address), case let .address(addressValue) = value.1.value {
|
||||||
if !text.isEmpty {
|
if !text.isEmpty {
|
||||||
text.append(", ")
|
text.append(", ")
|
||||||
}
|
}
|
||||||
@ -302,7 +431,7 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
|
|||||||
title = strings.Passport_FieldPhone
|
title = strings.Passport_FieldPhone
|
||||||
placeholder = strings.Passport_FieldPhoneHelp
|
placeholder = strings.Passport_FieldPhoneHelp
|
||||||
|
|
||||||
if let value = findValue(values, key: .phone), case let .phone(phoneValue) = value.1 {
|
if let value = findValue(values, key: .phone), case let .phone(phoneValue) = value.1.value {
|
||||||
if !text.isEmpty {
|
if !text.isEmpty {
|
||||||
text.append(", ")
|
text.append(", ")
|
||||||
}
|
}
|
||||||
@ -312,7 +441,7 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
|
|||||||
title = strings.Passport_FieldEmail
|
title = strings.Passport_FieldEmail
|
||||||
placeholder = strings.Passport_FieldEmailHelp
|
placeholder = strings.Passport_FieldEmailHelp
|
||||||
|
|
||||||
if let value = findValue(values, key: .email), case let .email(emailValue) = value.1 {
|
if let value = findValue(values, key: .email), case let .email(emailValue) = value.1.value {
|
||||||
if !text.isEmpty {
|
if !text.isEmpty {
|
||||||
text.append(", ")
|
text.append(", ")
|
||||||
}
|
}
|
||||||
@ -373,7 +502,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var validLayout: (CGFloat, Bool, Bool)?
|
private var validLayout: (CGFloat, Bool, Bool)?
|
||||||
|
|
||||||
private let field: SecureIdParsedRequestedFormField
|
let field: SecureIdParsedRequestedFormField
|
||||||
private let theme: PresentationTheme
|
private let theme: PresentationTheme
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
|
|
||||||
@ -475,7 +604,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
|
|||||||
switch document {
|
switch document {
|
||||||
case let .just(type):
|
case let .just(type):
|
||||||
if let value = findValue(values, key: type.valueKey)?.1 {
|
if let value = findValue(values, key: type.valueKey)?.1 {
|
||||||
let data = extractValueAdditionalData(value)
|
let data = extractValueAdditionalData(value.value)
|
||||||
if selfie && !data.selfie {
|
if selfie && !data.selfie {
|
||||||
filled = false
|
filled = false
|
||||||
}
|
}
|
||||||
@ -489,7 +618,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
|
|||||||
var anyDocument = false
|
var anyDocument = false
|
||||||
for type in types {
|
for type in types {
|
||||||
if let value = findValue(values, key: type.valueKey)?.1 {
|
if let value = findValue(values, key: type.valueKey)?.1 {
|
||||||
let data = extractValueAdditionalData(value)
|
let data = extractValueAdditionalData(value.value)
|
||||||
var dataFilled = true
|
var dataFilled = true
|
||||||
if selfie && !data.selfie {
|
if selfie && !data.selfie {
|
||||||
dataFilled = false
|
dataFilled = false
|
||||||
@ -517,7 +646,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
|
|||||||
switch document {
|
switch document {
|
||||||
case let .just(type):
|
case let .just(type):
|
||||||
if let value = findValue(values, key: type.valueKey)?.1 {
|
if let value = findValue(values, key: type.valueKey)?.1 {
|
||||||
let data = extractValueAdditionalData(value)
|
let data = extractValueAdditionalData(value.value)
|
||||||
if translation && !data.translation {
|
if translation && !data.translation {
|
||||||
filled = false
|
filled = false
|
||||||
}
|
}
|
||||||
@ -528,7 +657,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
|
|||||||
var anyDocument = false
|
var anyDocument = false
|
||||||
for type in types {
|
for type in types {
|
||||||
if let value = findValue(values, key: type.valueKey)?.1 {
|
if let value = findValue(values, key: type.valueKey)?.1 {
|
||||||
let data = extractValueAdditionalData(value)
|
let data = extractValueAdditionalData(value.value)
|
||||||
var dataFilled = true
|
var dataFilled = true
|
||||||
if translation && !data.translation {
|
if translation && !data.translation {
|
||||||
dataFilled = false
|
dataFilled = false
|
||||||
@ -602,4 +731,15 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
|
|||||||
@objc private func buttonPressed() {
|
@objc private func buttonPressed() {
|
||||||
self.selected()
|
self.selected()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func highlight() {
|
||||||
|
self.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||||
|
self.highlightedBackgroundNode.alpha = 1.0
|
||||||
|
self.view.superview?.bringSubview(toFront: self.view)
|
||||||
|
|
||||||
|
Queue.mainQueue().after(1.0, {
|
||||||
|
self.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
self.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,45 +64,58 @@ final class SecureIdAuthHeaderNode: ASDisplayNode {
|
|||||||
self.verificationState = verificationState
|
self.verificationState = verificationState
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> (compact: CGFloat, expanded: CGFloat, apply: (Bool) -> Void) {
|
||||||
if !self.iconNode.isHidden {
|
if !self.iconNode.isHidden {
|
||||||
guard let image = self.iconNode.image else {
|
guard let image = self.iconNode.image else {
|
||||||
return 1.0
|
return (1.0, 1.0, { _ in
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0), y: 0.0), size: image.size)
|
return (image.size.height, image.size.height, { [weak self] _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
let resultHeight: CGFloat = image.size.height
|
return
|
||||||
return resultHeight
|
}
|
||||||
|
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0), y: 0.0), size: image.size)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
let avatarSize = CGSize(width: 70.0, height: 70.0)
|
let avatarSize = CGSize(width: 70.0, height: 70.0)
|
||||||
|
|
||||||
let serviceAvatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize.width) / 2.0), y: 0.0), size: avatarSize)
|
|
||||||
transition.updateFrame(node: self.serviceAvatarNode, frame: serviceAvatarFrame)
|
|
||||||
|
|
||||||
if let verificationState = self.verificationState, case .noChallenge = verificationState {
|
|
||||||
transition.updateAlpha(node: self.serviceAvatarNode, alpha: 0.0)
|
|
||||||
} else {
|
|
||||||
transition.updateAlpha(node: self.serviceAvatarNode, alpha: 1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
let avatarTitleSpacing: CGFloat = 20.0
|
let avatarTitleSpacing: CGFloat = 20.0
|
||||||
|
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: width - 20.0, height: 1000.0))
|
let titleSize = self.titleNode.updateLayout(CGSize(width: width - 20.0, height: 1000.0))
|
||||||
|
|
||||||
var titleOffset: CGFloat = 0.0
|
var expandedHeight: CGFloat = titleSize.height
|
||||||
if !self.serviceAvatarNode.alpha.isZero {
|
if !self.serviceAvatarNode.isHidden {
|
||||||
titleOffset = avatarSize.height + avatarTitleSpacing
|
expandedHeight += avatarSize.height + avatarTitleSpacing
|
||||||
}
|
}
|
||||||
|
let compactHeight = titleSize.height
|
||||||
|
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: titleOffset), size: titleSize)
|
return (compactHeight, expandedHeight, { [weak self] expanded in
|
||||||
ContainedViewLayoutTransition.immediate.updateFrame(node: self.titleNode, frame: titleFrame)
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
var resultHeight: CGFloat = titleSize.height
|
}
|
||||||
if !self.serviceAvatarNode.alpha.isZero {
|
transition.updateAlpha(node: strongSelf.serviceAvatarNode, alpha: expanded ? 1.0 : 0.0)
|
||||||
resultHeight += avatarSize.height + avatarTitleSpacing
|
|
||||||
}
|
var titleOffset: CGFloat = 0.0
|
||||||
return resultHeight
|
if expanded && !strongSelf.serviceAvatarNode.isHidden && !strongSelf.serviceAvatarNode.alpha.isZero {
|
||||||
|
titleOffset = avatarSize.height + avatarTitleSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: titleOffset), size: titleSize)
|
||||||
|
let previousTitleFrame = strongSelf.titleNode.frame
|
||||||
|
ContainedViewLayoutTransition.immediate.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
|
||||||
|
transition.animatePositionAdditive(node: strongSelf.titleNode, offset: CGPoint(x: 0.0, y: previousTitleFrame.midY - titleFrame.midY))
|
||||||
|
|
||||||
|
let serviceAvatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize.width) / 2.0), y: titleFrame.minY - avatarTitleSpacing - avatarSize.height), size: avatarSize)
|
||||||
|
transition.updateFrame(node: strongSelf.serviceAvatarNode, frame: serviceAvatarFrame)
|
||||||
|
|
||||||
|
if let verificationState = strongSelf.verificationState, case .noChallenge = verificationState {
|
||||||
|
strongSelf.serviceAvatarNode.isHidden = true
|
||||||
|
} else {
|
||||||
|
strongSelf.serviceAvatarNode.isHidden = false
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,7 +87,7 @@ private func fieldTitleAndText(field: SecureIdAuthListContentField, strings: Pre
|
|||||||
title = strings.Passport_FieldPhone
|
title = strings.Passport_FieldPhone
|
||||||
placeholder = strings.Passport_FieldPhoneHelp
|
placeholder = strings.Passport_FieldPhoneHelp
|
||||||
|
|
||||||
if let value = findValue(values, key: .phone), case let .phone(phoneValue) = value.1 {
|
if let value = findValue(values, key: .phone), case let .phone(phoneValue) = value.1.value {
|
||||||
if !text.isEmpty {
|
if !text.isEmpty {
|
||||||
text.append(", ")
|
text.append(", ")
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@ private func fieldTitleAndText(field: SecureIdAuthListContentField, strings: Pre
|
|||||||
title = strings.Passport_FieldEmail
|
title = strings.Passport_FieldEmail
|
||||||
placeholder = strings.Passport_FieldEmailHelp
|
placeholder = strings.Passport_FieldEmailHelp
|
||||||
|
|
||||||
if let value = findValue(values, key: .email), case let .email(emailValue) = value.1 {
|
if let value = findValue(values, key: .email), case let .email(emailValue) = value.1.value {
|
||||||
if !text.isEmpty {
|
if !text.isEmpty {
|
||||||
text.append(", ")
|
text.append(", ")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2347,7 +2347,7 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode<SecureIdDocum
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let backSideDocument = innerState.backSideDocument, backSideDocument.resource.isEqual(to: resource) {
|
if let backSideDocument = innerState.backSideDocument, backSideDocument.resource.isEqual(to: resource) {
|
||||||
innerState.selfieDocument = nil
|
innerState.backSideDocument = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0 ..< innerState.documents.count {
|
for i in 0 ..< innerState.documents.count {
|
||||||
|
|||||||
@ -424,7 +424,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacySettingsKind, current: SelectivePrivacySettings, callSettings: VoiceCallSettings? = nil, callIntegrationAvailable: Bool? = nil, updated: @escaping (SelectivePrivacySettings, VoiceCallSettings?) -> Void) -> ViewController {
|
func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacySettingsKind, current: SelectivePrivacySettings, callSettings: VoiceCallSettings? = nil, voipConfiguration: VoipConfiguration? = nil, callIntegrationAvailable: Bool? = nil, updated: @escaping (SelectivePrivacySettings, VoiceCallSettings?) -> Void) -> ViewController {
|
||||||
let strings = account.telegramApplicationContext.currentPresentationData.with { $0 }.strings
|
let strings = account.telegramApplicationContext.currentPresentationData.with { $0 }.strings
|
||||||
|
|
||||||
var initialEnableFor = Set<PeerId>()
|
var initialEnableFor = Set<PeerId>()
|
||||||
@ -438,7 +438,7 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy
|
|||||||
case let .enableEveryone(disableFor):
|
case let .enableEveryone(disableFor):
|
||||||
initialDisableFor = disableFor
|
initialDisableFor = disableFor
|
||||||
}
|
}
|
||||||
let initialState = SelectivePrivacySettingsControllerState(setting: SelectivePrivacySettingType(current), enableFor: initialEnableFor, disableFor: initialDisableFor, saving: false, callDataSaving: callSettings?.dataSaving, callP2PMode: callSettings?.p2pMode, callIntegrationAvailable: callIntegrationAvailable, callIntegrationEnabled: callSettings?.enableSystemIntegration)
|
let initialState = SelectivePrivacySettingsControllerState(setting: SelectivePrivacySettingType(current), enableFor: initialEnableFor, disableFor: initialDisableFor, saving: false, callDataSaving: callSettings?.dataSaving, callP2PMode: callSettings?.p2pMode ?? voipConfiguration?.defaultP2PMode, callIntegrationAvailable: callIntegrationAvailable, callIntegrationEnabled: callSettings?.enableSystemIntegration)
|
||||||
|
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: initialState)
|
let stateValue = Atomic(value: initialState)
|
||||||
|
|||||||
@ -95,6 +95,8 @@ public final class TelegramApplicationContext {
|
|||||||
}
|
}
|
||||||
private var experimentalUISettingsDisposable: Disposable?
|
private var experimentalUISettingsDisposable: Disposable?
|
||||||
|
|
||||||
|
private var storedPassword: (String, CFAbsoluteTime, SwiftSignalKit.Timer)?
|
||||||
|
|
||||||
public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, account: Account?, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, postbox: Postbox) {
|
public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, account: Account?, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, postbox: Postbox) {
|
||||||
self.mediaManager = MediaManager(postbox: postbox, inForeground: applicationBindings.applicationInForeground)
|
self.mediaManager = MediaManager(postbox: postbox, inForeground: applicationBindings.applicationInForeground)
|
||||||
|
|
||||||
@ -210,6 +212,27 @@ public final class TelegramApplicationContext {
|
|||||||
public func attachOverlayMediaController(_ controller: OverlayMediaController) {
|
public func attachOverlayMediaController(_ controller: OverlayMediaController) {
|
||||||
self.mediaManager.overlayMediaManager.attachOverlayMediaController(controller)
|
self.mediaManager.overlayMediaManager.attachOverlayMediaController(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func storeSecureIdPassword(password: String) {
|
||||||
|
self.storedPassword?.2.invalidate()
|
||||||
|
let timer = SwiftSignalKit.Timer(timeout: 1.0 * 60.0 * 60.0, repeat: false, completion: { [weak self] in
|
||||||
|
self?.storedPassword = nil
|
||||||
|
}, queue: Queue.mainQueue())
|
||||||
|
self.storedPassword = (password, CFAbsoluteTimeGetCurrent(), timer)
|
||||||
|
timer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getStoredSecureIdPassword() -> String? {
|
||||||
|
if let (password, timestamp, timer) = self.storedPassword {
|
||||||
|
if CFAbsoluteTimeGetCurrent() > timestamp + 1.0 * 60.0 * 60.0 {
|
||||||
|
timer.invalidate()
|
||||||
|
self.storedPassword = nil
|
||||||
|
}
|
||||||
|
return password
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Account {
|
public extension Account {
|
||||||
|
|||||||
@ -562,44 +562,46 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep
|
|||||||
})
|
})
|
||||||
|
|
||||||
var initialFocusImpl: (() -> Void)?
|
var initialFocusImpl: (() -> Void)?
|
||||||
|
var didAppear = false
|
||||||
|
|
||||||
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), dataPromise.get() |> deliverOnMainQueue) |> deliverOnMainQueue
|
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), dataPromise.get() |> deliverOnMainQueue) |> deliverOnMainQueue
|
||||||
|> map { presentationData, state, data -> (ItemListControllerState, (ItemListNodeState<TwoStepVerificationUnlockSettingsEntry>, TwoStepVerificationUnlockSettingsEntry.ItemGenerationArguments)) in
|
|> map { presentationData, state, data -> (ItemListControllerState, (ItemListNodeState<TwoStepVerificationUnlockSettingsEntry>, TwoStepVerificationUnlockSettingsEntry.ItemGenerationArguments)) in
|
||||||
|
|
||||||
var rightNavigationButton: ItemListNavigationButton?
|
var rightNavigationButton: ItemListNavigationButton?
|
||||||
var emptyStateItem: ItemListControllerEmptyStateItem?
|
var emptyStateItem: ItemListControllerEmptyStateItem?
|
||||||
let title: String
|
let title: String
|
||||||
switch data {
|
switch data {
|
||||||
case let .access(configuration):
|
case let .access(configuration):
|
||||||
title = presentationData.strings.TwoStepAuth_Title
|
title = presentationData.strings.TwoStepAuth_Title
|
||||||
if let configuration = configuration {
|
if let configuration = configuration {
|
||||||
if state.checking {
|
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
|
||||||
} else {
|
|
||||||
switch configuration {
|
|
||||||
case .notSet:
|
|
||||||
break
|
|
||||||
case let .set(_, _, _, hasSecureValues):
|
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Next), style: .bold, enabled: true, action: {
|
|
||||||
arguments.checkPassword()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
|
|
||||||
}
|
|
||||||
case .manage:
|
|
||||||
title = presentationData.strings.PrivacySettings_TwoStepAuth
|
|
||||||
if state.checking {
|
if state.checking {
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||||
|
} else {
|
||||||
|
switch configuration {
|
||||||
|
case .notSet:
|
||||||
|
break
|
||||||
|
case let .set(_, _, _, hasSecureValues):
|
||||||
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Next), style: .bold, enabled: true, action: {
|
||||||
|
arguments.checkPassword()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
|
||||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
}
|
||||||
let listState = ItemListNodeState(entries: twoStepVerificationUnlockSettingsControllerEntries(presentationData: presentationData, state: state, data: data), style: .blocks, focusItemTag: TwoStepVerificationUnlockSettingsEntryTag.password, emptyStateItem: emptyStateItem, animateChanges: false)
|
case .manage:
|
||||||
|
title = presentationData.strings.PrivacySettings_TwoStepAuth
|
||||||
return (controllerState, (listState, arguments))
|
if state.checking {
|
||||||
} |> afterDisposed {
|
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||||
|
let listState = ItemListNodeState(entries: twoStepVerificationUnlockSettingsControllerEntries(presentationData: presentationData, state: state, data: data), style: .blocks, focusItemTag: didAppear ? TwoStepVerificationUnlockSettingsEntryTag.password : nil, emptyStateItem: emptyStateItem, animateChanges: false)
|
||||||
|
|
||||||
|
return (controllerState, (listState, arguments))
|
||||||
|
}
|
||||||
|
|> afterDisposed {
|
||||||
actionsDisposable.dispose()
|
actionsDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -632,6 +634,7 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep
|
|||||||
if !firstTime {
|
if !firstTime {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
didAppear = true
|
||||||
initialFocusImpl?()
|
initialFocusImpl?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Postbox
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
public enum VoiceCallDataSaving: Int32 {
|
public enum VoiceCallDataSaving: Int32 {
|
||||||
@ -8,22 +9,16 @@ public enum VoiceCallDataSaving: Int32 {
|
|||||||
case always
|
case always
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum VoiceCallP2PMode: Int32 {
|
|
||||||
case never = 0
|
|
||||||
case contacts = 1
|
|
||||||
case always = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct VoiceCallSettings: PreferencesEntry, Equatable {
|
public struct VoiceCallSettings: PreferencesEntry, Equatable {
|
||||||
public var dataSaving: VoiceCallDataSaving
|
public var dataSaving: VoiceCallDataSaving
|
||||||
public var p2pMode: VoiceCallP2PMode
|
public var p2pMode: VoiceCallP2PMode?
|
||||||
public var enableSystemIntegration: Bool
|
public var enableSystemIntegration: Bool
|
||||||
|
|
||||||
public static var defaultSettings: VoiceCallSettings {
|
public static var defaultSettings: VoiceCallSettings {
|
||||||
return VoiceCallSettings(dataSaving: .never, p2pMode: .contacts, enableSystemIntegration: true)
|
return VoiceCallSettings(dataSaving: .never, p2pMode: nil, enableSystemIntegration: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(dataSaving: VoiceCallDataSaving, p2pMode: VoiceCallP2PMode, enableSystemIntegration: Bool) {
|
init(dataSaving: VoiceCallDataSaving, p2pMode: VoiceCallP2PMode?, enableSystemIntegration: Bool) {
|
||||||
self.dataSaving = dataSaving
|
self.dataSaving = dataSaving
|
||||||
self.p2pMode = p2pMode
|
self.p2pMode = p2pMode
|
||||||
self.enableSystemIntegration = enableSystemIntegration
|
self.enableSystemIntegration = enableSystemIntegration
|
||||||
@ -31,13 +26,21 @@ public struct VoiceCallSettings: PreferencesEntry, Equatable {
|
|||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
self.dataSaving = VoiceCallDataSaving(rawValue: decoder.decodeInt32ForKey("ds", orElse: 0))!
|
self.dataSaving = VoiceCallDataSaving(rawValue: decoder.decodeInt32ForKey("ds", orElse: 0))!
|
||||||
self.p2pMode = VoiceCallP2PMode(rawValue: decoder.decodeInt32ForKey("p2pMode", orElse: 1))!
|
if let value = decoder.decodeOptionalInt32ForKey("p2pMode") {
|
||||||
|
self.p2pMode = VoiceCallP2PMode(rawValue: value) ?? .contacts
|
||||||
|
} else {
|
||||||
|
self.p2pMode = nil
|
||||||
|
}
|
||||||
self.enableSystemIntegration = decoder.decodeInt32ForKey("enableSystemIntegration", orElse: 1) != 0
|
self.enableSystemIntegration = decoder.decodeInt32ForKey("enableSystemIntegration", orElse: 1) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
encoder.encodeInt32(self.dataSaving.rawValue, forKey: "ds")
|
encoder.encodeInt32(self.dataSaving.rawValue, forKey: "ds")
|
||||||
encoder.encodeInt32(self.p2pMode.rawValue, forKey: "p2pMode")
|
if let p2pMode = self.p2pMode {
|
||||||
|
encoder.encodeInt32(p2pMode.rawValue, forKey: "p2pMode")
|
||||||
|
} else {
|
||||||
|
encoder.encodeNil(forKey: "p2pMode")
|
||||||
|
}
|
||||||
encoder.encodeInt32(self.enableSystemIntegration ? 1 : 0, forKey: "enableSystemIntegration")
|
encoder.encodeInt32(self.enableSystemIntegration ? 1 : 0, forKey: "enableSystemIntegration")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user