mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' into features/64-bit
This commit is contained in:
commit
ee8ec6ed30
@ -138,7 +138,7 @@ private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||
self.audioConverter = CustomAudioConverter(asbd: asbd)
|
||||
}
|
||||
if let audioConverter = self.audioConverter {
|
||||
if let data = audioConverter.convert(sampleBuffer: sampleBuffer) {
|
||||
if let data = audioConverter.convert(sampleBuffer: sampleBuffer), !data.isEmpty {
|
||||
self.screencastBufferClientContext?.writeAudioData(data: data)
|
||||
}
|
||||
}
|
||||
|
@ -6532,7 +6532,7 @@ Sorry for the inconvenience.";
|
||||
"Settings.TryEnterPassword" = "Not sure, let me try";
|
||||
|
||||
"TwoFactorSetup.PasswordRecovery.Title" = "Create New Password";
|
||||
"TwoFactorSetup.PasswordRecovery.Text" = "You have successfully reset your password.\nPlease enter a new password to continue";
|
||||
"TwoFactorSetup.PasswordRecovery.Text" = "You can now set a new password that will be used to log into your account.";
|
||||
"TwoFactorSetup.PasswordRecovery.PlaceholderPassword" = "New Password";
|
||||
"TwoFactorSetup.PasswordRecovery.PlaceholderConfirmPassword" = "Re-enter New Password";
|
||||
"TwoFactorSetup.PasswordRecovery.Action" = "Continue";
|
||||
@ -6549,3 +6549,8 @@ Sorry for the inconvenience.";
|
||||
"TwoStepAuth.CancelResetTitle" = "Cancel Reset";
|
||||
"TwoStepAuth.ResetAction" = "Reset Password";
|
||||
"TwoStepAuth.CancelResetText" = "Cancel the password resetting process? If you proceed, the expired part of the 7-day delay will be lost.";
|
||||
"TwoStepAuth.RecoveryEmailResetNoAccess" = "Lost access to your Email?";
|
||||
|
||||
"TwoFactorSetup.ResetDone.Title" = "New Password Set!";
|
||||
"TwoFactorSetup.ResetDone.Text" = "This password will be required when you log in on a new device in addition to the code you get via SMS.";
|
||||
"TwoFactorSetup.ResetDone.Action" = "Continue";
|
||||
|
@ -686,7 +686,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
||||
controller.dismiss()
|
||||
}
|
||||
switch update {
|
||||
case .noPassword, .awaitingEmailConfirmation:
|
||||
case .noPassword, .awaitingEmailConfirmation, .pendingPasswordReset:
|
||||
break
|
||||
case .passwordSet:
|
||||
var updatedToken = webToken
|
||||
@ -754,7 +754,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
||||
controller.dismiss()
|
||||
}
|
||||
switch update {
|
||||
case .noPassword, .awaitingEmailConfirmation:
|
||||
case .noPassword, .awaitingEmailConfirmation, .pendingPasswordReset:
|
||||
break
|
||||
case .passwordSet:
|
||||
var updatedToken = webToken
|
||||
@ -810,7 +810,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
||||
controller.dismiss()
|
||||
}
|
||||
switch update {
|
||||
case .noPassword, .awaitingEmailConfirmation:
|
||||
case .noPassword, .awaitingEmailConfirmation, .pendingPasswordReset:
|
||||
break
|
||||
case .passwordSet:
|
||||
var updatedToken = token
|
||||
|
@ -322,7 +322,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
isMuted = true
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
let _ = (togglePeerMuted(account: context.account, peerId: peerId)
|
||||
let _ = (context.engine.peers.togglePeerMuted(peerId: peerId)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
f(.default)
|
||||
})
|
||||
@ -332,7 +332,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
if case .search = source {
|
||||
if let _ = peer as? TelegramChannel {
|
||||
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_JoinChannel, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
var createSignal = context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peerId, hash: nil)
|
||||
var createSignal = context.peerChannelMemberCategoriesContextsManager.join(engine: context.engine, peerId: peerId, hash: nil)
|
||||
var cancelImpl: (() -> Void)?
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
@ -688,7 +688,7 @@ public final class ChatListNode: ListView {
|
||||
return
|
||||
}
|
||||
strongSelf.setCurrentRemovingPeerId(peerId)
|
||||
let _ = (togglePeerMuted(account: context.account, peerId: peerId)
|
||||
let _ = (context.engine.peers.togglePeerMuted(peerId: peerId)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
self?.updateState { state in
|
||||
var state = state
|
||||
|
@ -365,7 +365,7 @@ final class InviteContactsControllerNode: ASDisplayNode {
|
||||
return DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phoneNumber.value))
|
||||
})))
|
||||
}
|
||||
return deviceContactsImportedByCount(postbox: context.account.postbox, contacts: mappedContacts)
|
||||
return context.engine.contacts.deviceContactsImportedByCount(contacts: mappedContacts)
|
||||
|> map { counts -> [(DeviceContactStableId, DeviceContactBasicData, Int32)]? in
|
||||
var result: [(DeviceContactStableId, DeviceContactBasicData, Int32)] = []
|
||||
var contactValues: [DeviceContactStableId: DeviceContactBasicData] = [:]
|
||||
|
@ -310,7 +310,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
|
||||
@objc func joinPressed() {
|
||||
if let peer = self.peer, case .notJoined = self.joinState {
|
||||
self.updateJoinState(.inProgress)
|
||||
self.joinDisposable.set((joinChannel(account: self.context.account, peerId: peer.id, hash: nil) |> deliverOnMainQueue).start(error: { [weak self] _ in
|
||||
self.joinDisposable.set((self.context.engine.peers.joinChannel(peerId: peer.id, hash: nil) |> deliverOnMainQueue).start(error: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
if case .inProgress = strongSelf.joinState {
|
||||
strongSelf.updateJoinState(.notJoined)
|
||||
|
@ -1,24 +0,0 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "MessageReactionListUI",
|
||||
module_name = "MessageReactionListUI",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/SyncCore:SyncCore",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/MergeLists:MergeLists",
|
||||
"//submodules/ItemListPeerItem:ItemListPeerItem",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -1,104 +0,0 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
|
||||
final class MessageReactionCategoryNode: ASDisplayNode {
|
||||
let category: MessageReactionListCategory
|
||||
private let action: () -> Void
|
||||
|
||||
private let buttonNode: HighlightableButtonNode
|
||||
private let highlightedBackgroundNode: ASImageNode
|
||||
private let iconNode: ASImageNode
|
||||
private let emojiNode: ImmediateTextNode
|
||||
private let countNode: ImmediateTextNode
|
||||
|
||||
var isSelected = false {
|
||||
didSet {
|
||||
self.highlightedBackgroundNode.alpha = self.isSelected ? 1.0 : 0.0
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: PresentationTheme, category: MessageReactionListCategory, count: Int, action: @escaping () -> Void) {
|
||||
self.category = category
|
||||
self.action = action
|
||||
|
||||
self.buttonNode = HighlightableButtonNode()
|
||||
|
||||
self.highlightedBackgroundNode = ASImageNode()
|
||||
self.highlightedBackgroundNode.displaysAsynchronously = false
|
||||
self.highlightedBackgroundNode.displayWithoutProcessing = true
|
||||
self.highlightedBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: UIColor(rgb: 0xe6e6e8))
|
||||
self.highlightedBackgroundNode.alpha = 1.0
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
|
||||
self.emojiNode = ImmediateTextNode()
|
||||
self.emojiNode.displaysAsynchronously = false
|
||||
let emojiText: String
|
||||
switch category {
|
||||
case .all:
|
||||
emojiText = ""
|
||||
self.iconNode.image = PresentationResourcesChat.chatInputTextFieldTimerImage(theme)
|
||||
case let .reaction(value):
|
||||
emojiText = value
|
||||
}
|
||||
self.emojiNode.attributedText = NSAttributedString(string: emojiText, font: Font.regular(18.0), textColor: .black)
|
||||
|
||||
self.countNode = ImmediateTextNode()
|
||||
self.countNode.displaysAsynchronously = false
|
||||
self.countNode.attributedText = NSAttributedString(string: "\(count)", font: Font.regular(16.0), textColor: .black)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.highlightedBackgroundNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.emojiNode)
|
||||
self.addSubnode(self.countNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
func updateLayout() -> CGSize {
|
||||
let sideInset: CGFloat = 6.0
|
||||
let spacing: CGFloat = 2.0
|
||||
let emojiSize = self.emojiNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||
let iconSize = self.iconNode.image?.size ?? CGSize()
|
||||
let countSize = self.countNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||
|
||||
let height: CGFloat = 60.0
|
||||
let backgroundHeight: CGFloat = 36.0
|
||||
|
||||
self.emojiNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floor((height - emojiSize.height) / 2.0)), size: emojiSize)
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floor((height - iconSize.height) / 2.0)), size: iconSize)
|
||||
|
||||
let iconFrame: CGRect
|
||||
if self.iconNode.image != nil {
|
||||
iconFrame = self.iconNode.frame
|
||||
} else {
|
||||
iconFrame = self.emojiNode.frame
|
||||
}
|
||||
|
||||
self.countNode.frame = CGRect(origin: CGPoint(x: iconFrame.maxX + spacing, y: floor((height - countSize.height) / 2.0)), size: countSize)
|
||||
let contentWidth = sideInset * 2.0 + spacing + iconFrame.width + countSize.width
|
||||
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - backgroundHeight) / 2.0)), size: CGSize(width: contentWidth, height: backgroundHeight))
|
||||
|
||||
let size = CGSize(width: contentWidth, height: height)
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
return size
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.action()
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.buttonNode.frame.contains(point) {
|
||||
return self.buttonNode.view
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,447 +0,0 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import SwiftSignalKit
|
||||
import MergeLists
|
||||
import ItemListPeerItem
|
||||
import ItemListUI
|
||||
|
||||
public final class MessageReactionListController: ViewController {
|
||||
private let context: AccountContext
|
||||
private let messageId: MessageId
|
||||
private let presentationData: PresentationData
|
||||
private let initialReactions: [MessageReaction]
|
||||
|
||||
private var controllerNode: MessageReactionListControllerNode {
|
||||
return self.displayNode as! MessageReactionListControllerNode
|
||||
}
|
||||
|
||||
private var animatedIn: Bool = false
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
public init(context: AccountContext, messageId: MessageId, initialReactions: [MessageReaction]) {
|
||||
self.context = context
|
||||
self.messageId = messageId
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.initialReactions = initialReactions
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = MessageReactionListControllerNode(context: self.context, presentationData: self.presentationData, messageId: messageId, initialReactions: initialReactions, dismiss: { [weak self] in
|
||||
self?.dismiss()
|
||||
})
|
||||
|
||||
super.displayNodeDidLoad()
|
||||
|
||||
self._ready.set(self.controllerNode.isReady.get())
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout: layout, transition: transition)
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if !self.animatedIn {
|
||||
self.animatedIn = true
|
||||
self.controllerNode.animateIn()
|
||||
}
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.controllerNode.animateOut(completion: { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private struct MessageReactionListTransaction {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
}
|
||||
|
||||
private struct MessageReactionListEntry: Comparable, Identifiable {
|
||||
let index: Int
|
||||
let item: MessageReactionListCategoryItem
|
||||
|
||||
var stableId: PeerId {
|
||||
return self.item.peer.id
|
||||
}
|
||||
|
||||
static func <(lhs: MessageReactionListEntry, rhs: MessageReactionListEntry) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(context: AccountContext, presentationData: PresentationData) -> ListViewItem {
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: self.item.peer, height: .peerList, nameStyle: .distinctBold, presence: nil, text: .none, label: .text(self.item.reaction, .custom(Font.regular(19.0))), editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: false, sectionId: 0, action: {
|
||||
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, noInsets: true, tag: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func preparedTransition(from fromEntries: [MessageReactionListEntry], to toEntries: [MessageReactionListEntry], context: AccountContext, presentationData: PresentationData) -> MessageReactionListTransaction {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData), directionHint: nil) }
|
||||
|
||||
return MessageReactionListTransaction(deletions: deletions, insertions: insertions, updates: updates)
|
||||
}
|
||||
|
||||
private let headerHeight: CGFloat = 60.0
|
||||
private let itemHeight: CGFloat = 50.0
|
||||
|
||||
private func topInsetForLayout(layout: ContainerViewLayout, itemCount: Int) -> CGFloat {
|
||||
let contentHeight = CGFloat(itemCount) * itemHeight
|
||||
let minimumItemHeights: CGFloat = max(contentHeight, itemHeight * 5.0)
|
||||
|
||||
return max(layout.size.height - layout.intrinsicInsets.bottom - minimumItemHeights, headerHeight)
|
||||
}
|
||||
|
||||
private final class MessageReactionListControllerNode: ViewControllerTracingNode {
|
||||
private let context: AccountContext
|
||||
private let presentationData: PresentationData
|
||||
private let dismiss: () -> Void
|
||||
|
||||
private let listContext: MessageReactionListContext
|
||||
|
||||
private let dimNode: ASDisplayNode
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let contentHeaderContainerNode: ASDisplayNode
|
||||
private let contentHeaderContainerBackgroundNode: ASImageNode
|
||||
private let contentHeaderContainerSeparatorNode: ASDisplayNode
|
||||
private var categoryItemNodes: [MessageReactionCategoryNode] = []
|
||||
private let categoryScrollNode: ASScrollNode
|
||||
private let listNode: ListView
|
||||
private var placeholderNode: MessageReactionListLoadingPlaceholder?
|
||||
private var placeholderNodeIsAnimatingOut = false
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private var currentCategory: MessageReactionListCategory = .all
|
||||
private var currentState: MessageReactionListState?
|
||||
|
||||
private var enqueuedTransactions: [MessageReactionListTransaction] = []
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
let isReady = Promise<Bool>()
|
||||
|
||||
private var forceHeaderTransition: ContainedViewLayoutTransition?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, messageId: MessageId, initialReactions: [MessageReaction], dismiss: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.dismiss = dismiss
|
||||
|
||||
self.dimNode = ASDisplayNode()
|
||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor
|
||||
|
||||
self.contentHeaderContainerNode = ASDisplayNode()
|
||||
self.contentHeaderContainerBackgroundNode = ASImageNode()
|
||||
self.contentHeaderContainerBackgroundNode.displaysAsynchronously = false
|
||||
|
||||
self.contentHeaderContainerSeparatorNode = ASDisplayNode()
|
||||
self.contentHeaderContainerSeparatorNode.backgroundColor = self.presentationData.theme.list.itemPlainSeparatorColor
|
||||
|
||||
self.categoryScrollNode = ASScrollNode()
|
||||
self.contentHeaderContainerBackgroundNode.displayWithoutProcessing = true
|
||||
self.contentHeaderContainerBackgroundNode.image = generateImage(CGSize(width: 10.0, height: 10.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(presentationData.theme.rootController.navigationBar.opaqueBackgroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.height / 2.0), size: CGSize(width: size.width, height: size.height / 2.0)))
|
||||
})?.stretchableImage(withLeftCapWidth: 5, topCapHeight: 5)
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.limitHitTestToNodes = true
|
||||
self.listNode.accessibilityPageScrolledString = { row, count in
|
||||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).0
|
||||
}
|
||||
|
||||
self.placeholderNode = MessageReactionListLoadingPlaceholder(theme: presentationData.theme, itemHeight: itemHeight)
|
||||
self.placeholderNode?.isUserInteractionEnabled = false
|
||||
|
||||
self.listContext = MessageReactionListContext(postbox: self.context.account.postbox, network: self.context.account.network, messageId: messageId, initialReactions: initialReactions)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.dimNode)
|
||||
self.addSubnode(self.backgroundNode)
|
||||
|
||||
self.listNode.stackFromBottom = false
|
||||
self.addSubnode(self.listNode)
|
||||
self.placeholderNode.flatMap(self.addSubnode)
|
||||
|
||||
self.addSubnode(self.contentHeaderContainerNode)
|
||||
self.contentHeaderContainerNode.addSubnode(self.contentHeaderContainerBackgroundNode)
|
||||
self.contentHeaderContainerNode.addSubnode(self.contentHeaderContainerSeparatorNode)
|
||||
self.contentHeaderContainerNode.addSubnode(self.categoryScrollNode)
|
||||
|
||||
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in
|
||||
guard let strongSelf = self, let layout = strongSelf.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
let transition = strongSelf.forceHeaderTransition ?? listTransition
|
||||
strongSelf.forceHeaderTransition = nil
|
||||
|
||||
let topOffset = offset
|
||||
transition.updateFrame(node: strongSelf.contentHeaderContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topOffset - headerHeight), size: CGSize(width: layout.size.width, height: headerHeight)))
|
||||
transition.updateFrame(node: strongSelf.contentHeaderContainerBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: headerHeight)))
|
||||
transition.updateFrame(node: strongSelf.contentHeaderContainerSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: headerHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||
if let placeholderNode = strongSelf.placeholderNode {
|
||||
transition.updateFrame(node: placeholderNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topOffset), size: placeholderNode.bounds.size))
|
||||
}
|
||||
transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topOffset - headerHeight / 2.0), size: CGSize(width: layout.size.width, height: layout.size.height + 300.0)))
|
||||
}
|
||||
|
||||
self.disposable.set((self.listContext.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
self?.updateState(state)
|
||||
}))
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTapGesture)))
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
let isFirstLayout = self.validLayout == nil
|
||||
self.validLayout = layout
|
||||
|
||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
transition.updateBounds(node: self.listNode, bounds: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height))
|
||||
transition.updatePosition(node: self.listNode, position: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0))
|
||||
|
||||
var currentCategoryItemCount = 0
|
||||
if let currentState = self.currentState {
|
||||
for (category, categoryState) in currentState.states {
|
||||
if category == self.currentCategory {
|
||||
currentCategoryItemCount = categoryState.count
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var insets = UIEdgeInsets()
|
||||
insets.top = topInsetForLayout(layout: layout, itemCount: currentCategoryItemCount)
|
||||
insets.bottom = layout.intrinsicInsets.bottom
|
||||
|
||||
if let placeholderNode = self.placeholderNode, !self.placeholderNodeIsAnimatingOut {
|
||||
let placeholderHeight = min(CGFloat(currentCategoryItemCount) * itemHeight, layout.size.height) + UIScreenPixel
|
||||
placeholderNode.frame = CGRect(origin: placeholderNode.frame.origin, size: CGSize(width: layout.size.width, height: placeholderHeight))
|
||||
placeholderNode.updateLayout(size: CGSize(width: layout.size.width, height: placeholderHeight))
|
||||
}
|
||||
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
let sideInset: CGFloat = 12.0
|
||||
let spacing: CGFloat = 6.0
|
||||
var leftX = sideInset
|
||||
for itemNode in self.categoryItemNodes {
|
||||
let itemSize = itemNode.updateLayout()
|
||||
itemNode.frame = CGRect(origin: CGPoint(x: leftX, y: 0.0), size: itemSize)
|
||||
leftX += spacing + itemSize.width
|
||||
}
|
||||
leftX += sideInset
|
||||
self.categoryScrollNode.view.contentSize = CGSize(width: leftX, height: 60.0)
|
||||
self.categoryScrollNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 60.0))
|
||||
|
||||
if isFirstLayout {
|
||||
while !self.enqueuedTransactions.isEmpty {
|
||||
self.dequeueTransaction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.dimNode.layer.animatePosition(from: CGPoint(x: self.dimNode.position.x, y: self.dimNode.position.y - self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
|
||||
})
|
||||
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, completion: { _ in
|
||||
})
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.dimNode.layer.animatePosition(from: self.dimNode.position, to: CGPoint(x: self.dimNode.position.x, y: self.dimNode.position.y - self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false)
|
||||
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: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
func updateState(_ state: MessageReactionListState) {
|
||||
if self.currentState != state {
|
||||
self.currentState = state
|
||||
|
||||
self.updateItems()
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var currentEntries: [MessageReactionListEntry]?
|
||||
private func updateItems() {
|
||||
var entries: [MessageReactionListEntry] = []
|
||||
|
||||
var index = 0
|
||||
let states = self.currentState?.states ?? []
|
||||
for (category, categoryState) in states {
|
||||
if self.categoryItemNodes.count <= index {
|
||||
let itemNode = MessageReactionCategoryNode(theme: self.presentationData.theme, category: category, count: categoryState.count, action: { [weak self] in
|
||||
self?.setCategory(category)
|
||||
})
|
||||
self.categoryItemNodes.append(itemNode)
|
||||
self.categoryScrollNode.addSubnode(itemNode)
|
||||
if category == self.currentCategory {
|
||||
itemNode.isSelected = true
|
||||
} else {
|
||||
itemNode.isSelected = false
|
||||
}
|
||||
}
|
||||
|
||||
if category == self.currentCategory {
|
||||
for item in categoryState.items {
|
||||
entries.append(MessageReactionListEntry(index: entries.count, item: item))
|
||||
}
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
let transaction = preparedTransition(from: self.currentEntries ?? [], to: entries, context: self.context, presentationData: self.presentationData)
|
||||
let previousWasEmpty = self.currentEntries == nil || self.currentEntries?.count == 0
|
||||
let isEmpty = entries.isEmpty
|
||||
self.currentEntries = entries
|
||||
|
||||
self.enqueuedTransactions.append(transaction)
|
||||
self.dequeueTransaction()
|
||||
|
||||
if previousWasEmpty && !isEmpty {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.placeholderNodeIsAnimatingOut = true
|
||||
placeholderNode.allowsGroupOpacity = true
|
||||
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.placeholderNode?.removeFromSupernode()
|
||||
strongSelf.placeholderNode = nil
|
||||
})
|
||||
}
|
||||
self.listNode.forEachItemNode({ itemNode in
|
||||
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setCategory(_ category: MessageReactionListCategory) {
|
||||
if self.currentCategory != category {
|
||||
self.currentCategory = category
|
||||
|
||||
for itemNode in self.categoryItemNodes {
|
||||
itemNode.isSelected = category == itemNode.category
|
||||
}
|
||||
|
||||
//self.forceHeaderTransition = .animated(duration: 0.3, curve: .spring)
|
||||
if let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout: validLayout, transition: .animated(duration: 0.3, curve: .spring))
|
||||
}
|
||||
|
||||
self.updateItems()
|
||||
}
|
||||
}
|
||||
|
||||
private func dequeueTransaction() {
|
||||
guard let layout = self.validLayout, let transaction = self.enqueuedTransactions.first else {
|
||||
return
|
||||
}
|
||||
|
||||
self.enqueuedTransactions.remove(at: 0)
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
options.insert(.Synchronous)
|
||||
options.insert(.PreferSynchronousResourceLoading)
|
||||
options.insert(.PreferSynchronousDrawing)
|
||||
|
||||
var currentCategoryItemCount = 0
|
||||
if let currentState = self.currentState {
|
||||
for (category, categoryState) in currentState.states {
|
||||
if category == self.currentCategory {
|
||||
currentCategoryItemCount = categoryState.count
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var insets = UIEdgeInsets()
|
||||
insets.top = topInsetForLayout(layout: layout, itemCount: currentCategoryItemCount)
|
||||
insets.bottom = layout.intrinsicInsets.bottom
|
||||
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: self.listNode.bounds.size, insets: insets, duration: 0.3, curve: .Default(duration: 0.3))
|
||||
|
||||
self.listNode.transaction(deleteIndices: transaction.deletions, insertIndicesAndItems: transaction.insertions, updateIndicesAndItems: transaction.updates, options: options, updateSizeAndInsets: updateSizeAndInsets, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||
self?.isReady.set(.single(true))
|
||||
})
|
||||
}
|
||||
|
||||
@objc private func dimNodeTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
for itemNode in self.categoryItemNodes {
|
||||
if let result = itemNode.hitTest(self.view.convert(point, to: itemNode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
if let result = self.listNode.hitTest(self.view.convert(point, to: self.listNode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
if point.y >= self.contentHeaderContainerNode.frame.minY && point.y < self.bounds.height {
|
||||
return self.listNode.view
|
||||
}
|
||||
if point.y >= 0 && point.y < self.contentHeaderContainerNode.frame.minY {
|
||||
return self.dimNode.view
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
|
||||
final class MessageReactionListLoadingPlaceholder: ASDisplayNode {
|
||||
private let theme: PresentationTheme
|
||||
private let itemHeight: CGFloat
|
||||
private let itemImage: UIImage?
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let highlightNode: ASImageNode
|
||||
private var itemNodes: [ASImageNode] = []
|
||||
|
||||
init(theme: PresentationTheme, itemHeight: CGFloat) {
|
||||
self.theme = theme
|
||||
self.itemHeight = itemHeight
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.backgroundColor = UIColor(white: 0.92, alpha: 1.0)
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.backgroundColor = theme.list.itemPlainSeparatorColor
|
||||
|
||||
self.highlightNode = ASImageNode()
|
||||
self.highlightNode.displaysAsynchronously = false
|
||||
self.highlightNode.displayWithoutProcessing = true
|
||||
|
||||
let leftInset: CGFloat = 15.0
|
||||
let avatarSize: CGFloat = 40.0
|
||||
let avatarSpacing: CGFloat = 11.0
|
||||
let contentWidth: CGFloat = 4.0
|
||||
let contentHeight: CGFloat = 14.0
|
||||
let rightInset: CGFloat = 54.0
|
||||
self.itemImage = generateImage(CGSize(width: leftInset + avatarSize + avatarSpacing + contentWidth + rightInset, height: itemHeight), rotatedContext: { size, context in
|
||||
context.setFillColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.list.itemPlainSeparatorColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: leftInset, y: floor((itemHeight - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize)))
|
||||
let contentOrigin = leftInset + avatarSize + avatarSpacing
|
||||
context.fill(CGRect(origin: CGPoint(x: contentOrigin, y: floor((size.height - contentHeight) / 2.0)), size: CGSize(width: size.width - contentOrigin - rightInset, height: contentHeight)))
|
||||
})?.stretchableImage(withLeftCapWidth: Int(leftInset + avatarSize + avatarSpacing + 1), topCapHeight: 0)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.highlightNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize) {
|
||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
var verticalOffset: CGFloat = 0.0
|
||||
var index = 0
|
||||
while verticalOffset < size.height - 1.0 {
|
||||
if self.itemNodes.count >= index {
|
||||
let itemNode = ASImageNode()
|
||||
itemNode.image = self.itemImage
|
||||
self.itemNodes.append(itemNode)
|
||||
self.addSubnode(itemNode)
|
||||
}
|
||||
self.itemNodes[index].frame = CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: size.width, height: self.itemHeight))
|
||||
verticalOffset += self.itemHeight
|
||||
index += 1
|
||||
}
|
||||
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: size.width, height: UIScreenPixel))
|
||||
if index < self.itemNodes.count {
|
||||
for i in index ..< self.itemNodes.count {
|
||||
self.itemNodes[i].removeFromSupernode()
|
||||
}
|
||||
self.itemNodes.removeLast(self.itemNodes.count - index)
|
||||
}
|
||||
}
|
||||
}
|
@ -423,28 +423,31 @@ static NSData *encryptRSALegacy(id<EncryptionProvider> encryptionProvider, NSDat
|
||||
return encryptedData;
|
||||
}
|
||||
|
||||
static NSData *reversedBytes(NSData *data) {
|
||||
NSMutableData *result = [[NSMutableData alloc] initWithLength:data.length];
|
||||
for (NSUInteger i = 0; i < result.length; i++) {
|
||||
((uint8_t *)result.mutableBytes)[i] = ((uint8_t *)data.bytes)[result.length - i - 1];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static NSData *encryptRSAModernPadding(id<EncryptionProvider> encryptionProvider, NSData *pqInnerData, NSString *publicKey) {
|
||||
NSMutableData *dataWithPadding = [[NSMutableData alloc] init];
|
||||
[dataWithPadding appendData:pqInnerData];
|
||||
if (dataWithPadding.length > 176) {
|
||||
if (dataWithPadding.length > 192) {
|
||||
return nil;
|
||||
}
|
||||
if (dataWithPadding.length != 176) {
|
||||
if (dataWithPadding.length != 192) {
|
||||
int originalLength = (int)dataWithPadding.length;
|
||||
int numPaddingBytes = 176 - originalLength;
|
||||
[dataWithPadding setLength:176];
|
||||
int numPaddingBytes = 192 - originalLength;
|
||||
[dataWithPadding setLength:192];
|
||||
int randomResult = SecRandomCopyBytes(kSecRandomDefault, numPaddingBytes, ((uint8_t *)dataWithPadding.mutableBytes) + originalLength);
|
||||
if (randomResult != errSecSuccess) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableData *dataWithHash = [[NSMutableData alloc] init];
|
||||
[dataWithHash appendData:dataWithPadding];
|
||||
[dataWithHash appendData:MTSha256(dataWithPadding)];
|
||||
if (dataWithHash.length != 208) {
|
||||
return nil;
|
||||
}
|
||||
NSData *dataWithPaddingReversed = reversedBytes(dataWithPadding);
|
||||
|
||||
while (true) {
|
||||
int randomResult = 0;
|
||||
@ -454,33 +457,36 @@ static NSData *encryptRSAModernPadding(id<EncryptionProvider> encryptionProvider
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableData *tempIv = [[NSMutableData alloc] initWithLength:16];
|
||||
randomResult = SecRandomCopyBytes(kSecRandomDefault, tempIv.length, tempIv.mutableBytes);
|
||||
if (randomResult != errSecSuccess) {
|
||||
NSMutableData *tempKeyAndDataWithPadding = [[NSMutableData alloc] init];
|
||||
[tempKeyAndDataWithPadding appendData:tempKey];
|
||||
[tempKeyAndDataWithPadding appendData:dataWithPadding];
|
||||
|
||||
NSMutableData *dataWithHash = [[NSMutableData alloc] init];
|
||||
[dataWithHash appendData:dataWithPaddingReversed];
|
||||
[dataWithHash appendData:MTSha256(tempKeyAndDataWithPadding)];
|
||||
if (dataWithHash.length != 224) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *aesEncrypted = aesCbcEncrypt(dataWithHash, tempKey, tempIv);
|
||||
NSMutableData *zeroIv = [[NSMutableData alloc] initWithLength:32];
|
||||
memset(zeroIv.mutableBytes, 0, zeroIv.length);
|
||||
|
||||
NSData *aesEncrypted = MTAesEncrypt(dataWithHash, tempKey, zeroIv);
|
||||
if (aesEncrypted == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableData *tempIvPlusAesEncrypted = [[NSMutableData alloc] init];
|
||||
[tempIvPlusAesEncrypted appendData:tempIv];
|
||||
[tempIvPlusAesEncrypted appendData:aesEncrypted];
|
||||
NSData *shaTempIvPlusAesEncrypted = MTSha256(tempIvPlusAesEncrypted);
|
||||
NSData *shaAesEncrypted = MTSha256(aesEncrypted);
|
||||
|
||||
NSMutableData *tempKeyXor = [[NSMutableData alloc] initWithLength:tempKey.length];
|
||||
if (tempKeyXor.length != shaTempIvPlusAesEncrypted.length) {
|
||||
if (tempKeyXor.length != shaAesEncrypted.length) {
|
||||
return nil;
|
||||
}
|
||||
for (NSUInteger i = 0; i < tempKey.length; i++) {
|
||||
((uint8_t *)tempKeyXor.mutableBytes)[i] = ((uint8_t *)tempKey.bytes)[i] ^ ((uint8_t *)shaTempIvPlusAesEncrypted.bytes)[i];
|
||||
((uint8_t *)tempKeyXor.mutableBytes)[i] = ((uint8_t *)tempKey.bytes)[i] ^ ((uint8_t *)shaAesEncrypted.bytes)[i];
|
||||
}
|
||||
|
||||
NSMutableData *keyAesEncrypted = [[NSMutableData alloc] init];
|
||||
[keyAesEncrypted appendData:tempKeyXor];
|
||||
[keyAesEncrypted appendData:tempIv];
|
||||
[keyAesEncrypted appendData:aesEncrypted];
|
||||
if (keyAesEncrypted.length != 256) {
|
||||
return nil;
|
||||
|
@ -717,8 +717,16 @@
|
||||
request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + 2.0);
|
||||
}
|
||||
}
|
||||
else if (rpcError.errorCode == 400 && [rpcError.errorDescription isEqualToString:@"MSG_WAIT_TIMEOUT"])
|
||||
{
|
||||
else if (
|
||||
(
|
||||
rpcError.errorCode == 400 &&
|
||||
[rpcError.errorDescription isEqualToString:@"MSG_WAIT_TIMEOUT"]
|
||||
) ||
|
||||
(
|
||||
rpcError.errorCode == 500 &&
|
||||
[rpcError.errorDescription isEqualToString:@"MSG_WAIT_FAILED"]
|
||||
)
|
||||
) {
|
||||
if (request.errorContext == nil) {
|
||||
request.errorContext = [[MTRequestErrorContext alloc] init];
|
||||
}
|
||||
|
@ -558,7 +558,7 @@ public final class SecureIdAuthController: ViewController, StandalonePresentable
|
||||
return
|
||||
}
|
||||
switch update {
|
||||
case .noPassword:
|
||||
case .noPassword, .pendingPasswordReset:
|
||||
strongSelf.updateState(animated: false, { state in
|
||||
var state = state
|
||||
if let verificationState = state.verificationState, case .noChallenge = verificationState {
|
||||
|
@ -179,7 +179,7 @@ public func resetPasswordController(context: AccountContext, emailPattern: Strin
|
||||
state.checking = true
|
||||
return state
|
||||
}
|
||||
saveDisposable.set((context.engine.auth.recoverTwoStepVerificationPassword(code: state.code)
|
||||
saveDisposable.set((context.engine.auth.performPasswordRecovery(code: state.code, updatedPassword: .none)
|
||||
|> deliverOnMainQueue).start(error: { error in
|
||||
updateState { state in
|
||||
var state = state
|
||||
@ -190,7 +190,7 @@ public func resetPasswordController(context: AccountContext, emailPattern: Strin
|
||||
switch error {
|
||||
case .invalidCode:
|
||||
text = presentationData.strings.TwoStepAuth_RecoveryCodeInvalid
|
||||
case .codeExpired:
|
||||
case .expired:
|
||||
text = presentationData.strings.TwoStepAuth_RecoveryCodeExpired
|
||||
case .limitExceeded:
|
||||
text = presentationData.strings.TwoStepAuth_FloodError
|
||||
|
@ -138,6 +138,7 @@ public enum SetupTwoStepVerificationStateUpdate {
|
||||
case noPassword
|
||||
case awaitingEmailConfirmation(password: String, pattern: String, codeLength: Int32?)
|
||||
case passwordSet(password: String?, hasRecoveryEmail: Bool, hasSecureValues: Bool)
|
||||
case pendingPasswordReset
|
||||
}
|
||||
|
||||
final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
|
||||
|
@ -14,18 +14,27 @@ import AnimatedStickerNode
|
||||
|
||||
public enum TwoFactorDataInputMode {
|
||||
public struct Recovery {
|
||||
public var code: String
|
||||
public var syncContacts: Bool
|
||||
public var account: UnauthorizedAccount
|
||||
public enum Mode {
|
||||
case notAuthorized(syncContacts: Bool)
|
||||
case authorized
|
||||
}
|
||||
|
||||
public init(code: String, syncContacts: Bool, account: UnauthorizedAccount) {
|
||||
public var code: String
|
||||
public var mode: Mode
|
||||
|
||||
public init(code: String, mode: Mode) {
|
||||
self.code = code
|
||||
self.syncContacts = syncContacts
|
||||
self.account = account
|
||||
self.mode = mode
|
||||
}
|
||||
}
|
||||
|
||||
public enum PasswordRecoveryEmailMode {
|
||||
case notAuthorized(syncContacts: Bool)
|
||||
case authorized
|
||||
}
|
||||
|
||||
case password
|
||||
case passwordRecoveryEmail(emailPattern: String, mode: PasswordRecoveryEmailMode)
|
||||
case passwordRecovery(Recovery)
|
||||
case emailAddress(password: String, hint: String)
|
||||
case updateEmailAddress(password: String)
|
||||
@ -39,8 +48,11 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
private var presentationData: PresentationData
|
||||
private let mode: TwoFactorDataInputMode
|
||||
private let stateUpdated: (SetupTwoStepVerificationStateUpdate) -> Void
|
||||
private let actionDisposable = MetaDisposable()
|
||||
|
||||
public var passwordRecoveryFailed: (() -> Void)?
|
||||
|
||||
public init(sharedContext: SharedAccountContext, engine: SomeTelegramEngine, mode: TwoFactorDataInputMode, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate) -> Void) {
|
||||
public init(sharedContext: SharedAccountContext, engine: SomeTelegramEngine, mode: TwoFactorDataInputMode, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate) -> Void, presentation: ViewControllerNavigationPresentation = .modalInLargeLayout) {
|
||||
self.sharedContext = sharedContext
|
||||
self.engine = engine
|
||||
self.mode = mode
|
||||
@ -54,7 +66,7 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
self.navigationPresentation = .modalInLargeLayout
|
||||
self.navigationPresentation = presentation
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
self.navigationBar?.intrinsicCanTransitionInline = false
|
||||
|
||||
@ -64,6 +76,10 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.actionDisposable.dispose()
|
||||
}
|
||||
|
||||
@objc private func backPressed() {
|
||||
self.dismiss()
|
||||
@ -101,8 +117,57 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
}
|
||||
return true
|
||||
}
|
||||
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordHint(recovery: nil, password: values[0]), stateUpdated: strongSelf.stateUpdated))
|
||||
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordHint(recovery: nil, password: values[0]), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
case let .passwordRecoveryEmail(_, mode):
|
||||
guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
|
||||
return
|
||||
}
|
||||
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
|
||||
strongSelf.present(statusController, in: .window(.root))
|
||||
|
||||
strongSelf.actionDisposable.set((strongSelf.engine.auth.checkPasswordRecoveryCode(code: text)
|
||||
|> deliverOnMainQueue).start(error: { [weak statusController] error in
|
||||
statusController?.dismiss()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = strongSelf.presentationData.strings.LoginPassword_FloodError
|
||||
case .invalidCode:
|
||||
text = strongSelf.presentationData.strings.Login_InvalidCodeError
|
||||
case .expired:
|
||||
text = strongSelf.presentationData.strings.Login_CodeExpiredError
|
||||
case .generic:
|
||||
text = strongSelf.presentationData.strings.Login_UnknownError
|
||||
}
|
||||
|
||||
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}, completed: { [weak statusController] in
|
||||
statusController?.dismiss()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let mappedMode: TwoFactorDataInputMode.Recovery.Mode
|
||||
switch mode {
|
||||
case .authorized:
|
||||
mappedMode = .authorized
|
||||
case let .notAuthorized(syncContacts):
|
||||
mappedMode = .notAuthorized(syncContacts: syncContacts)
|
||||
}
|
||||
|
||||
let setupController = TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordRecovery(TwoFactorDataInputMode.Recovery(code: text, mode: mappedMode)), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation)
|
||||
|
||||
guard let navigationController = strongSelf.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
navigationController.replaceController(strongSelf, with: setupController, animated: true)
|
||||
}))
|
||||
case let .passwordRecovery(recovery):
|
||||
let values = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText
|
||||
if values.count != 2 {
|
||||
@ -120,17 +185,7 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
guard let navigationController = strongSelf.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
var controllers = navigationController.viewControllers.filter { controller in
|
||||
if controller is TwoFactorAuthSplashScreen {
|
||||
return false
|
||||
}
|
||||
if controller is TwoFactorDataInputScreen && controller !== strongSelf {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordHint(recovery: recovery, password: values[0]), stateUpdated: strongSelf.stateUpdated))
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
navigationController.replaceController(strongSelf, with: TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordHint(recovery: recovery, password: values[0]), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation), animated: true)
|
||||
case let .emailAddress(password, hint):
|
||||
guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
|
||||
return
|
||||
@ -163,7 +218,7 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
}
|
||||
return true
|
||||
}
|
||||
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailConfirmation(passwordAndHint: (password, hint), emailPattern: text, codeLength: pendingEmail.codeLength.flatMap(Int.init)), stateUpdated: strongSelf.stateUpdated))
|
||||
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailConfirmation(passwordAndHint: (password, hint), emailPattern: text, codeLength: pendingEmail.codeLength.flatMap(Int.init)), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
} else {
|
||||
guard let navigationController = strongSelf.navigationController as? NavigationController else {
|
||||
@ -337,7 +392,7 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
if let recovery = recovery {
|
||||
strongSelf.performRecovery(recovery: recovery, password: password, hint: value)
|
||||
} else {
|
||||
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: value), stateUpdated: strongSelf.stateUpdated))
|
||||
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: value), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
|
||||
}
|
||||
}
|
||||
}, skipAction: { [weak self] in
|
||||
@ -405,7 +460,7 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
if let recovery = recovery {
|
||||
strongSelf.performRecovery(recovery: recovery, password: password, hint: "")
|
||||
} else {
|
||||
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: ""), stateUpdated: strongSelf.stateUpdated))
|
||||
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: ""), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
|
||||
}
|
||||
case let .passwordRecovery(recovery):
|
||||
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertTitle, text: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertText, actions: [
|
||||
@ -439,10 +494,37 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
}
|
||||
return true
|
||||
}
|
||||
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: hint), stateUpdated: strongSelf.stateUpdated))
|
||||
controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: hint), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
} else {
|
||||
}
|
||||
case .passwordRecoveryEmail:
|
||||
switch strongSelf.engine {
|
||||
case let .authorized(engine):
|
||||
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailableResetTitle, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryEmailResetText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailableResetAction, action: {
|
||||
let _ = (engine.auth.requestTwoStepPasswordReset()
|
||||
|> deliverOnMainQueue).start(next: { result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch result {
|
||||
case .done, .waitingForReset:
|
||||
strongSelf.stateUpdated(.pendingPasswordReset)
|
||||
case .declined:
|
||||
break
|
||||
case let .error(reason):
|
||||
break
|
||||
}
|
||||
})
|
||||
})]), in: .window(.root))
|
||||
case .unauthorized:
|
||||
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryFailed, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.passwordRecoveryFailed?()
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -450,29 +532,57 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
|
||||
strongSelf.present(statusController, in: .window(.root))
|
||||
switch strongSelf.mode {
|
||||
case .passwordRecoveryEmail:
|
||||
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
|
||||
strongSelf.present(statusController, in: .window(.root))
|
||||
|
||||
let _ = (strongSelf.engine.auth.resendTwoStepRecoveryEmail()
|
||||
|> deliverOnMainQueue).start(error: { [weak statusController] error in
|
||||
statusController?.dismiss()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
let _ = (strongSelf.engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
|
||||
|> deliverOnMainQueue).start(error: { [weak statusController] error in
|
||||
statusController?.dismiss()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = strongSelf.presentationData.strings.TwoStepAuth_FloodError
|
||||
case .generic:
|
||||
text = strongSelf.presentationData.strings.Login_UnknownError
|
||||
}
|
||||
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}, completed: { [weak statusController] in
|
||||
statusController?.dismiss()
|
||||
})
|
||||
default:
|
||||
guard case let .authorized(engine) = strongSelf.engine else {
|
||||
return
|
||||
}
|
||||
|
||||
let text: String
|
||||
switch error {
|
||||
case .flood:
|
||||
text = strongSelf.presentationData.strings.TwoStepAuth_FloodError
|
||||
case .generic:
|
||||
text = strongSelf.presentationData.strings.Login_UnknownError
|
||||
}
|
||||
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}, completed: { [weak statusController] in
|
||||
statusController?.dismiss()
|
||||
})
|
||||
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
|
||||
strongSelf.present(statusController, in: .window(.root))
|
||||
|
||||
let _ = (engine.auth.resendTwoStepRecoveryEmail()
|
||||
|> deliverOnMainQueue).start(error: { [weak statusController] error in
|
||||
statusController?.dismiss()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let text: String
|
||||
switch error {
|
||||
case .flood:
|
||||
text = strongSelf.presentationData.strings.TwoStepAuth_FloodError
|
||||
case .generic:
|
||||
text = strongSelf.presentationData.strings.Login_UnknownError
|
||||
}
|
||||
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}, completed: { [weak statusController] in
|
||||
statusController?.dismiss()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
@ -485,42 +595,93 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
}
|
||||
|
||||
private func performRecovery(recovery: TwoFactorDataInputMode.Recovery, password: String, hint: String) {
|
||||
guard case let .unauthorized(engine) = self.engine else {
|
||||
return
|
||||
}
|
||||
let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: nil))
|
||||
self.present(statusController, in: .window(.root))
|
||||
|
||||
let _ = (engine.auth.performPasswordRecovery(accountManager: self.sharedContext.accountManager, code: recovery.code, syncContacts: recovery.syncContacts, updatedPassword: password.isEmpty ? .none : .password(password: password, hint: hint, email: nil))
|
||||
|> deliverOnMainQueue).start(error: { [weak self, weak statusController] error in
|
||||
statusController?.dismiss()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
switch self.engine {
|
||||
case let .unauthorized(engine):
|
||||
var syncContacts = false
|
||||
switch recovery.mode {
|
||||
case let .notAuthorized(syncContactsValue):
|
||||
syncContacts = syncContactsValue
|
||||
case .authorized:
|
||||
break
|
||||
}
|
||||
let _ = (engine.auth.performPasswordRecovery(code: recovery.code, updatedPassword: password.isEmpty ? .none : .password(password: password, hint: hint, email: nil))
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak statusController] recoveredAccountData in
|
||||
statusController?.dismiss()
|
||||
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = strongSelf.presentationData.strings.LoginPassword_FloodError
|
||||
case .invalidCode:
|
||||
text = strongSelf.presentationData.strings.Login_InvalidCodeError
|
||||
case .expired:
|
||||
text = strongSelf.presentationData.strings.Login_CodeExpiredError
|
||||
case .generic:
|
||||
text = strongSelf.presentationData.strings.Login_UnknownError
|
||||
}
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}, completed: { [weak self, weak statusController] in
|
||||
statusController?.dismiss()
|
||||
if password.isEmpty {
|
||||
strongSelf.stateUpdated(.noPassword)
|
||||
} else {
|
||||
strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: false))
|
||||
}
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
(strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .recoveryDone(recoveredAccountData: recoveredAccountData, syncContacts: syncContacts)), animated: true)
|
||||
}, error: { [weak self, weak statusController] error in
|
||||
statusController?.dismiss()
|
||||
|
||||
strongSelf.dismiss()
|
||||
})
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = strongSelf.presentationData.strings.LoginPassword_FloodError
|
||||
case .invalidCode:
|
||||
text = strongSelf.presentationData.strings.Login_InvalidCodeError
|
||||
case .expired:
|
||||
text = strongSelf.presentationData.strings.Login_CodeExpiredError
|
||||
case .generic:
|
||||
text = strongSelf.presentationData.strings.Login_UnknownError
|
||||
}
|
||||
|
||||
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}, completed: { [weak self, weak statusController] in
|
||||
statusController?.dismiss()
|
||||
})
|
||||
case let .authorized(engine):
|
||||
let _ = (engine.auth.performPasswordRecovery(code: recovery.code, updatedPassword: password.isEmpty ? .none : .password(password: password, hint: hint, email: nil))
|
||||
|> deliverOnMainQueue).start(error: { [weak self, weak statusController] error in
|
||||
statusController?.dismiss()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = strongSelf.presentationData.strings.LoginPassword_FloodError
|
||||
case .invalidCode:
|
||||
text = strongSelf.presentationData.strings.Login_InvalidCodeError
|
||||
case .expired:
|
||||
text = strongSelf.presentationData.strings.Login_CodeExpiredError
|
||||
case .generic:
|
||||
text = strongSelf.presentationData.strings.Login_UnknownError
|
||||
}
|
||||
|
||||
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}, completed: { [weak self, weak statusController] in
|
||||
statusController?.dismiss()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if password.isEmpty {
|
||||
strongSelf.stateUpdated(.noPassword)
|
||||
} else {
|
||||
strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: false))
|
||||
}
|
||||
strongSelf.dismiss()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -807,7 +968,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
||||
switch mode {
|
||||
case .password, .passwordRecovery, .emailAddress, .updateEmailAddress:
|
||||
self.monkeyNode = ManagedMonkeyAnimationNode()
|
||||
case .emailConfirmation:
|
||||
case .emailConfirmation, .passwordRecoveryEmail:
|
||||
if let path = getAppBundle().path(forResource: "TwoFactorSetupMail", ofType: "tgs") {
|
||||
let animatedStickerNode = AnimatedStickerNode()
|
||||
animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 272, height: 272, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||
@ -909,6 +1070,33 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
||||
toggleTextHidden?(node)
|
||||
}),
|
||||
]
|
||||
case let .passwordRecoveryEmail(emailPattern, _):
|
||||
title = presentationData.strings.TwoFactorSetup_EmailVerification_Title
|
||||
let (rawText, ranges) = presentationData.strings.TwoFactorSetup_EmailVerification_Text(emailPattern)
|
||||
|
||||
let string = NSMutableAttributedString()
|
||||
string.append(NSAttributedString(string: rawText, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor))
|
||||
for (_, range) in ranges {
|
||||
string.addAttribute(.font, value: Font.semibold(16.0), range: range)
|
||||
}
|
||||
|
||||
text = string
|
||||
|
||||
buttonText = presentationData.strings.TwoFactorSetup_EmailVerification_Action
|
||||
skipActionText = ""
|
||||
changeEmailActionText = presentationData.strings.TwoStepAuth_RecoveryEmailResetNoAccess
|
||||
resendCodeActionText = presentationData.strings.TwoFactorSetup_EmailVerification_ResendAction
|
||||
inputNodes = [
|
||||
TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .code, placeholder: presentationData.strings.TwoFactorSetup_EmailVerification_Placeholder, focusUpdated: { node, focused in
|
||||
focusUpdated?(node, focused)
|
||||
}, next: { node in
|
||||
next?(node)
|
||||
}, updated: { node in
|
||||
updated?(node)
|
||||
}, toggleTextHidden: { node in
|
||||
toggleTextHidden?(node)
|
||||
}),
|
||||
]
|
||||
case let .emailConfirmation(_, emailPattern, _):
|
||||
title = presentationData.strings.TwoFactorSetup_EmailVerification_Title
|
||||
let (rawText, ranges) = presentationData.strings.TwoFactorSetup_EmailVerification_Text(emailPattern)
|
||||
@ -985,7 +1173,6 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
||||
self.changeEmailActionTitleNode.attributedText = NSAttributedString(string: changeEmailActionText, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemAccentColor)
|
||||
self.changeEmailActionButtonNode = HighlightTrackingButtonNode()
|
||||
self.changeEmailActionButtonNode.isHidden = changeEmailActionText.isEmpty
|
||||
self.changeEmailActionButtonNode.isHidden = changeEmailActionText.isEmpty
|
||||
|
||||
self.resendCodeActionTitleNode = ImmediateTextNode()
|
||||
self.resendCodeActionTitleNode.isUserInteractionEnabled = false
|
||||
@ -1088,7 +1275,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
||||
return
|
||||
}
|
||||
switch strongSelf.mode {
|
||||
case .password:
|
||||
case .password, .passwordRecovery:
|
||||
if strongSelf.inputNodes[1].isFocused {
|
||||
let maxWidth = strongSelf.inputNodes[1].bounds.width
|
||||
|
||||
@ -1129,7 +1316,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
||||
break
|
||||
}
|
||||
}
|
||||
focusUpdated = { [weak self] node, _ in
|
||||
focusUpdated = { node, _ in
|
||||
DispatchQueue.main.async {
|
||||
updateAnimations()
|
||||
}
|
||||
@ -1156,6 +1343,18 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
||||
if let codeLength = codeLength, text.count == codeLength {
|
||||
action()
|
||||
}
|
||||
case .passwordRecoveryEmail:
|
||||
let text = strongSelf.inputNodes[0].text
|
||||
let hasText = !text.isEmpty
|
||||
strongSelf.buttonNode.isHidden = !hasText
|
||||
strongSelf.changeEmailActionTitleNode.isHidden = hasText
|
||||
strongSelf.changeEmailActionButtonNode.isHidden = hasText
|
||||
strongSelf.resendCodeActionTitleNode.isHidden = hasText
|
||||
strongSelf.resendCodeActionButtonNode.isHidden = hasText
|
||||
|
||||
if text.count == 6 {
|
||||
action()
|
||||
}
|
||||
case .passwordHint:
|
||||
let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty })
|
||||
strongSelf.buttonNode.isHidden = !hasText
|
||||
@ -1171,7 +1370,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
||||
return
|
||||
}
|
||||
switch strongSelf.mode {
|
||||
case .password:
|
||||
case .password, .passwordRecovery:
|
||||
textHidden = !textHidden
|
||||
for node in strongSelf.inputNodes {
|
||||
node.updateTextHidden(textHidden)
|
||||
@ -1320,7 +1519,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
||||
|
||||
let buttonWidth = contentAreaSize.width - buttonSideInset * 2.0
|
||||
|
||||
let maxButtonY = min(areaHeight - buttonSpacing, layout.size.height - buttonBottomInset) - buttonHeight
|
||||
let maxButtonY = min(areaHeight - buttonSpacing, layout.size.height - buttonBottomInset) - buttonHeight * 2.0
|
||||
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: floor((contentAreaSize.width - buttonWidth) / 2.0), y: max(contentHeight + buttonSpacing, maxButtonY)), size: CGSize(width: buttonWidth, height: buttonHeight))
|
||||
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
|
||||
@ -1338,7 +1537,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
|
||||
let changeEmailActionButtonFrame: CGRect
|
||||
let resendCodeActionFrame: CGRect
|
||||
let resendCodeActionButtonFrame: CGRect
|
||||
if changeEmailActionSize.width + resendCodeActionSize.width > layout.size.width - 24.0 {
|
||||
if changeEmailActionSize.width + resendCodeActionSize.width > layout.size.width - buttonFrame.minX * 2.0 {
|
||||
changeEmailActionButtonFrame = CGRect(origin: CGPoint(x: buttonFrame.minX, y: buttonFrame.minY), size: CGSize(width: buttonFrame.width, height: buttonFrame.height))
|
||||
changeEmailActionFrame = CGRect(origin: CGPoint(x: changeEmailActionButtonFrame.minX + floor((changeEmailActionButtonFrame.width - changeEmailActionSize.width) / 2.0), y: changeEmailActionButtonFrame.minY + floor((changeEmailActionButtonFrame.height - changeEmailActionSize.height) / 2.0)), size: changeEmailActionSize)
|
||||
resendCodeActionButtonFrame = CGRect(origin: CGPoint(x: buttonFrame.minX, y: buttonFrame.maxY), size: CGSize(width: buttonFrame.width, height: buttonFrame.height))
|
||||
|
@ -15,6 +15,7 @@ import TelegramCore
|
||||
public enum TwoFactorAuthSplashMode {
|
||||
case intro
|
||||
case done
|
||||
case recoveryDone(recoveredAccountData: RecoveredAccountData, syncContacts: Bool)
|
||||
}
|
||||
|
||||
public final class TwoFactorAuthSplashScreen: ViewController {
|
||||
@ -23,7 +24,7 @@ public final class TwoFactorAuthSplashScreen: ViewController {
|
||||
private var presentationData: PresentationData
|
||||
private var mode: TwoFactorAuthSplashMode
|
||||
|
||||
public init(sharedContext: SharedAccountContext, engine: SomeTelegramEngine, mode: TwoFactorAuthSplashMode) {
|
||||
public init(sharedContext: SharedAccountContext, engine: SomeTelegramEngine, mode: TwoFactorAuthSplashMode, presentation: ViewControllerNavigationPresentation = .modalInLargeLayout) {
|
||||
self.sharedContext = sharedContext
|
||||
self.engine = engine
|
||||
self.mode = mode
|
||||
@ -34,9 +35,10 @@ public final class TwoFactorAuthSplashScreen: ViewController {
|
||||
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor)
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))
|
||||
|
||||
self.navigationPresentation = presentation
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
self.navigationPresentation = .modalInLargeLayout
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
self.navigationBar?.intrinsicCanTransitionInline = false
|
||||
|
||||
@ -58,12 +60,22 @@ public final class TwoFactorAuthSplashScreen: ViewController {
|
||||
switch strongSelf.mode {
|
||||
case .intro:
|
||||
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .password, stateUpdated: { _ in
|
||||
}))
|
||||
}, presentation: strongSelf.navigationPresentation))
|
||||
case .done:
|
||||
guard let navigationController = strongSelf.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
navigationController.filterController(strongSelf, animated: true)
|
||||
case let .recoveryDone(recoveredAccountData, syncContacts):
|
||||
guard let navigationController = strongSelf.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
switch strongSelf.engine {
|
||||
case let .unauthorized(engine):
|
||||
let _ = loginWithRecoveredAccountData(accountManager: strongSelf.sharedContext.accountManager, account: engine.account, recoveredAccountData: recoveredAccountData, syncContacts: syncContacts).start()
|
||||
case .authorized:
|
||||
navigationController.filterController(strongSelf, animated: true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -124,6 +136,16 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
|
||||
text = NSAttributedString(string: self.presentationData.strings.TwoFactorSetup_Done_Text, font: textFont, textColor: textColor)
|
||||
buttonText = self.presentationData.strings.TwoFactorSetup_Done_Action
|
||||
|
||||
if let path = getAppBundle().path(forResource: "TwoFactorSetupDone", ofType: "tgs") {
|
||||
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, mode: .direct(cachePathPrefix: nil))
|
||||
self.animationSize = CGSize(width: 124.0, height: 124.0)
|
||||
self.animationNode.visibility = true
|
||||
}
|
||||
case .recoveryDone:
|
||||
title = self.presentationData.strings.TwoFactorSetup_ResetDone_Title
|
||||
text = NSAttributedString(string: self.presentationData.strings.TwoFactorSetup_ResetDone_Text, font: textFont, textColor: textColor)
|
||||
buttonText = self.presentationData.strings.TwoFactorSetup_ResetDone_Action
|
||||
|
||||
if let path = getAppBundle().path(forResource: "TwoFactorSetupDone", ofType: "tgs") {
|
||||
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, mode: .direct(cachePathPrefix: nil))
|
||||
self.animationSize = CGSize(width: 124.0, height: 124.0)
|
||||
|
@ -36,7 +36,7 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: PeerId) -> Si
|
||||
if let firstEntry = entries.first {
|
||||
return context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> mapToSignal { peer -> Signal<(Bool, [AvatarGalleryEntry])?, NoError>in
|
||||
return fetchedAvatarGalleryEntries(account: context.account, peer: peer, firstEntry: firstEntry)
|
||||
return fetchedAvatarGalleryEntries(engine: context.engine, account: context.account, peer: peer, firstEntry: firstEntry)
|
||||
|> map(Optional.init)
|
||||
}
|
||||
} else {
|
||||
@ -210,7 +210,7 @@ public func initialAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchedAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<[AvatarGalleryEntry], NoError> {
|
||||
public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: Peer) -> Signal<[AvatarGalleryEntry], NoError> {
|
||||
return initialAvatarGalleryEntries(account: account, peer: peer)
|
||||
|> map { entries -> [AvatarGalleryEntry] in
|
||||
return entries ?? []
|
||||
@ -218,7 +218,7 @@ public func fetchedAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<
|
||||
|> mapToSignal { initialEntries in
|
||||
return .single(initialEntries)
|
||||
|> then(
|
||||
requestPeerPhotos(postbox: account.postbox, network: account.network, peerId: peer.id)
|
||||
engine.peers.requestPeerPhotos(peerId: peer.id)
|
||||
|> map { photos -> [AvatarGalleryEntry] in
|
||||
var result: [AvatarGalleryEntry] = []
|
||||
if photos.isEmpty {
|
||||
@ -269,11 +269,11 @@ public func fetchedAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchedAvatarGalleryEntries(account: Account, peer: Peer, firstEntry: AvatarGalleryEntry) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> {
|
||||
public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: Peer, firstEntry: AvatarGalleryEntry) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> {
|
||||
let initialEntries = [firstEntry]
|
||||
return Signal<(Bool, [AvatarGalleryEntry]), NoError>.single((false, initialEntries))
|
||||
|> then(
|
||||
requestPeerPhotos(postbox: account.postbox, network: account.network, peerId: peer.id)
|
||||
engine.peers.requestPeerPhotos(peerId: peer.id)
|
||||
|> map { photos -> (Bool, [AvatarGalleryEntry]) in
|
||||
var result: [AvatarGalleryEntry] = []
|
||||
let initialEntries = [firstEntry]
|
||||
@ -403,7 +403,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
|
||||
if let remoteEntries = remoteEntries {
|
||||
remoteEntriesSignal = remoteEntries.get()
|
||||
} else {
|
||||
remoteEntriesSignal = fetchedAvatarGalleryEntries(account: context.account, peer: peer)
|
||||
remoteEntriesSignal = fetchedAvatarGalleryEntries(engine: context.engine, account: context.account, peer: peer)
|
||||
}
|
||||
|
||||
let initialSignal = initialAvatarGalleryEntries(account: context.account, peer: peer)
|
||||
|
@ -889,7 +889,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
|
||||
return
|
||||
}
|
||||
|
||||
transferOwnershipDisposable.set((checkOwnershipTranfserAvailability(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, memberId: adminId) |> deliverOnMainQueue).start(error: { error in
|
||||
transferOwnershipDisposable.set((context.engine.peers.checkOwnershipTranfserAvailability(memberId: adminId) |> deliverOnMainQueue).start(error: { error in
|
||||
let controller = channelOwnershipTransferController(context: context, peer: peer, member: member, initialError: error, present: { c, a in
|
||||
presentControllerImpl?(c, a)
|
||||
}, completion: { upgradedPeerId in
|
||||
|
@ -665,7 +665,7 @@ public func channelAdminsController(context: AccountContext, peerId initialPeerI
|
||||
|> deliverOnMainQueue).start(next: { peerId in
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
var didReportLoadCompleted = false
|
||||
let membersAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?) = context.peerChannelMemberCategoriesContextsManager.admins(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) { membersState in
|
||||
let membersAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) { membersState in
|
||||
if case .loading = membersState.loadingState, membersState.list.isEmpty {
|
||||
adminsPromise.set(.single(nil))
|
||||
} else {
|
||||
|
@ -506,7 +506,7 @@ public func channelBannedMemberController(context: AccountContext, peerId: PeerI
|
||||
state.updating = true
|
||||
return state
|
||||
}
|
||||
updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: nil)
|
||||
updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: peerId, memberId: memberId, bannedRights: nil)
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
|
||||
}, completed: {
|
||||
@ -667,7 +667,7 @@ public func channelBannedMemberController(context: AccountContext, peerId: PeerI
|
||||
guard let upgradedPeerId = upgradedPeerId else {
|
||||
return .single(nil)
|
||||
}
|
||||
return context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: upgradedPeerId, memberId: memberId, bannedRights: cleanResolvedRights)
|
||||
return context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: upgradedPeerId, memberId: memberId, bannedRights: cleanResolvedRights)
|
||||
|> mapToSignal { _ -> Signal<PeerId?, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
@ -700,7 +700,7 @@ public func channelBannedMemberController(context: AccountContext, peerId: PeerI
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: cleanResolvedRights)
|
||||
updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: peerId, memberId: memberId, bannedRights: cleanResolvedRights)
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
|
||||
}, completed: {
|
||||
|
@ -324,7 +324,7 @@ public func channelBlacklistController(context: AccountContext, peerId: PeerId)
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let progress = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
|
||||
presentControllerImpl?(progress, nil)
|
||||
removePeerDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: peer.id, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|
||||
removePeerDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: peerId, memberId: peer.id, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|
||||
|> deliverOnMainQueue).start(error: { [weak progress] _ in
|
||||
progress?.dismiss()
|
||||
dismissController?()
|
||||
@ -343,7 +343,7 @@ public func channelBlacklistController(context: AccountContext, peerId: PeerId)
|
||||
return $0.withUpdatedRemovingPeerId(memberId)
|
||||
}
|
||||
|
||||
removePeerDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: nil) |> deliverOnMainQueue).start(error: { _ in
|
||||
removePeerDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: peerId, memberId: memberId, bannedRights: nil) |> deliverOnMainQueue).start(error: { _ in
|
||||
updateState {
|
||||
return $0.withUpdatedRemovingPeerId(nil)
|
||||
}
|
||||
@ -388,7 +388,7 @@ public func channelBlacklistController(context: AccountContext, peerId: PeerId)
|
||||
updateState {
|
||||
return $0.withUpdatedRemovingPeerId(memberId)
|
||||
}
|
||||
let signal = context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: nil)
|
||||
let signal = context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: peerId, memberId: memberId, bannedRights: nil)
|
||||
|> ignoreValues
|
||||
|> then(
|
||||
context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: peerId, memberId: memberId)
|
||||
@ -418,7 +418,7 @@ public func channelBlacklistController(context: AccountContext, peerId: PeerId)
|
||||
return $0.withUpdatedRemovingPeerId(memberId)
|
||||
}
|
||||
|
||||
removePeerDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: nil) |> deliverOnMainQueue).start(error: { _ in
|
||||
removePeerDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: peerId, memberId: memberId, bannedRights: nil) |> deliverOnMainQueue).start(error: { _ in
|
||||
updateState {
|
||||
return $0.withUpdatedRemovingPeerId(nil)
|
||||
}
|
||||
@ -437,7 +437,7 @@ public func channelBlacklistController(context: AccountContext, peerId: PeerId)
|
||||
})
|
||||
})
|
||||
|
||||
let (listDisposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.banned(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { listState in
|
||||
let (listDisposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.banned(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { listState in
|
||||
if case .loading(true) = listState.loadingState, listState.list.isEmpty {
|
||||
blacklistPromise.set(.single(nil))
|
||||
} else {
|
||||
|
@ -230,7 +230,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
|
||||
let groupPeers = Promise<[Peer]?>()
|
||||
groupPeers.set(.single(nil)
|
||||
|> then(
|
||||
availableGroupsForChannelDiscussion(postbox: context.account.postbox, network: context.account.network)
|
||||
context.engine.peers.availableGroupsForChannelDiscussion()
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<[Peer]?, NoError> in
|
||||
return .single(nil)
|
||||
@ -260,7 +260,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
pushControllerImpl?(context.sharedContext.makeCreateGroupController(context: context, peerIds: [], initialTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + " Chat", mode: .supergroup, completion: { groupId, dismiss in
|
||||
var applySignal = updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: peerId, groupId: groupId)
|
||||
var applySignal = context.engine.peers.updateGroupDiscussionForChannel(channelId: peerId, groupId: groupId)
|
||||
var cancelImpl: (() -> Void)?
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -358,13 +358,13 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
|
||||
})
|
||||
}
|
||||
|
||||
return updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: peerId, groupId: resultPeerId)
|
||||
return context.engine.peers.updateGroupDiscussionForChannel(channelId: peerId, groupId: resultPeerId)
|
||||
}
|
||||
|> castError(ChannelDiscussionGroupError.self)
|
||||
|> switchToLatest
|
||||
}
|
||||
} else {
|
||||
applySignal = updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: peerId, groupId: groupId)
|
||||
applySignal = context.engine.peers.updateGroupDiscussionForChannel(channelId: peerId, groupId: groupId)
|
||||
}
|
||||
var cancelImpl: (() -> Void)?
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
@ -413,7 +413,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
|
||||
case .groupHistoryIsCurrentlyPrivate:
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Channel_DiscussionGroup_MakeHistoryPublic, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_DiscussionGroup_MakeHistoryPublicProceed, action: {
|
||||
var applySignal: Signal<Bool, ChannelDiscussionGroupError> = updateChannelHistoryAvailabilitySettingsInteractively(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, peerId: updatedPeerId ?? groupId, historyAvailableForNewMembers: true)
|
||||
var applySignal: Signal<Bool, ChannelDiscussionGroupError> = context.engine.peers.updateChannelHistoryAvailabilitySettingsInteractively(peerId: updatedPeerId ?? groupId, historyAvailableForNewMembers: true)
|
||||
|> mapError { _ -> ChannelDiscussionGroupError in
|
||||
return .generic
|
||||
}
|
||||
@ -421,7 +421,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
|
||||
return .complete()
|
||||
}
|
||||
|> then(
|
||||
updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: peerId, groupId: updatedPeerId ?? groupId)
|
||||
context.engine.peers.updateGroupDiscussionForChannel(channelId: peerId, groupId: updatedPeerId ?? groupId)
|
||||
)
|
||||
var cancelImpl: (() -> Void)?
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
@ -502,7 +502,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
|
||||
return
|
||||
}
|
||||
|
||||
var applySignal = updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: applyPeerId, groupId: nil)
|
||||
var applySignal = context.engine.peers.updateGroupDiscussionForChannel(channelId: applyPeerId, groupId: nil)
|
||||
var cancelImpl: (() -> Void)?
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
@ -836,11 +836,11 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
|
||||
}
|
||||
let controller = notificationMuteSettingsController(presentationData: presentationData, notificationSettings: globalSettings.effective.groupChats, soundSettings: soundSettings, openSoundSettings: {
|
||||
let controller = notificationSoundSelectionController(context: context, isModal: true, currentSound: peerSettings.messageSound, defaultSound: globalSettings.effective.groupChats.sound, completion: { sound in
|
||||
let _ = updatePeerNotificationSoundInteractive(account: context.account, peerId: peerId, sound: sound).start()
|
||||
let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, sound: sound).start()
|
||||
})
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}, updateSettings: { value in
|
||||
changeMuteSettingsDisposable.set(updatePeerMuteSetting(account: context.account, peerId: peerId, muteInterval: value).start())
|
||||
changeMuteSettingsDisposable.set(context.engine.peers.updatePeerMuteSetting(peerId: peerId, muteInterval: value).start())
|
||||
})
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
})
|
||||
@ -918,7 +918,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
|
||||
}, aboutLinkAction: { action, itemLink in
|
||||
aboutLinkActionImpl?(action, itemLink)
|
||||
}, toggleSignatures: { enabled in
|
||||
actionsDisposable.add(toggleShouldChannelMessagesSignatures(account: context.account, peerId: peerId, enabled: enabled).start())
|
||||
actionsDisposable.add(context.engine.peers.toggleShouldChannelMessagesSignatures(peerId: peerId, enabled: enabled).start())
|
||||
})
|
||||
|
||||
var wasEditing: Bool?
|
||||
|
@ -443,7 +443,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
|
||||
return $0.withUpdatedRemovingPeerId(memberId)
|
||||
}
|
||||
|
||||
removePeerDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|
||||
removePeerDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
updateState {
|
||||
return $0.withUpdatedRemovingPeerId(nil)
|
||||
@ -462,7 +462,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
|
||||
|
||||
let peerView = context.account.viewTracker.peerView(peerId)
|
||||
|
||||
let (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in
|
||||
let (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in
|
||||
peersPromise.set(.single(state.list))
|
||||
})
|
||||
actionsDisposable.add(disposable)
|
||||
|
@ -231,13 +231,13 @@ private func categorySignal(context: AccountContext, peerId: PeerId, category: G
|
||||
}
|
||||
switch category {
|
||||
case .admins:
|
||||
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.admins(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState)
|
||||
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState)
|
||||
case .contacts:
|
||||
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.contacts(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState)
|
||||
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState)
|
||||
case .bots:
|
||||
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.bots(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState)
|
||||
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.bots(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState)
|
||||
case .members:
|
||||
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState)
|
||||
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState)
|
||||
}
|
||||
|
||||
let (disposable, _) = disposableAndLoadMoreControl
|
||||
@ -452,7 +452,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
}
|
||||
}
|
||||
|
||||
return context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|
||||
return context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
updateState { state in
|
||||
@ -479,7 +479,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
}
|
||||
}
|
||||
|
||||
return removePeerMember(account: context.account, peerId: peerId, memberId: memberId)
|
||||
return context.engine.peers.removePeerMember(peerId: peerId, memberId: memberId)
|
||||
|> deliverOnMainQueue
|
||||
|> afterDisposed {
|
||||
updateState { state in
|
||||
@ -608,7 +608,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
switch mode {
|
||||
case .searchMembers, .banAndPromoteActions:
|
||||
foundGroupMembers = Signal { subscriber in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in
|
||||
if case .ready = state.loadingState {
|
||||
subscriber.putNext(state.list)
|
||||
}
|
||||
@ -619,11 +619,11 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
foundMembers = .single([])
|
||||
case .inviteActions:
|
||||
foundGroupMembers = .single([])
|
||||
foundMembers = channelMembers(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, category: .recent(.search(query)))
|
||||
foundMembers = context.engine.peers.channelMembers(peerId: peerId, category: .recent(.search(query)))
|
||||
|> map { $0 ?? [] }
|
||||
case .searchAdmins:
|
||||
foundGroupMembers = Signal { subscriber in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in
|
||||
if case .ready = state.loadingState {
|
||||
subscriber.putNext(state.list)
|
||||
}
|
||||
@ -633,7 +633,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
foundMembers = .single([])
|
||||
case .searchBanned:
|
||||
foundGroupMembers = Signal { subscriber in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.restricted(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.restricted(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in
|
||||
if case .ready = state.loadingState {
|
||||
subscriber.putNext(state.list)
|
||||
subscriber.putCompletion()
|
||||
@ -643,7 +643,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
foundMembers = Signal { subscriber in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in
|
||||
if case .ready = state.loadingState {
|
||||
subscriber.putNext(state.list.filter({ participant in
|
||||
return participant.peer.id != context.account.peerId
|
||||
@ -655,7 +655,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
|> runOn(Queue.mainQueue())
|
||||
case .searchKicked:
|
||||
foundGroupMembers = Signal { subscriber in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.banned(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.banned(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in
|
||||
if case .ready = state.loadingState {
|
||||
subscriber.putNext(state.list)
|
||||
subscriber.putCompletion()
|
||||
|
@ -419,7 +419,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
} else {
|
||||
let membersState = Promise<ChannelMemberListState>()
|
||||
|
||||
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in
|
||||
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in
|
||||
membersState.set(.single(state))
|
||||
})
|
||||
|
||||
|
@ -455,7 +455,7 @@ private func commitChannelOwnershipTransferController(context: AccountContext, p
|
||||
|
||||
let signal: Signal<PeerId?, ChannelOwnershipTransferError>
|
||||
if let peer = peer as? TelegramChannel {
|
||||
signal = context.peerChannelMemberCategoriesContextsManager.transferOwnership(account: context.account, peerId: peer.id, memberId: member.id, password: contentNode.password) |> mapToSignal { _ in
|
||||
signal = context.peerChannelMemberCategoriesContextsManager.transferOwnership(engine: context.engine, peerId: peer.id, memberId: member.id, password: contentNode.password) |> mapToSignal { _ in
|
||||
return .complete()
|
||||
}
|
||||
|> then(.single(nil))
|
||||
@ -475,7 +475,7 @@ private func commitChannelOwnershipTransferController(context: AccountContext, p
|
||||
guard let upgradedPeerId = upgradedPeerId else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return context.peerChannelMemberCategoriesContextsManager.transferOwnership(account: context.account, peerId: upgradedPeerId, memberId: member.id, password: contentNode.password) |> mapToSignal { _ in
|
||||
return context.peerChannelMemberCategoriesContextsManager.transferOwnership(engine: context.engine, peerId: upgradedPeerId, memberId: member.id, password: contentNode.password) |> mapToSignal { _ in
|
||||
return .complete()
|
||||
}
|
||||
|> then(.single(upgradedPeerId))
|
||||
|
@ -536,7 +536,7 @@ public func channelPermissionsController(context: AccountContext, peerId origina
|
||||
peersPromise.set(.single((peerId, nil)))
|
||||
} else {
|
||||
var loadCompletedCalled = false
|
||||
let disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.restricted(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in
|
||||
let disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.restricted(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in
|
||||
if case .loading(true) = state.loadingState, !updated {
|
||||
peersPromise.set(.single((peerId, nil)))
|
||||
} else {
|
||||
@ -594,7 +594,7 @@ public func channelPermissionsController(context: AccountContext, peerId origina
|
||||
}
|
||||
let state = stateValue.with { $0 }
|
||||
if let modifiedRightsFlags = state.modifiedRightsFlags {
|
||||
updateDefaultRightsDisposable.set((updateDefaultChannelMemberBannedRights(account: context.account, peerId: view.peerId, rights: TelegramChatBannedRights(flags: completeRights(modifiedRightsFlags), untilDate: Int32.max))
|
||||
updateDefaultRightsDisposable.set((context.engine.peers.updateDefaultChannelMemberBannedRights(peerId: view.peerId, rights: TelegramChatBannedRights(flags: completeRights(modifiedRightsFlags), untilDate: Int32.max))
|
||||
|> deliverOnMainQueue).start())
|
||||
}
|
||||
} else if let group = view.peers[view.peerId] as? TelegramGroup, let _ = view.cachedData as? CachedGroupData {
|
||||
@ -624,7 +624,7 @@ public func channelPermissionsController(context: AccountContext, peerId origina
|
||||
}
|
||||
let state = stateValue.with { $0 }
|
||||
if let modifiedRightsFlags = state.modifiedRightsFlags {
|
||||
updateDefaultRightsDisposable.set((updateDefaultChannelMemberBannedRights(account: context.account, peerId: view.peerId, rights: TelegramChatBannedRights(flags: completeRights(modifiedRightsFlags), untilDate: Int32.max))
|
||||
updateDefaultRightsDisposable.set((context.engine.peers.updateDefaultChannelMemberBannedRights(peerId: view.peerId, rights: TelegramChatBannedRights(flags: completeRights(modifiedRightsFlags), untilDate: Int32.max))
|
||||
|> deliverOnMainQueue).start())
|
||||
}
|
||||
}
|
||||
@ -679,7 +679,7 @@ public func channelPermissionsController(context: AccountContext, peerId origina
|
||||
return state
|
||||
}
|
||||
|
||||
removePeerDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: nil)
|
||||
removePeerDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: peerId, memberId: memberId, bannedRights: nil)
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
updateState { state in
|
||||
var state = state
|
||||
|
@ -1104,7 +1104,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
|
||||
switch subject {
|
||||
case let .create(peer, _, share, shareViaException, _):
|
||||
if share, filteredPhoneNumbers.count <= 1, let peer = peer {
|
||||
addContactDisposable.set((addContactInteractively(account: context.account, peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", addToPrivacyExceptions: shareViaException && addToPrivacyExceptions)
|
||||
addContactDisposable.set((context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", addToPrivacyExceptions: shareViaException && addToPrivacyExceptions)
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}, completed: {
|
||||
@ -1138,7 +1138,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
|
||||
switch subject {
|
||||
case let .create(peer, _, share, shareViaException, _):
|
||||
if share, let peer = peer {
|
||||
return addContactInteractively(account: context.account, peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", addToPrivacyExceptions: shareViaException && addToPrivacyExceptions)
|
||||
return context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", addToPrivacyExceptions: shareViaException && addToPrivacyExceptions)
|
||||
|> mapToSignal { _ -> Signal<(DeviceContactStableId, DeviceContactExtendedData, Peer?)?, AddContactError> in
|
||||
return .complete()
|
||||
}
|
||||
@ -1153,7 +1153,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
|
||||
break
|
||||
}
|
||||
|
||||
return importContact(account: context.account, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers[0].value)
|
||||
return context.engine.contacts.importContact(firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers[0].value)
|
||||
|> castError(AddContactError.self)
|
||||
|> mapToSignal { peerId -> Signal<(DeviceContactStableId, DeviceContactExtendedData, Peer?)?, AddContactError> in
|
||||
if let peerId = peerId {
|
||||
|
@ -163,7 +163,7 @@ public func groupPreHistorySetupController(context: AccountContext, peerId: Peer
|
||||
if peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
let signal = context.engine.peers.convertGroupToSupergroup(peerId: peerId)
|
||||
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, ConvertGroupToSupergroupError> in
|
||||
return updateChannelHistoryAvailabilitySettingsInteractively(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, peerId: upgradedPeerId, historyAvailableForNewMembers: value)
|
||||
return context.engine.peers.updateChannelHistoryAvailabilitySettingsInteractively(peerId: upgradedPeerId, historyAvailableForNewMembers: value)
|
||||
|> `catch` { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
@ -190,7 +190,7 @@ public func groupPreHistorySetupController(context: AccountContext, peerId: Peer
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
applyDisposable.set((updateChannelHistoryAvailabilitySettingsInteractively(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, peerId: peerId, historyAvailableForNewMembers: value)
|
||||
applyDisposable.set((context.engine.peers.updateChannelHistoryAvailabilitySettingsInteractively(peerId: peerId, historyAvailableForNewMembers: value)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
dismissImpl?()
|
||||
}))
|
||||
|
@ -448,7 +448,7 @@ public func groupStickerPackSetupController(context: AccountContext, peerId: Pee
|
||||
state.isSaving = true
|
||||
return state
|
||||
}
|
||||
saveDisposable.set((updateGroupSpecificStickerset(postbox: context.account.postbox, network: context.account.network, peerId: peerId, info: info)
|
||||
saveDisposable.set((context.engine.peers.updateGroupSpecificStickerset(peerId: peerId, info: info)
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
updateState { state in
|
||||
var state = state
|
||||
|
@ -18,7 +18,7 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
|
||||
|> mapToSignal { cachedData -> Signal<([Peer], Bool), NoError> in
|
||||
if case .peer = chatLocation, let cachedData = cachedData, let memberCount = cachedData.participantsSummary.memberCount, memberCount <= 64 {
|
||||
return Signal { subscriber in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, requestUpdate: false, updated: { state in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, requestUpdate: false, updated: { state in
|
||||
if case .ready = state.loadingState {
|
||||
let normalizedQuery = query.lowercased()
|
||||
subscriber.putNext((state.list.compactMap { participant -> Peer? in
|
||||
@ -54,7 +54,7 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
|
||||
return Signal { subscriber in
|
||||
switch chatLocation {
|
||||
case let .peer(peerId):
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query.isEmpty ? nil : query, updated: { state in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query.isEmpty ? nil : query, updated: { state in
|
||||
if case .ready = state.loadingState {
|
||||
subscriber.putNext((state.list.compactMap { participant in
|
||||
if participant.peer.isDeleted {
|
||||
@ -69,7 +69,7 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
|
||||
disposable.dispose()
|
||||
}
|
||||
case let .replyThread(replyThreadMessage):
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.mentions(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, threadMessageId: replyThreadMessage.messageId, searchQuery: query.isEmpty ? nil : query, updated: { state in
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.mentions(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, threadMessageId: replyThreadMessage.messageId, searchQuery: query.isEmpty ? nil : query, updated: { state in
|
||||
if case .ready = state.loadingState {
|
||||
subscriber.putNext((state.list.compactMap { participant in
|
||||
if participant.peer.isDeleted {
|
||||
@ -117,6 +117,6 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return searchGroupMembers(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, query: query)
|
||||
return context.engine.peers.searchGroupMembers(peerId: peerId, query: query)
|
||||
}
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ func changePhoneNumberCodeController(context: AccountContext, phoneNumber: Strin
|
||||
|> take(1)
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return Signal { subscriber in
|
||||
return requestNextChangeAccountPhoneNumberVerification(account: context.account, phoneNumber: phoneNumber, phoneCodeHash: data.hash).start(next: { next in
|
||||
return context.engine.accountData.requestNextChangeAccountPhoneNumberVerification(phoneNumber: phoneNumber, phoneCodeHash: data.hash).start(next: { next in
|
||||
currentDataPromise?.set(.single(next))
|
||||
}, error: { error in
|
||||
|
||||
@ -254,7 +254,7 @@ func changePhoneNumberCodeController(context: AccountContext, phoneNumber: Strin
|
||||
}
|
||||
}
|
||||
if let code = code {
|
||||
changePhoneDisposable.set((requestChangeAccountPhoneNumber(account: context.account, phoneNumber: phoneNumber, phoneCodeHash: codeData.hash, phoneCode: code) |> deliverOnMainQueue).start(error: { error in
|
||||
changePhoneDisposable.set((context.engine.accountData.requestChangeAccountPhoneNumber(phoneNumber: phoneNumber, phoneCodeHash: codeData.hash, phoneCode: code) |> deliverOnMainQueue).start(error: { error in
|
||||
updateState {
|
||||
return $0.withUpdatedChecking(false)
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ final class ChangePhoneNumberController: ViewController, MFMailComposeViewContro
|
||||
}
|
||||
if !number.isEmpty {
|
||||
self.inProgress = true
|
||||
self.requestDisposable.set((requestChangeAccountPhoneNumberVerification(account: self.context.account, phoneNumber: self.controllerNode.currentNumber) |> deliverOnMainQueue).start(next: { [weak self] next in
|
||||
self.requestDisposable.set((self.context.engine.accountData.requestChangeAccountPhoneNumberVerification(phoneNumber: self.controllerNode.currentNumber) |> deliverOnMainQueue).start(next: { [weak self] next in
|
||||
if let strongSelf = self {
|
||||
strongSelf.inProgress = false
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(changePhoneNumberCodeController(context: strongSelf.context, phoneNumber: strongSelf.controllerNode.currentNumber, codeData: next))
|
||||
|
@ -780,16 +780,16 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode {
|
||||
let presentationData = context.sharedContext.currentPresentationData.modify {$0}
|
||||
|
||||
let updatePeerSound: (PeerId, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
|
||||
return updatePeerNotificationSoundInteractive(account: context.account, peerId: peerId, sound: sound) |> deliverOnMainQueue
|
||||
return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue
|
||||
}
|
||||
|
||||
let updatePeerNotificationInterval:(PeerId, Int32?) -> Signal<Void, NoError> = { peerId, muteInterval in
|
||||
return updatePeerMuteSetting(account: context.account, peerId: peerId, muteInterval: muteInterval) |> deliverOnMainQueue
|
||||
let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal<Void, NoError> = { peerId, muteInterval in
|
||||
return context.engine.peers.updatePeerMuteSetting(peerId: peerId, muteInterval: muteInterval) |> deliverOnMainQueue
|
||||
}
|
||||
|
||||
let updatePeerDisplayPreviews:(PeerId, PeerNotificationDisplayPreviews) -> Signal<Void, NoError> = {
|
||||
peerId, displayPreviews in
|
||||
return updatePeerDisplayPreviewsSetting(account: context.account, peerId: peerId, displayPreviews: displayPreviews) |> deliverOnMainQueue
|
||||
return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, displayPreviews: displayPreviews) |> deliverOnMainQueue
|
||||
}
|
||||
|
||||
self.backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||
@ -842,13 +842,11 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode {
|
||||
updateNotificationsView({})
|
||||
})
|
||||
}, removePeerFromExceptions: {
|
||||
let _ = (context.account.postbox.transaction { transaction -> Peer? in
|
||||
updatePeerMuteSetting(transaction: transaction, peerId: peerId, muteInterval: nil)
|
||||
updatePeerDisplayPreviewsSetting(transaction: transaction, peerId: peerId, displayPreviews: .default)
|
||||
updatePeerNotificationSoundInteractive(transaction: transaction, peerId: peerId, sound: .default)
|
||||
let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: [peerId])
|
||||
|> map { _ -> Peer? in }
|
||||
|> then(context.account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(peerId)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
})).start(next: { peer in
|
||||
guard let peer = peer else {
|
||||
return
|
||||
}
|
||||
@ -917,11 +915,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode {
|
||||
updateState { value in
|
||||
return value.withUpdatedPeerMuteInterval(peer, nil).withUpdatedPeerSound(peer, .default).withUpdatedPeerDisplayPreviews(peer, .default)
|
||||
}
|
||||
_ = (context.account.postbox.transaction { transaction in
|
||||
updatePeerNotificationSoundInteractive(transaction: transaction, peerId: peer.id, sound: .default)
|
||||
updatePeerMuteSetting(transaction: transaction, peerId: peer.id, muteInterval: nil)
|
||||
updatePeerDisplayPreviewsSetting(transaction: transaction, peerId: peer.id, displayPreviews: .default)
|
||||
}
|
||||
let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: [peer.id])
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
updateNotificationsView({})
|
||||
})
|
||||
@ -953,13 +947,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
return state
|
||||
}
|
||||
let _ = (context.account.postbox.transaction { transaction -> Void in
|
||||
for value in values {
|
||||
updatePeerNotificationSoundInteractive(transaction: transaction, peerId: value.peer.id, sound: .default)
|
||||
updatePeerMuteSetting(transaction: transaction, peerId: value.peer.id, muteInterval: nil)
|
||||
updatePeerDisplayPreviewsSetting(transaction: transaction, peerId: value.peer.id, displayPreviews: .default)
|
||||
}
|
||||
}
|
||||
let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: values.map(\.peer.id))
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
updateNotificationsView({})
|
||||
})
|
||||
|
@ -216,7 +216,7 @@ public func confirmPhoneNumberCodeController(context: AccountContext, phoneNumbe
|
||||
|> take(1)
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return Signal { subscriber in
|
||||
return requestNextCancelAccountResetOption(network: context.account.network, phoneNumber: phoneNumber, phoneCodeHash: data.hash).start(next: { next in
|
||||
return context.engine.auth.requestNextCancelAccountResetOption(phoneNumber: phoneNumber, phoneCodeHash: data.hash).start(next: { next in
|
||||
currentDataPromise?.set(.single(next))
|
||||
}, error: { error in
|
||||
|
||||
@ -242,7 +242,7 @@ public func confirmPhoneNumberCodeController(context: AccountContext, phoneNumbe
|
||||
}
|
||||
}
|
||||
if let code = code {
|
||||
confirmPhoneDisposable.set((requestCancelAccountReset(network: context.account.network, phoneCodeHash: codeData.hash, phoneCode: code)
|
||||
confirmPhoneDisposable.set((context.engine.auth.requestCancelAccountReset(phoneCodeHash: codeData.hash, phoneCode: code)
|
||||
|> deliverOnMainQueue).start(error: { error in
|
||||
updateState { state in
|
||||
var state = state
|
||||
|
@ -426,7 +426,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController {
|
||||
})
|
||||
}).start()
|
||||
|
||||
actionsDisposable.add((deleteAllContacts(account: context.account)
|
||||
actionsDisposable.add((context.engine.contacts.deleteAllContacts()
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
updateState { state in
|
||||
var state = state
|
||||
|
@ -506,11 +506,11 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
actionsDisposable.add(updateAutoArchiveDisposable)
|
||||
|
||||
let privacySettingsPromise = Promise<AccountPrivacySettings?>()
|
||||
privacySettingsPromise.set(.single(initialSettings) |> then(requestAccountPrivacySettings(account: context.account) |> map(Optional.init)))
|
||||
privacySettingsPromise.set(.single(initialSettings) |> then(context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init)))
|
||||
|
||||
let blockedPeersContext = blockedPeersContext ?? BlockedPeersContext(account: context.account)
|
||||
let activeSessionsContext = activeSessionsContext ?? ActiveSessionsContext(account: context.account)
|
||||
let webSessionsContext = webSessionsContext ?? WebSessionsContext(account: context.account)
|
||||
let activeSessionsContext = activeSessionsContext ?? context.engine.privacy.activeSessions()
|
||||
let webSessionsContext = webSessionsContext ?? context.engine.privacy.webSessions()
|
||||
|
||||
let blockedPeersState = Promise<BlockedPeersContextState>()
|
||||
blockedPeersState.set(blockedPeersContext.state)
|
||||
@ -779,7 +779,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
return .complete()
|
||||
}
|
||||
|
||||
updateAutoArchiveDisposable.set((updateAccountAutoArchiveChats(account: context.account, value: archiveValue)
|
||||
updateAutoArchiveDisposable.set((context.engine.privacy.updateAccountAutoArchiveChats(value: archiveValue)
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in }
|
||||
|> then(applyTimeout)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
@ -817,7 +817,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
updateAccountTimeoutDisposable.set((updateAccountRemovalTimeout(account: context.account, timeout: timeout)
|
||||
updateAccountTimeoutDisposable.set((context.engine.privacy.updateAccountRemovalTimeout(timeout: timeout)
|
||||
|> then(applyTimeout)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
updateState { state in
|
||||
|
@ -1038,14 +1038,14 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective
|
||||
type = .phoneNumber
|
||||
}
|
||||
|
||||
let updateSettingsSignal = updateSelectiveAccountPrivacySettings(account: context.account, type: type, settings: settings)
|
||||
let updateSettingsSignal = context.engine.privacy.updateSelectiveAccountPrivacySettings(type: type, settings: settings)
|
||||
var updateCallP2PSettingsSignal: Signal<Void, NoError> = Signal.complete()
|
||||
if let callP2PSettings = callP2PSettings {
|
||||
updateCallP2PSettingsSignal = updateSelectiveAccountPrivacySettings(account: context.account, type: .voiceCallsP2P, settings: callP2PSettings)
|
||||
updateCallP2PSettingsSignal = context.engine.privacy.updateSelectiveAccountPrivacySettings(type: .voiceCallsP2P, settings: callP2PSettings)
|
||||
}
|
||||
var updatePhoneDiscoverySignal: Signal<Void, NoError> = Signal.complete()
|
||||
if let phoneDiscoveryEnabled = phoneDiscoveryEnabled {
|
||||
updatePhoneDiscoverySignal = updatePhoneNumberDiscovery(account: context.account, value: phoneDiscoveryEnabled)
|
||||
updatePhoneDiscoverySignal = context.engine.privacy.updatePhoneNumberDiscovery(value: phoneDiscoveryEnabled)
|
||||
}
|
||||
|
||||
let _ = (combineLatest(updateSettingsSignal, updateCallP2PSettingsSignal, updatePhoneDiscoverySignal)
|
||||
|
@ -1,260 +0,0 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import TextFormat
|
||||
import AccountContext
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import Markdown
|
||||
|
||||
private final class TwoStepVerificationResetControllerArguments {
|
||||
let updateEntryText: (String) -> Void
|
||||
let next: () -> Void
|
||||
let openEmailInaccessible: () -> Void
|
||||
|
||||
init(updateEntryText: @escaping (String) -> Void, next: @escaping () -> Void, openEmailInaccessible: @escaping () -> Void) {
|
||||
self.updateEntryText = updateEntryText
|
||||
self.next = next
|
||||
self.openEmailInaccessible = openEmailInaccessible
|
||||
}
|
||||
}
|
||||
|
||||
private enum TwoStepVerificationResetSection: Int32 {
|
||||
case password
|
||||
}
|
||||
|
||||
private enum TwoStepVerificationResetTag: ItemListItemTag {
|
||||
case input
|
||||
|
||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? TwoStepVerificationResetTag {
|
||||
switch self {
|
||||
case .input:
|
||||
if case .input = other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum TwoStepVerificationResetEntry: ItemListNodeEntry {
|
||||
case codeEntry(PresentationTheme, PresentationStrings, String, String)
|
||||
case codeInfo(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
return TwoStepVerificationResetSection.password.rawValue
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .codeEntry:
|
||||
return 0
|
||||
case .codeInfo:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: TwoStepVerificationResetEntry, rhs: TwoStepVerificationResetEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .codeEntry(lhsTheme, lhsStrings, lhsPlaceholder, lhsText):
|
||||
if case let .codeEntry(rhsTheme, rhsStrings, rhsPlaceholder, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .codeInfo(lhsTheme, lhsText):
|
||||
if case let .codeInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: TwoStepVerificationResetEntry, rhs: TwoStepVerificationResetEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! TwoStepVerificationResetControllerArguments
|
||||
switch self {
|
||||
case let .codeEntry(theme, strings, placeholder, text):
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: placeholder, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .password, spacing: 10.0, tag: TwoStepVerificationResetTag.input, sectionId: self.section, textUpdated: { updatedText in
|
||||
arguments.updateEntryText(updatedText)
|
||||
}, action: {
|
||||
arguments.next()
|
||||
})
|
||||
case let .codeInfo(theme, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TwoStepVerificationResetControllerState: Equatable {
|
||||
let codeText: String
|
||||
let checking: Bool
|
||||
|
||||
init(codeText: String, checking: Bool) {
|
||||
self.codeText = codeText
|
||||
self.checking = checking
|
||||
}
|
||||
|
||||
static func ==(lhs: TwoStepVerificationResetControllerState, rhs: TwoStepVerificationResetControllerState) -> Bool {
|
||||
if lhs.codeText != rhs.codeText {
|
||||
return false
|
||||
}
|
||||
if lhs.checking != rhs.checking {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func withUpdatedCodeText(_ codeText: String) -> TwoStepVerificationResetControllerState {
|
||||
return TwoStepVerificationResetControllerState(codeText: codeText, checking: self.checking)
|
||||
}
|
||||
|
||||
func withUpdatedChecking(_ checking: Bool) -> TwoStepVerificationResetControllerState {
|
||||
return TwoStepVerificationResetControllerState(codeText: self.codeText, checking: checking)
|
||||
}
|
||||
}
|
||||
|
||||
private func twoStepVerificationResetControllerEntries(presentationData: PresentationData, state: TwoStepVerificationResetControllerState, emailPattern: String) -> [TwoStepVerificationResetEntry] {
|
||||
var entries: [TwoStepVerificationResetEntry] = []
|
||||
|
||||
entries.append(.codeEntry(presentationData.theme, presentationData.strings, presentationData.strings.TwoStepAuth_RecoveryCode, state.codeText))
|
||||
entries.append(.codeInfo(presentationData.theme, "\(presentationData.strings.TwoStepAuth_RecoveryCodeHelp)\n\n[\(presentationData.strings.TwoStepAuth_RecoveryEmailUnavailable(escapedPlaintextForMarkdown(emailPattern)).0)]()"))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
func twoStepVerificationResetController(context: AccountContext, emailPattern: String, result: Promise<Bool>, requestedRecoveryReset: @escaping () -> Void) -> ViewController {
|
||||
let initialState = TwoStepVerificationResetControllerState(codeText: "", checking: false)
|
||||
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let updateState: ((TwoStepVerificationResetControllerState) -> TwoStepVerificationResetControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments) -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let resetPasswordDisposable = MetaDisposable()
|
||||
actionsDisposable.add(resetPasswordDisposable)
|
||||
|
||||
let checkCode: () -> Void = {
|
||||
var code: String?
|
||||
updateState { state in
|
||||
if state.checking || state.codeText.isEmpty {
|
||||
return state
|
||||
} else {
|
||||
code = state.codeText
|
||||
return state.withUpdatedChecking(true)
|
||||
}
|
||||
}
|
||||
if let code = code {
|
||||
resetPasswordDisposable.set((context.engine.auth.recoverTwoStepVerificationPassword(code: code) |> deliverOnMainQueue).start(error: { error in
|
||||
updateState {
|
||||
return $0.withUpdatedChecking(false)
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let alertText: String
|
||||
switch error {
|
||||
case .generic:
|
||||
alertText = presentationData.strings.Login_UnknownError
|
||||
case .invalidCode:
|
||||
alertText = presentationData.strings.Login_InvalidCodeError
|
||||
case .codeExpired:
|
||||
alertText = presentationData.strings.Login_CodeExpiredError
|
||||
case .limitExceeded:
|
||||
alertText = presentationData.strings.Login_CodeFloodError
|
||||
}
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}, completed: {
|
||||
updateState {
|
||||
return $0.withUpdatedChecking(false)
|
||||
}
|
||||
result.set(.single(true))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
let arguments = TwoStepVerificationResetControllerArguments(updateEntryText: { updatedText in
|
||||
updateState {
|
||||
$0.withUpdatedCodeText(updatedText)
|
||||
}
|
||||
}, next: {
|
||||
checkCode()
|
||||
}, openEmailInaccessible: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.TwoStepAuth_RecoveryUnavailableResetTitle, text: presentationData.strings.TwoStepAuth_RecoveryEmailResetText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.TwoStepAuth_RecoveryUnavailableResetAction, action: {
|
||||
let _ = (context.engine.auth.requestTwoStepPasswordReset()
|
||||
|> deliverOnMainQueue).start(next: { result in
|
||||
switch result {
|
||||
case .done, .waitingForReset:
|
||||
requestedRecoveryReset()
|
||||
case .declined:
|
||||
break
|
||||
case let .error(reason):
|
||||
break
|
||||
}
|
||||
})
|
||||
})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
})
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get()) |> deliverOnMainQueue
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||
dismissImpl?()
|
||||
})
|
||||
|
||||
var rightNavigationButton: ItemListNavigationButton?
|
||||
if state.checking {
|
||||
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||
} else {
|
||||
var nextEnabled = true
|
||||
if state.codeText.isEmpty {
|
||||
nextEnabled = false
|
||||
}
|
||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Next), style: .bold, enabled: nextEnabled, action: {
|
||||
checkCode()
|
||||
})
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.TwoStepAuth_RecoveryTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: twoStepVerificationResetControllerEntries(presentationData: presentationData, state: state, emailPattern: emailPattern), style: .blocks, focusItemTag: TwoStepVerificationResetTag.input, emptyStateItem: nil, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
} |> afterDisposed {
|
||||
actionsDisposable.dispose()
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
presentControllerImpl = { [weak controller] c, p in
|
||||
if let controller = controller {
|
||||
controller.present(c, in: .window(.root), with: p)
|
||||
}
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
@ -293,6 +293,7 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
|
||||
|
||||
var replaceControllerImpl: ((ViewController, Bool) -> Void)?
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
@ -507,28 +508,28 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
|
||||
state.checking = false
|
||||
return state
|
||||
}
|
||||
|
||||
var completionImpl: ((Bool) -> Void)?
|
||||
let controller = resetPasswordController(context: context, emailPattern: emailPattern, completion: { result in
|
||||
completionImpl?(result)
|
||||
|
||||
var stateUpdated: ((SetupTwoStepVerificationStateUpdate) -> Void)?
|
||||
let controller = TwoFactorDataInputScreen(sharedContext: context.sharedContext, engine: .authorized(context.engine), mode: .passwordRecoveryEmail(emailPattern: emailPattern, mode: .authorized), stateUpdated: { state in
|
||||
stateUpdated?(state)
|
||||
})
|
||||
completionImpl = { [weak controller] result in
|
||||
if !result {
|
||||
stateUpdated = { [weak controller] state in
|
||||
controller?.view.endEditing(true)
|
||||
controller?.dismiss()
|
||||
|
||||
switch state {
|
||||
case .noPassword, .awaitingEmailConfirmation, .passwordSet:
|
||||
controller?.dismiss()
|
||||
|
||||
dismissImpl?()
|
||||
case .pendingPasswordReset:
|
||||
dataPromise.set(context.engine.auth.twoStepVerificationConfiguration()
|
||||
|> map { TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationAccessConfiguration(configuration: $0, password: nil))
|
||||
})
|
||||
controller?.view.endEditing(true)
|
||||
controller?.dismiss()
|
||||
} else {
|
||||
dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: .notSet(pendingEmail: nil))))
|
||||
controller?.view.endEditing(true)
|
||||
controller?.dismiss()
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.TwoStepAuth_DisableSuccess, false)), nil)
|
||||
}
|
||||
}
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
|
||||
pushControllerImpl?(controller)
|
||||
}, error: { _ in
|
||||
updateState { state in
|
||||
var state = state
|
||||
@ -592,6 +593,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
|
||||
case .notSet:
|
||||
let controller = SetupTwoStepVerificationController(context: context, initialState: .createPassword, stateUpdated: { update, shouldDismiss, controller in
|
||||
switch update {
|
||||
case .pendingPasswordReset:
|
||||
break
|
||||
case .noPassword:
|
||||
dataPromise.set(.single(.access(configuration: .notSet(pendingEmail: nil))))
|
||||
case let .awaitingEmailConfirmation(password, pattern, codeLength):
|
||||
@ -623,6 +626,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
|
||||
case let .manage(password, hasRecovery, pendingEmail, hasSecureValues):
|
||||
let controller = SetupTwoStepVerificationController(context: context, initialState: .updatePassword(current: password, hasRecoveryEmail: hasRecovery, hasSecureValues: hasSecureValues), stateUpdated: { update, shouldDismiss, controller in
|
||||
switch update {
|
||||
case .pendingPasswordReset:
|
||||
break
|
||||
case .noPassword:
|
||||
dataPromise.set(.single(.access(configuration: .notSet(pendingEmail: nil))))
|
||||
case .awaitingEmailConfirmation:
|
||||
@ -712,10 +717,10 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
|
||||
case .access:
|
||||
break
|
||||
case let .manage(password, emailSet, _, hasSecureValues):
|
||||
//let controller = TwoFactorDataInputScreen(context: context, mode: .updateEmailAddress(password: password))
|
||||
|
||||
let controller = SetupTwoStepVerificationController(context: context, initialState: .addEmail(hadRecoveryEmail: emailSet, hasSecureValues: hasSecureValues, password: password), stateUpdated: { update, shouldDismiss, controller in
|
||||
switch update {
|
||||
case .pendingPasswordReset:
|
||||
break
|
||||
case .noPassword:
|
||||
assertionFailure()
|
||||
break
|
||||
@ -803,6 +808,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
|
||||
}
|
||||
let controller = SetupTwoStepVerificationController(context: context, initialState: .confirmEmail(password: password, hasSecureValues: hasSecureValues, pattern: pendingEmail.pattern, codeLength: pendingEmail.codeLength), stateUpdated: { update, shouldDismiss, controller in
|
||||
switch update {
|
||||
case .pendingPasswordReset:
|
||||
break
|
||||
case .noPassword:
|
||||
assertionFailure()
|
||||
break
|
||||
@ -941,6 +948,9 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
|
||||
controller.present(c, in: .window(.root), with: p)
|
||||
}
|
||||
}
|
||||
pushControllerImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
|
@ -430,7 +430,7 @@ private func privacySearchableItems(context: AccountContext, privacySettings: Ac
|
||||
if let privacySettings = privacySettings {
|
||||
privacySignal = .single(privacySettings)
|
||||
} else {
|
||||
privacySignal = requestAccountPrivacySettings(account: context.account)
|
||||
privacySignal = context.engine.privacy.requestAccountPrivacySettings()
|
||||
}
|
||||
let callsSignal: Signal<(VoiceCallSettings, VoipConfiguration)?, NoError>
|
||||
if case .voiceCalls = kind {
|
||||
@ -535,10 +535,10 @@ private func privacySearchableItems(context: AccountContext, privacySettings: Ac
|
||||
present(.push, twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: true, data: nil)))
|
||||
}),
|
||||
activeSessionsContext == nil ? nil : SettingsSearchableItem(id: .privacy(9), title: strings.Settings_Devices, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_AuthSessions) + [strings.PrivacySettings_AuthSessions], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
|
||||
present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext!, webSessionsContext: webSessionsContext ?? WebSessionsContext(account: context.account), websitesOnly: false))
|
||||
present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext!, webSessionsContext: webSessionsContext ?? context.engine.privacy.webSessions(), websitesOnly: false))
|
||||
}),
|
||||
webSessionsContext == nil ? nil : SettingsSearchableItem(id: .privacy(10), title: strings.PrivacySettings_WebSessions, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_AuthSessions), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
|
||||
present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext ?? ActiveSessionsContext(account: context.account), webSessionsContext: webSessionsContext ?? WebSessionsContext(account: context.account), websitesOnly: true))
|
||||
present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext ?? context.engine.privacy.activeSessions(), webSessionsContext: webSessionsContext ?? context.engine.privacy.webSessions(), websitesOnly: true))
|
||||
}),
|
||||
SettingsSearchableItem(id: .privacy(11), title: strings.PrivacySettings_DeleteAccountTitle, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_DeleteAccountIfAwayFor), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
|
||||
presentPrivacySettings(context, present, .accountTimeout)
|
||||
|
@ -379,7 +379,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
let animation = contentNode.layer.makeAnimation(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "opacity", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.35)
|
||||
animation.fillMode = .both
|
||||
if !fastOut {
|
||||
animation.beginTime = CACurrentMediaTime() + 0.1
|
||||
animation.beginTime = contentNode.layer.convertTime(CACurrentMediaTime(), from: nil) + 0.1
|
||||
}
|
||||
contentNode.layer.add(animation, forKey: "opacity")
|
||||
}
|
||||
|
@ -584,7 +584,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1160215659] = { return Api.InputMessage.parse_inputMessageReplyTo($0) }
|
||||
dict[-2037963464] = { return Api.InputMessage.parse_inputMessagePinned($0) }
|
||||
dict[-1392895362] = { return Api.InputMessage.parse_inputMessageCallbackQuery($0) }
|
||||
dict[2028213859] = { return Api.GroupCallParticipantVideo.parse_groupCallParticipantVideo($0) }
|
||||
dict[1735736008] = { return Api.GroupCallParticipantVideo.parse_groupCallParticipantVideo($0) }
|
||||
dict[-58224696] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($0) }
|
||||
dict[-1237848657] = { return Api.StatsDateRangeDays.parse_statsDateRangeDays($0) }
|
||||
dict[-275956116] = { return Api.messages.AffectedFoundMessages.parse_affectedFoundMessages($0) }
|
||||
|
@ -14974,13 +14974,13 @@ public extension Api {
|
||||
|
||||
}
|
||||
public enum GroupCallParticipantVideo: TypeConstructorDescription {
|
||||
case groupCallParticipantVideo(flags: Int32, endpoint: String, sourceGroups: [Api.GroupCallParticipantVideoSourceGroup])
|
||||
case groupCallParticipantVideo(flags: Int32, endpoint: String, sourceGroups: [Api.GroupCallParticipantVideoSourceGroup], audioSource: Int32?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .groupCallParticipantVideo(let flags, let endpoint, let sourceGroups):
|
||||
case .groupCallParticipantVideo(let flags, let endpoint, let sourceGroups, let audioSource):
|
||||
if boxed {
|
||||
buffer.appendInt32(2028213859)
|
||||
buffer.appendInt32(1735736008)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(endpoint, buffer: buffer, boxed: false)
|
||||
@ -14989,14 +14989,15 @@ public extension Api {
|
||||
for item in sourceGroups {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(audioSource!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .groupCallParticipantVideo(let flags, let endpoint, let sourceGroups):
|
||||
return ("groupCallParticipantVideo", [("flags", flags), ("endpoint", endpoint), ("sourceGroups", sourceGroups)])
|
||||
case .groupCallParticipantVideo(let flags, let endpoint, let sourceGroups, let audioSource):
|
||||
return ("groupCallParticipantVideo", [("flags", flags), ("endpoint", endpoint), ("sourceGroups", sourceGroups), ("audioSource", audioSource)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -15009,11 +15010,14 @@ public extension Api {
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipantVideoSourceGroup.self)
|
||||
}
|
||||
var _4: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.GroupCallParticipantVideo.groupCallParticipantVideo(flags: _1!, endpoint: _2!, sourceGroups: _3!)
|
||||
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.GroupCallParticipantVideo.groupCallParticipantVideo(flags: _1!, endpoint: _2!, sourceGroups: _3!, audioSource: _4)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -415,12 +415,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
private var screencastBufferServerContext: IpcGroupCallBufferAppContext?
|
||||
private var screencastCapturer: OngoingCallVideoCapturer?
|
||||
|
||||
//private var screencastIpcContext: IpcGroupCallAppContext?
|
||||
|
||||
private var ssrcMapping: [UInt32: PeerId] = [:]
|
||||
|
||||
private var requestedSsrcs = Set<UInt32>()
|
||||
|
||||
private var summaryInfoState = Promise<SummaryInfoState?>(nil)
|
||||
private var summaryParticipantsState = Promise<SummaryParticipantsState?>(nil)
|
||||
|
||||
@ -887,7 +883,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.genericCallContext?.addExternalAudioData(data: data)
|
||||
strongSelf.screencastCallContext?.addExternalAudioData(data: data)
|
||||
})
|
||||
self.screencastStateDisposable = (screencastBufferServerContext.isActive
|
||||
|> distinctUntilChanged
|
||||
@ -1140,7 +1136,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
let rawAdminIds: Signal<Set<PeerId>, NoError>
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
rawAdminIds = Signal { subscriber in
|
||||
let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(postbox: accountContext.account.postbox, network: accountContext.account.network, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in
|
||||
let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(engine: accountContext.engine, postbox: accountContext.account.postbox, network: accountContext.account.network, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in
|
||||
var peerIds = Set<PeerId>()
|
||||
for item in list.list {
|
||||
if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) {
|
||||
@ -1429,7 +1425,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
let peerId = strongSelf.peerId
|
||||
if strongSelf.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
peerAdminIds = Signal { subscriber in
|
||||
let (disposable, _) = strongSelf.accountContext.peerChannelMemberCategoriesContextsManager.admins(postbox: strongSelf.accountContext.account.postbox, network: strongSelf.accountContext.account.network, accountPeerId: strongSelf.accountContext.account.peerId, peerId: peerId, updated: { list in
|
||||
let (disposable, _) = strongSelf.accountContext.peerChannelMemberCategoriesContextsManager.admins(engine: strongSelf.accountContext.engine, postbox: strongSelf.accountContext.account.postbox, network: strongSelf.accountContext.account.network, accountPeerId: strongSelf.accountContext.account.peerId, peerId: peerId, updated: { list in
|
||||
var peerIds = Set<PeerId>()
|
||||
for item in list.list {
|
||||
if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) {
|
||||
@ -1674,7 +1670,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
let rawAdminIds: Signal<Set<PeerId>, NoError>
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
rawAdminIds = Signal { subscriber in
|
||||
let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(postbox: accountContext.account.postbox, network: accountContext.account.network, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in
|
||||
let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(engine: accountContext.engine, postbox: accountContext.account.postbox, network: accountContext.account.network, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in
|
||||
var peerIds = Set<PeerId>()
|
||||
for item in list.list {
|
||||
if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) {
|
||||
@ -2050,6 +2046,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
videoDescription: nil
|
||||
))
|
||||
}
|
||||
|
||||
if let screencastSsrc = participant.presentationDescription?.audioSsrc {
|
||||
if remainingSsrcs.contains(screencastSsrc) {
|
||||
remainingSsrcs.remove(screencastSsrc)
|
||||
|
||||
result.append(OngoingGroupCallContext.MediaChannelDescription(
|
||||
kind: .audio,
|
||||
audioSsrc: screencastSsrc,
|
||||
videoDescription: nil
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1545,7 +1545,7 @@ public final class VoiceChatController: ViewController {
|
||||
let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_EditBioTitle, text: presentationData.strings.VoiceChat_EditBioText, placeholder: presentationData.strings.VoiceChat_EditBioPlaceholder, doneButtonTitle: presentationData.strings.VoiceChat_EditBioSave, value: entry.about, maxLength: maxBioLength, apply: { bio in
|
||||
if let strongSelf = self, let bio = bio {
|
||||
if peer.id.namespace == Namespaces.Peer.CloudUser {
|
||||
let _ = (updateAbout(account: strongSelf.context.account, about: bio)
|
||||
let _ = (strongSelf.context.engine.accountData.updateAbout(about: bio)
|
||||
|> `catch` { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}).start()
|
||||
@ -1575,7 +1575,7 @@ public final class VoiceChatController: ViewController {
|
||||
Queue.mainQueue().after(0.1) {
|
||||
let controller = voiceChatUserNameController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_ChangeNameTitle, firstNamePlaceholder: presentationData.strings.UserInfo_FirstNamePlaceholder, lastNamePlaceholder: presentationData.strings.UserInfo_LastNamePlaceholder, doneButtonTitle: presentationData.strings.VoiceChat_EditBioSave, firstName: peer.firstName, lastName: peer.lastName, maxLength: 128, apply: { firstAndLastName in
|
||||
if let strongSelf = self, let (firstName, lastName) = firstAndLastName {
|
||||
let _ = updateAccountPeerName(account: context.account, firstName: firstName, lastName: lastName).start()
|
||||
let _ = context.engine.accountData.updateAccountPeerName(firstName: firstName, lastName: lastName).start()
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .info(text: strongSelf.presentationData.strings.VoiceChat_EditNameSuccess), action: { _ in return false })
|
||||
}
|
||||
@ -1711,7 +1711,7 @@ public final class VoiceChatController: ViewController {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = strongSelf.context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: strongSelf.context.account, peerId: strongSelf.call.peerId, memberId: peer.id, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max)).start()
|
||||
let _ = strongSelf.context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: strongSelf.context.engine, peerId: strongSelf.call.peerId, memberId: peer.id, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max)).start()
|
||||
strongSelf.call.removedPeer(peer.id)
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .banned(text: strongSelf.presentationData.strings.VoiceChat_RemovedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0), action: { _ in return false })
|
||||
|
@ -4,7 +4,7 @@ import SwiftSignalKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public func currentWebDocumentsHostDatacenterId(postbox: Postbox, isTestingEnvironment: Bool) -> Signal<Int32, NoError> {
|
||||
func currentWebDocumentsHostDatacenterId(postbox: Postbox, isTestingEnvironment: Bool) -> Signal<Int32, NoError> {
|
||||
return postbox.transaction { transaction -> Int32 in
|
||||
if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.remoteStorageConfiguration) as? RemoteStorageConfiguration {
|
||||
return entry.webDocumentsHostDatacenterId
|
@ -342,30 +342,6 @@ public enum PasswordRecoveryOption {
|
||||
case email(pattern: String)
|
||||
}
|
||||
|
||||
public func requestPasswordRecovery(account: UnauthorizedAccount) -> Signal<PasswordRecoveryOption, PasswordRecoveryRequestError> {
|
||||
return account.network.request(Api.functions.auth.requestPasswordRecovery())
|
||||
|> map(Optional.init)
|
||||
|> `catch` { error -> Signal<Api.auth.PasswordRecovery?, PasswordRecoveryRequestError> in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .fail(.limitExceeded)
|
||||
} else if error.errorDescription.hasPrefix("PASSWORD_RECOVERY_NA") {
|
||||
return .single(nil)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|> map { result -> PasswordRecoveryOption in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .passwordRecovery(emailPattern):
|
||||
return .email(pattern: emailPattern)
|
||||
}
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PasswordRecoveryError {
|
||||
case invalidCode
|
||||
case limitExceeded
|
||||
@ -373,7 +349,7 @@ public enum PasswordRecoveryError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func checkPasswordRecoveryCode(network: Network, code: String) -> Signal<Never, PasswordRecoveryError> {
|
||||
func _internal_checkPasswordRecoveryCode(network: Network, code: String) -> Signal<Never, PasswordRecoveryError> {
|
||||
return network.request(Api.functions.auth.checkRecoveryPassword(code: code), automaticFloodWait: false)
|
||||
|> mapError { error -> PasswordRecoveryError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
@ -389,12 +365,40 @@ public func checkPasswordRecoveryCode(network: Network, code: String) -> Signal<
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_performPasswordRecovery(accountManager: AccountManager, account: UnauthorizedAccount, code: String, syncContacts: Bool, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<Void, PasswordRecoveryError> {
|
||||
return _internal_twoStepAuthData(account.network)
|
||||
public final class RecoveredAccountData {
|
||||
let authorization: Api.auth.Authorization
|
||||
|
||||
init(authorization: Api.auth.Authorization) {
|
||||
self.authorization = authorization
|
||||
}
|
||||
}
|
||||
|
||||
public func loginWithRecoveredAccountData(accountManager: AccountManager, account: UnauthorizedAccount, recoveredAccountData: RecoveredAccountData, syncContacts: Bool) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
switch recoveredAccountData.authorization {
|
||||
case let .authorization(_, _, user):
|
||||
let user = TelegramUser(user: user)
|
||||
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
|
||||
|
||||
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
|
||||
transaction.setState(state)
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
switchToAuthorizedAccount(transaction: transaction, account: account)
|
||||
}
|
||||
case .authorizationSignUpRequired:
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
func _internal_performPasswordRecovery(network: Network, code: String, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<RecoveredAccountData, PasswordRecoveryError> {
|
||||
return _internal_twoStepAuthData(network)
|
||||
|> mapError { _ -> PasswordRecoveryError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { authData -> Signal<Void, PasswordRecoveryError> in
|
||||
|> mapToSignal { authData -> Signal<RecoveredAccountData, PasswordRecoveryError> in
|
||||
let newSettings: Api.account.PasswordInputSettings?
|
||||
switch updatedPassword {
|
||||
case .none:
|
||||
@ -405,7 +409,7 @@ func _internal_performPasswordRecovery(accountManager: AccountManager, account:
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
|
||||
guard let (updatedPasswordHash, updatedPasswordDerivation) = passwordUpdateKDF(encryptionProvider: account.network.encryptionProvider, password: password, derivation: authData.nextPasswordDerivation) else {
|
||||
guard let (updatedPasswordHash, updatedPasswordDerivation) = passwordUpdateKDF(encryptionProvider: network.encryptionProvider, password: password, derivation: authData.nextPasswordDerivation) else {
|
||||
return .fail(.invalidCode)
|
||||
}
|
||||
|
||||
@ -416,7 +420,7 @@ func _internal_performPasswordRecovery(accountManager: AccountManager, account:
|
||||
if newSettings != nil {
|
||||
flags |= 1 << 0
|
||||
}
|
||||
return account.network.request(Api.functions.auth.recoverPassword(flags: flags, code: code, newSettings: newSettings), automaticFloodWait: false)
|
||||
return network.request(Api.functions.auth.recoverPassword(flags: flags, code: code, newSettings: newSettings), automaticFloodWait: false)
|
||||
|> mapError { error -> PasswordRecoveryError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
@ -426,24 +430,8 @@ func _internal_performPasswordRecovery(accountManager: AccountManager, account:
|
||||
return .invalidCode
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, PasswordRecoveryError> in
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
switch result {
|
||||
case let .authorization(_, _, user):
|
||||
let user = TelegramUser(user: user)
|
||||
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
|
||||
|
||||
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
|
||||
transaction.setState(state)
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
switchToAuthorizedAccount(transaction: transaction, account: account)
|
||||
}
|
||||
case .authorizationSignUpRequired:
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
|> mapError { _ -> PasswordRecoveryError in }
|
||||
|> mapToSignal { result -> Signal<RecoveredAccountData, PasswordRecoveryError> in
|
||||
return .single(RecoveredAccountData(authorization: result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,82 +0,0 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public func channelAdmins(account: Account, peerId: PeerId) -> Signal<[RenderedChannelParticipant], NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<[RenderedChannelParticipant], NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
|
||||
return account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsAdmins, offset: 0, limit: 100, hash: 0))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<[RenderedChannelParticipant], NoError> in
|
||||
switch result {
|
||||
case let .channelParticipants(count, participants, chats, users):
|
||||
var items: [RenderedChannelParticipant] = []
|
||||
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
var presences:[PeerId: PeerPresence] = [:]
|
||||
for user in users {
|
||||
let peer = TelegramUser(user: user)
|
||||
peers[peer.id] = peer
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
presences[peer.id] = presence
|
||||
}
|
||||
}
|
||||
|
||||
for participant in CachedChannelParticipants(apiParticipants: participants).participants {
|
||||
if let peer = peers[participant.peerId] {
|
||||
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: presences))
|
||||
}
|
||||
}
|
||||
|
||||
return account.postbox.transaction { transaction -> [RenderedChannelParticipant] in
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||
if let cachedData = cachedData as? CachedChannelData {
|
||||
return cachedData.withUpdatedParticipantsSummary(cachedData.participantsSummary.withUpdatedAdminCount(count))
|
||||
} else {
|
||||
return cachedData
|
||||
}
|
||||
})
|
||||
return items
|
||||
}
|
||||
case .channelParticipantsNotModified:
|
||||
return .single([])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single([])
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
public func channelAdminIds(postbox: Postbox, network: Network, peerId: PeerId, hash: Int32) -> Signal<[PeerId], NoError> {
|
||||
return postbox.transaction { transaction in
|
||||
if let peer = transaction.getPeer(peerId) as? TelegramChannel, case .group = peer.info, let apiChannel = apiInputChannel(peer) {
|
||||
let api = Api.functions.channels.getParticipants(channel: apiChannel, filter: .channelParticipantsAdmins, offset: 0, limit: 100, hash: hash)
|
||||
return network.request(api) |> retryRequest |> mapToSignal { result in
|
||||
switch result {
|
||||
case let .channelParticipants(_, participants, _, users):
|
||||
let users = users.filter({ user in
|
||||
return participants.contains(where: { participant in
|
||||
switch participant {
|
||||
case let .channelParticipantAdmin(_, userId, _, _, _, _, _):
|
||||
return user.peerId.id._internalGetInt64Value() == userId
|
||||
case let .channelParticipantCreator(_, userId, _, _):
|
||||
return user.peerId.id._internalGetInt64Value() == userId
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
return .single(users.map({TelegramUser(user: $0).id}))
|
||||
default:
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
return .complete()
|
||||
} |> switchToLatest
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public struct RenderedChannelParticipant: Equatable {
|
||||
public let participant: ChannelParticipant
|
||||
public let peer: Peer
|
||||
public let peers: [PeerId: Peer]
|
||||
public let presences: [PeerId: PeerPresence]
|
||||
|
||||
public init(participant: ChannelParticipant, peer: Peer, peers: [PeerId: Peer] = [:], presences: [PeerId: PeerPresence] = [:]) {
|
||||
self.participant = participant
|
||||
self.peer = peer
|
||||
self.peers = peers
|
||||
self.presences = presences
|
||||
}
|
||||
|
||||
public static func ==(lhs: RenderedChannelParticipant, rhs: RenderedChannelParticipant) -> Bool {
|
||||
return lhs.participant == rhs.participant && lhs.peer.isEqual(rhs.peer)
|
||||
}
|
||||
}
|
||||
|
||||
func updateChannelParticipantsSummary(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
|
||||
let admins = account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsAdmins, offset: 0, limit: 0, hash: 0))
|
||||
let members = account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsRecent, offset: 0, limit: 0, hash: 0))
|
||||
let banned = account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsBanned(q: ""), offset: 0, limit: 0, hash: 0))
|
||||
let kicked = account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsKicked(q: ""), offset: 0, limit: 0, hash: 0))
|
||||
return combineLatest(admins, members, banned, kicked)
|
||||
|> mapToSignal { admins, members, banned, kicked -> Signal<Void, MTRpcError> in
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedChannelData {
|
||||
let adminCount: Int32
|
||||
switch admins {
|
||||
case let .channelParticipants(count, _, _, _):
|
||||
adminCount = count
|
||||
case .channelParticipantsNotModified:
|
||||
assertionFailure()
|
||||
adminCount = 0
|
||||
}
|
||||
let memberCount: Int32
|
||||
switch members {
|
||||
case let .channelParticipants(count, _, _, _):
|
||||
memberCount = count
|
||||
case .channelParticipantsNotModified:
|
||||
assertionFailure()
|
||||
memberCount = 0
|
||||
}
|
||||
let bannedCount: Int32
|
||||
switch banned {
|
||||
case let .channelParticipants(count, _, _, _):
|
||||
bannedCount = count
|
||||
case .channelParticipantsNotModified:
|
||||
assertionFailure()
|
||||
bannedCount = 0
|
||||
}
|
||||
let kickedCount: Int32
|
||||
switch kicked {
|
||||
case let .channelParticipants(count, _, _, _):
|
||||
kickedCount = count
|
||||
case .channelParticipantsNotModified:
|
||||
assertionFailure()
|
||||
kickedCount = 0
|
||||
}
|
||||
return current.withUpdatedParticipantsSummary(CachedChannelParticipantsSummary(memberCount: memberCount, adminCount: adminCount, bannedCount: bannedCount, kickedCount: kickedCount))
|
||||
}
|
||||
return current
|
||||
})
|
||||
} |> mapError { _ -> MTRpcError in return MTRpcError(errorCode: 0, errorDescription: "") }
|
||||
}
|
||||
|> `catch` { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
@ -461,7 +461,7 @@ final class MediaReferenceRevalidationContext {
|
||||
|
||||
func peerAvatars(postbox: Postbox, network: Network, background: Bool, peer: PeerReference) -> Signal<[TelegramPeerPhoto], RevalidateMediaReferenceError> {
|
||||
return self.genericItem(key: .peerAvatars(peer: peer), background: background, request: { next, error in
|
||||
return (requestPeerPhotos(postbox: postbox, network: network, peerId: peer.id)
|
||||
return (_internal_requestPeerPhotos(postbox: postbox, network: network, peerId: peer.id)
|
||||
|> mapError { _ -> RevalidateMediaReferenceError in
|
||||
return .generic
|
||||
}).start(next: { value in
|
||||
|
@ -855,6 +855,7 @@ public final class GroupCallParticipantsContext {
|
||||
|
||||
public var endpointId: String
|
||||
public var ssrcGroups: [SsrcGroup]
|
||||
public var audioSsrc: UInt32?
|
||||
public var isPaused: Bool
|
||||
}
|
||||
|
||||
@ -1527,6 +1528,9 @@ public final class GroupCallParticipantsContext {
|
||||
if let ssrc = participant.ssrc {
|
||||
existingSsrcs.insert(ssrc)
|
||||
}
|
||||
if let presentationDescription = participant.presentationDescription, let presentationAudioSsrc = presentationDescription.audioSsrc {
|
||||
existingSsrcs.insert(presentationAudioSsrc)
|
||||
}
|
||||
}
|
||||
|
||||
for ssrc in ssrcs {
|
||||
@ -2488,7 +2492,7 @@ extension GroupCallParticipantsContext.Participant {
|
||||
private extension GroupCallParticipantsContext.Participant.VideoDescription {
|
||||
init(_ apiVideo: Api.GroupCallParticipantVideo) {
|
||||
switch apiVideo {
|
||||
case let .groupCallParticipantVideo(flags, endpoint, sourceGroups):
|
||||
case let .groupCallParticipantVideo(flags, endpoint, sourceGroups, audioSource):
|
||||
var parsedSsrcGroups: [SsrcGroup] = []
|
||||
for group in sourceGroups {
|
||||
switch group {
|
||||
@ -2497,7 +2501,7 @@ private extension GroupCallParticipantsContext.Participant.VideoDescription {
|
||||
}
|
||||
}
|
||||
let isPaused = (flags & (1 << 0)) != 0
|
||||
self.init(endpointId: endpoint, ssrcGroups: parsedSsrcGroups, isPaused: isPaused)
|
||||
self.init(endpointId: endpoint, ssrcGroups: parsedSsrcGroups, audioSsrc: audioSource.flatMap(UInt32.init(bitPattern:)), isPaused: isPaused)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,210 +0,0 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public enum MessageReactionListCategory: Hashable {
|
||||
case all
|
||||
case reaction(String)
|
||||
}
|
||||
|
||||
public final class MessageReactionListCategoryItem: Equatable {
|
||||
public let peer: Peer
|
||||
public let reaction: String
|
||||
|
||||
init(peer: Peer, reaction: String) {
|
||||
self.peer = peer
|
||||
self.reaction = reaction
|
||||
}
|
||||
|
||||
public static func ==(lhs: MessageReactionListCategoryItem, rhs: MessageReactionListCategoryItem) -> Bool {
|
||||
if lhs.peer.id != rhs.peer.id {
|
||||
return false
|
||||
}
|
||||
if lhs.reaction != rhs.reaction {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public struct MessageReactionListCategoryState: Equatable {
|
||||
public var count: Int
|
||||
public var completed: Bool
|
||||
public var items: [MessageReactionListCategoryItem]
|
||||
public var loadingMore: Bool
|
||||
fileprivate var nextOffset: String?
|
||||
}
|
||||
|
||||
private enum LoadReactionsError {
|
||||
case generic
|
||||
}
|
||||
|
||||
private final class MessageReactionCategoryContext {
|
||||
private let postbox: Postbox
|
||||
private let network: Network
|
||||
private let messageId: MessageId
|
||||
private let category: MessageReactionListCategory
|
||||
private var state: MessageReactionListCategoryState
|
||||
var statePromise: ValuePromise<MessageReactionListCategoryState>
|
||||
|
||||
private let loadingDisposable = MetaDisposable()
|
||||
|
||||
init(postbox: Postbox, network: Network, messageId: MessageId, category: MessageReactionListCategory, initialState: MessageReactionListCategoryState) {
|
||||
self.postbox = postbox
|
||||
self.network = network
|
||||
self.messageId = messageId
|
||||
self.category = category
|
||||
self.state = initialState
|
||||
self.statePromise = ValuePromise(initialState)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.loadingDisposable.dispose()
|
||||
}
|
||||
|
||||
func loadMore() {
|
||||
if self.state.completed || self.state.loadingMore {
|
||||
return
|
||||
}
|
||||
/*self.state.loadingMore = true
|
||||
self.statePromise.set(self.state)
|
||||
|
||||
var flags: Int32 = 0
|
||||
var reaction: String?
|
||||
switch self.category {
|
||||
case .all:
|
||||
break
|
||||
case let .reaction(value):
|
||||
flags |= 1 << 0
|
||||
reaction = value
|
||||
}
|
||||
let messageId = self.messageId
|
||||
let offset = self.state.nextOffset
|
||||
var request = self.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
||||
return inputPeer
|
||||
}
|
||||
|> castError(LoadReactionsError.self)
|
||||
|> mapToSignal { inputPeer -> Signal<Api.messages.MessageReactionsList, LoadReactionsError> in
|
||||
guard let inputPeer = inputPeer else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return self.network.request(Api.functions.messages.getMessageReactionsList(flags: flags, peer: inputPeer, id: messageId.id, reaction: reaction, offset: offset, limit: 64))
|
||||
|> mapError { _ -> LoadReactionsError in
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
//#if DEBUG
|
||||
//request = request |> delay(1.0, queue: .mainQueue())
|
||||
//#endif
|
||||
self.loadingDisposable.set((request
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let currentState = strongSelf.state
|
||||
let _ = (strongSelf.postbox.transaction { transaction -> MessageReactionListCategoryState in
|
||||
var mergedItems = currentState.items
|
||||
var currentIds = Set(mergedItems.lazy.map { $0.peer.id })
|
||||
switch result {
|
||||
case let .messageReactionsList(_, count, reactions, users, nextOffset):
|
||||
var peers: [Peer] = []
|
||||
for user in users {
|
||||
let parsedUser = TelegramUser(user: user)
|
||||
peers.append(parsedUser)
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated in updated })
|
||||
for reaction in reactions {
|
||||
switch reaction {
|
||||
case let .messageUserReaction(userId, reaction):
|
||||
if let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)) {
|
||||
if !currentIds.contains(peer.id) {
|
||||
currentIds.insert(peer.id)
|
||||
mergedItems.append(MessageReactionListCategoryItem(peer: peer, reaction: reaction))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return MessageReactionListCategoryState(count: max(mergedItems.count, Int(count)), completed: nextOffset == nil, items: mergedItems, loadingMore: false, nextOffset: nextOffset)
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { state in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.state = state
|
||||
strongSelf.statePromise.set(state)
|
||||
})
|
||||
}, error: { _ in
|
||||
|
||||
}))*/
|
||||
}
|
||||
}
|
||||
|
||||
public struct MessageReactionListState: Equatable {
|
||||
public var states: [(MessageReactionListCategory, MessageReactionListCategoryState)]
|
||||
|
||||
public static func ==(lhs: MessageReactionListState, rhs: MessageReactionListState) -> Bool {
|
||||
if lhs.states.count != rhs.states.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhs.states.count {
|
||||
if lhs.states[i].0 != rhs.states[i].0 {
|
||||
return false
|
||||
}
|
||||
if lhs.states[i].1 != rhs.states[i].1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class MessageReactionListContext {
|
||||
private let postbox: Postbox
|
||||
private let network: Network
|
||||
|
||||
private var categoryContexts: [MessageReactionListCategory: MessageReactionCategoryContext] = [:]
|
||||
|
||||
private let _state = Promise<MessageReactionListState>()
|
||||
public var state: Signal<MessageReactionListState, NoError> {
|
||||
return self._state.get()
|
||||
}
|
||||
|
||||
public init(postbox: Postbox, network: Network, messageId: MessageId, initialReactions: [MessageReaction]) {
|
||||
self.postbox = postbox
|
||||
self.network = network
|
||||
|
||||
var allState = MessageReactionListCategoryState(count: 0, completed: false, items: [], loadingMore: false, nextOffset: nil)
|
||||
var signals: [Signal<(MessageReactionListCategory, MessageReactionListCategoryState), NoError>] = []
|
||||
for reaction in initialReactions {
|
||||
allState.count += Int(reaction.count)
|
||||
let context = MessageReactionCategoryContext(postbox: postbox, network: network, messageId: messageId, category: .reaction(reaction.value), initialState: MessageReactionListCategoryState(count: Int(reaction.count), completed: false, items: [], loadingMore: false, nextOffset: nil))
|
||||
signals.append(context.statePromise.get() |> map { value -> (MessageReactionListCategory, MessageReactionListCategoryState) in
|
||||
return (.reaction(reaction.value), value)
|
||||
})
|
||||
self.categoryContexts[.reaction(reaction.value)] = context
|
||||
context.loadMore()
|
||||
}
|
||||
let allContext = MessageReactionCategoryContext(postbox: postbox, network: network, messageId: messageId, category: .all, initialState: allState)
|
||||
signals.insert(allContext.statePromise.get() |> map { value -> (MessageReactionListCategory, MessageReactionListCategoryState) in
|
||||
return (.all, value)
|
||||
}, at: 0)
|
||||
self.categoryContexts[.all] = allContext
|
||||
|
||||
self._state.set(combineLatest(queue: .mainQueue(), signals)
|
||||
|> map { states in
|
||||
return MessageReactionListState(states: states)
|
||||
})
|
||||
|
||||
allContext.loadMore()
|
||||
}
|
||||
|
||||
public func loadMore(category: MessageReactionListCategory) {
|
||||
self.categoryContexts[category]?.loadMore()
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
private struct PeerParticipants: Equatable {
|
||||
let peers: [Peer]
|
||||
|
||||
static func ==(lhs: PeerParticipants, rhs: PeerParticipants) -> Bool {
|
||||
if lhs.peers.count != rhs.peers.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhs.peers.count {
|
||||
if !lhs.peers[i].isEqual(rhs.peers[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public func peerParticipants(postbox: Postbox, id: PeerId) -> Signal<[Peer], NoError> {
|
||||
return postbox.peerView(id: id) |> map { view -> PeerParticipants in
|
||||
if let cachedGroupData = view.cachedData as? CachedGroupData, let participants = cachedGroupData.participants {
|
||||
var peers: [Peer] = []
|
||||
for participant in participants.participants {
|
||||
if let peer = view.peers[participant.peerId] {
|
||||
peers.append(peer)
|
||||
}
|
||||
}
|
||||
return PeerParticipants(peers: peers)
|
||||
} else {
|
||||
return PeerParticipants(peers: [])
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged |> map { participants in
|
||||
return participants.peers
|
||||
}
|
||||
}
|
@ -65,5 +65,5 @@ public func unarchiveAutomaticallyArchivedPeer(account: Account, peerId: PeerId)
|
||||
}
|
||||
|> deliverOnMainQueue).start()
|
||||
|
||||
let _ = updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: nil).start()
|
||||
let _ = _internal_updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: nil).start()
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ public enum RequestChangeAccountPhoneNumberVerificationError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func requestChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
|
||||
func _internal_requestChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
|
||||
return account.network.request(Api.functions.account.sendChangePhoneCode(phoneNumber: phoneNumber, settings: .codeSettings(flags: 0)), automaticFloodWait: false)
|
||||
|> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
@ -64,7 +64,7 @@ public func requestChangeAccountPhoneNumberVerification(account: Account, phoneN
|
||||
}
|
||||
}
|
||||
|
||||
public func requestNextChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
|
||||
func _internal_requestNextChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
|
||||
return account.network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false)
|
||||
|> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
@ -96,7 +96,7 @@ public enum ChangeAccountPhoneNumberError {
|
||||
case limitExceeded
|
||||
}
|
||||
|
||||
public func requestChangeAccountPhoneNumber(account: Account, phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> Signal<Void, ChangeAccountPhoneNumberError> {
|
||||
func _internal_requestChangeAccountPhoneNumber(account: Account, phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> Signal<Void, ChangeAccountPhoneNumberError> {
|
||||
return account.network.request(Api.functions.account.changePhone(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, phoneCode: phoneCode), automaticFloodWait: false)
|
||||
|> mapError { error -> ChangeAccountPhoneNumberError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
@ -15,5 +15,25 @@ public extension TelegramEngine {
|
||||
public func resetAccountDueTermsOfService() -> Signal<Void, NoError> {
|
||||
return _internal_resetAccountDueTermsOfService(network: self.account.network)
|
||||
}
|
||||
|
||||
public func requestChangeAccountPhoneNumberVerification(phoneNumber: String) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
|
||||
return _internal_requestChangeAccountPhoneNumberVerification(account: self.account, phoneNumber: phoneNumber)
|
||||
}
|
||||
|
||||
public func requestNextChangeAccountPhoneNumberVerification(phoneNumber: String, phoneCodeHash: String) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
|
||||
return _internal_requestNextChangeAccountPhoneNumberVerification(account: self.account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash)
|
||||
}
|
||||
|
||||
public func requestChangeAccountPhoneNumber(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> Signal<Void, ChangeAccountPhoneNumberError> {
|
||||
return _internal_requestChangeAccountPhoneNumber(account: self.account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, phoneCode: phoneCode)
|
||||
}
|
||||
|
||||
public func updateAccountPeerName(firstName: String, lastName: String) -> Signal<Void, NoError> {
|
||||
return _internal_updateAccountPeerName(account: self.account, firstName: firstName, lastName: lastName)
|
||||
}
|
||||
|
||||
public func updateAbout(about: String?) -> Signal<Void, UpdateAboutError> {
|
||||
return _internal_updateAbout(account: self.account, about: about)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public func updateAccountPeerName(account: Account, firstName: String, lastName: String) -> Signal<Void, NoError> {
|
||||
func _internal_updateAccountPeerName(account: Account, firstName: String, lastName: String) -> Signal<Void, NoError> {
|
||||
return account.network.request(Api.functions.account.updateProfile(flags: (1 << 0) | (1 << 1), firstName: firstName, lastName: lastName, about: nil))
|
||||
|> map { result -> Api.User? in
|
||||
return result
|
||||
@ -30,7 +30,7 @@ public enum UpdateAboutError {
|
||||
}
|
||||
|
||||
|
||||
public func updateAbout(account: Account, about: String?) -> Signal<Void, UpdateAboutError> {
|
||||
func _internal_updateAbout(account: Account, about: String?) -> Signal<Void, UpdateAboutError> {
|
||||
return account.network.request(Api.functions.account.updateProfile(flags: about == nil ? 0 : (1 << 2), firstName: nil, lastName: nil, about: about))
|
||||
|> mapError { _ -> UpdateAboutError in
|
||||
return .generic
|
@ -18,7 +18,7 @@ public enum RequestCancelAccountResetDataError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func requestCancelAccountResetData(network: Network, hash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
|
||||
func _internal_requestCancelAccountResetData(network: Network, hash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
|
||||
return network.request(Api.functions.account.sendConfirmPhoneCode(hash: hash, settings: .codeSettings(flags: 0)), automaticFloodWait: false)
|
||||
|> mapError { error -> RequestCancelAccountResetDataError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
@ -39,7 +39,7 @@ public func requestCancelAccountResetData(network: Network, hash: String) -> Sig
|
||||
}
|
||||
}
|
||||
|
||||
public func requestNextCancelAccountResetOption(network: Network, phoneNumber: String, phoneCodeHash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
|
||||
func _internal_requestNextCancelAccountResetOption(network: Network, phoneNumber: String, phoneCodeHash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
|
||||
return network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false)
|
||||
|> mapError { error -> RequestCancelAccountResetDataError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
@ -67,7 +67,7 @@ public enum CancelAccountResetError {
|
||||
case limitExceeded
|
||||
}
|
||||
|
||||
public func requestCancelAccountReset(network: Network, phoneCodeHash: String, phoneCode: String) -> Signal<Never, CancelAccountResetError> {
|
||||
func _internal_requestCancelAccountReset(network: Network, phoneCodeHash: String, phoneCode: String) -> Signal<Never, CancelAccountResetError> {
|
||||
return network.request(Api.functions.account.confirmPhone(phoneCodeHash: phoneCodeHash, phoneCode: phoneCode))
|
||||
|> mapError { error -> CancelAccountResetError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
@ -24,8 +24,16 @@ public extension TelegramEngineUnauthorized {
|
||||
return _internal_updateTwoStepVerificationPassword(network: self.account.network, currentPassword: currentPassword, updatedPassword: updatedPassword)
|
||||
}
|
||||
|
||||
public func performPasswordRecovery(accountManager: AccountManager, code: String, syncContacts: Bool, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<Void, PasswordRecoveryError> {
|
||||
return _internal_performPasswordRecovery(accountManager: accountManager, account: self.account, code: code, syncContacts: syncContacts, updatedPassword: updatedPassword)
|
||||
public func requestTwoStepVerificationPasswordRecoveryCode() -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
|
||||
return _internal_requestTwoStepVerificationPasswordRecoveryCode(network: self.account.network)
|
||||
}
|
||||
|
||||
public func checkPasswordRecoveryCode(code: String) -> Signal<Never, PasswordRecoveryError> {
|
||||
return _internal_checkPasswordRecoveryCode(network: self.account.network, code: code)
|
||||
}
|
||||
|
||||
public func performPasswordRecovery(code: String, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<RecoveredAccountData, PasswordRecoveryError> {
|
||||
return _internal_performPasswordRecovery(network: self.account.network, code: code, updatedPassword: updatedPassword)
|
||||
}
|
||||
|
||||
public func resendTwoStepRecoveryEmail() -> Signal<Never, ResendTwoStepRecoveryEmailError> {
|
||||
@ -90,8 +98,8 @@ public extension TelegramEngine {
|
||||
return _internal_requestTwoStepVerificationPasswordRecoveryCode(network: self.account.network)
|
||||
}
|
||||
|
||||
public func recoverTwoStepVerificationPassword(code: String) -> Signal<Void, RecoverTwoStepVerificationPasswordError> {
|
||||
return _internal_recoverTwoStepVerificationPassword(network: self.account.network, code: code)
|
||||
public func performPasswordRecovery(code: String, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<RecoveredAccountData, PasswordRecoveryError> {
|
||||
return _internal_performPasswordRecovery(network: self.account.network, code: code, updatedPassword: updatedPassword)
|
||||
}
|
||||
|
||||
public func cachedTwoStepPasswordToken() -> Signal<TemporaryTwoStepPasswordToken?, NoError> {
|
||||
@ -106,6 +114,10 @@ public extension TelegramEngine {
|
||||
return _internal_requestTemporaryTwoStepPasswordToken(account: self.account, password: password, period: period, requiresBiometrics: requiresBiometrics)
|
||||
}
|
||||
|
||||
public func checkPasswordRecoveryCode(code: String) -> Signal<Never, PasswordRecoveryError> {
|
||||
return _internal_checkPasswordRecoveryCode(network: self.account.network, code: code)
|
||||
}
|
||||
|
||||
public func requestTwoStepPasswordReset() -> Signal<RequestTwoStepPasswordResetResult, NoError> {
|
||||
return _internal_requestTwoStepPasswordReset(network: self.account.network)
|
||||
}
|
||||
@ -113,6 +125,18 @@ public extension TelegramEngine {
|
||||
public func declineTwoStepPasswordReset() -> Signal<Never, NoError> {
|
||||
return _internal_declineTwoStepPasswordReset(network: self.account.network)
|
||||
}
|
||||
|
||||
public func requestCancelAccountResetData(hash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
|
||||
return _internal_requestCancelAccountResetData(network: self.account.network, hash: hash)
|
||||
}
|
||||
|
||||
public func requestNextCancelAccountResetOption(phoneNumber: String, phoneCodeHash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
|
||||
return _internal_requestNextCancelAccountResetOption(network: self.account.network, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash)
|
||||
}
|
||||
|
||||
public func requestCancelAccountReset(phoneCodeHash: String, phoneCode: String) -> Signal<Never, CancelAccountResetError> {
|
||||
return _internal_requestCancelAccountReset(network: self.account.network, phoneCodeHash: phoneCodeHash, phoneCode: phoneCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,12 +166,21 @@ public extension SomeTelegramEngine {
|
||||
}
|
||||
}
|
||||
|
||||
public func resendTwoStepRecoveryEmail() -> Signal<Never, ResendTwoStepRecoveryEmailError> {
|
||||
public func requestTwoStepVerificationPasswordRecoveryCode() -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
|
||||
switch self.engine {
|
||||
case let .authorized(engine):
|
||||
return engine.auth.resendTwoStepRecoveryEmail()
|
||||
return engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
|
||||
case let .unauthorized(engine):
|
||||
return engine.auth.resendTwoStepRecoveryEmail()
|
||||
return engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
|
||||
}
|
||||
}
|
||||
|
||||
public func checkPasswordRecoveryCode(code: String) -> Signal<Never, PasswordRecoveryError> {
|
||||
switch self.engine {
|
||||
case let .authorized(engine):
|
||||
return engine.auth.checkPasswordRecoveryCode(code: code)
|
||||
case let .unauthorized(engine):
|
||||
return engine.auth.checkPasswordRecoveryCode(code: code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -312,19 +312,26 @@ func _internal_updateTwoStepVerificationEmail(network: Network, currentPassword:
|
||||
|
||||
public enum RequestTwoStepVerificationPasswordRecoveryCodeError {
|
||||
case generic
|
||||
case limitExceeded
|
||||
}
|
||||
|
||||
func _internal_requestTwoStepVerificationPasswordRecoveryCode(network: Network) -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
|
||||
return network.request(Api.functions.auth.requestPasswordRecovery(), automaticFloodWait: false)
|
||||
|> mapError { _ -> RequestTwoStepVerificationPasswordRecoveryCodeError in
|
||||
|> mapError { error -> RequestTwoStepVerificationPasswordRecoveryCodeError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
} else if error.errorDescription.hasPrefix("PASSWORD_RECOVERY_NA") {
|
||||
return .generic
|
||||
} else {
|
||||
return .generic
|
||||
}
|
||||
|> map { result -> String in
|
||||
switch result {
|
||||
case let .passwordRecovery(emailPattern):
|
||||
return emailPattern
|
||||
}
|
||||
}
|
||||
|> map { result -> String in
|
||||
switch result {
|
||||
case let .passwordRecovery(emailPattern):
|
||||
return emailPattern
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum RecoverTwoStepVerificationPasswordError {
|
||||
@ -334,35 +341,6 @@ public enum RecoverTwoStepVerificationPasswordError {
|
||||
case invalidCode
|
||||
}
|
||||
|
||||
func _internal_recoverTwoStepVerificationPassword(network: Network, code: String) -> Signal<Void, RecoverTwoStepVerificationPasswordError> {
|
||||
return _internal_twoStepAuthData(network)
|
||||
|> mapError { _ -> RecoverTwoStepVerificationPasswordError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { authData -> Signal<Void, RecoverTwoStepVerificationPasswordError> in
|
||||
var flags: Int32 = (1 << 1)
|
||||
if authData.currentPasswordDerivation != nil {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
|
||||
return network.request(Api.functions.auth.recoverPassword(flags: 0, code: code, newSettings: nil), automaticFloodWait: false)
|
||||
|> mapError { error -> RecoverTwoStepVerificationPasswordError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT_") {
|
||||
return .limitExceeded
|
||||
} else if error.errorDescription == "PASSWORD_RECOVERY_EXPIRED" {
|
||||
return .codeExpired
|
||||
} else if error.errorDescription == "CODE_INVALID" {
|
||||
return .invalidCode
|
||||
} else {
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Void, RecoverTwoStepVerificationPasswordError> in
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_cachedTwoStepPasswordToken(postbox: Postbox) -> Signal<TemporaryTwoStepPasswordToken?, NoError> {
|
||||
return postbox.transaction { transaction -> TemporaryTwoStepPasswordToken? in
|
||||
let key = ValueBoxKey(length: 1)
|
||||
|
@ -105,7 +105,7 @@ func syncContactsOnce(network: Network, postbox: Postbox, accountPeerId: PeerId)
|
||||
return appliedUpdatedPeers
|
||||
}
|
||||
|
||||
public func deleteContactPeerInteractively(account: Account, peerId: PeerId) -> Signal<Never, NoError> {
|
||||
func _internal_deleteContactPeerInteractively(account: Account, peerId: PeerId) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
|
||||
return account.network.request(Api.functions.contacts.deleteContacts(id: [inputUser]))
|
||||
@ -133,7 +133,7 @@ public func deleteContactPeerInteractively(account: Account, peerId: PeerId) ->
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public func deleteAllContacts(account: Account) -> Signal<Never, NoError> {
|
||||
func _internal_deleteAllContacts(account: Account) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> [Api.InputUser] in
|
||||
return transaction.getContactPeerIds().compactMap(transaction.getPeer).compactMap({ apiInputUser($0) }).compactMap({ $0 })
|
||||
}
|
||||
@ -166,7 +166,7 @@ public func deleteAllContacts(account: Account) -> Signal<Never, NoError> {
|
||||
}
|
||||
}
|
||||
|
||||
public func resetSavedContacts(network: Network) -> Signal<Void, NoError> {
|
||||
func _internal_resetSavedContacts(network: Network) -> Signal<Void, NoError> {
|
||||
return network.request(Api.functions.contacts.resetSaved())
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
@ -4,8 +4,7 @@ import SwiftSignalKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public func importContact(account: Account, firstName: String, lastName: String, phoneNumber: String) -> Signal<PeerId?, NoError> {
|
||||
|
||||
func _internal_importContact(account: Account, firstName: String, lastName: String, phoneNumber: String) -> Signal<PeerId?, NoError> {
|
||||
let input = Api.InputContact.inputPhoneContact(clientId: 1, phone: phoneNumber, firstName: firstName, lastName: lastName)
|
||||
|
||||
return account.network.request(Api.functions.contacts.importContacts(contacts: [input]))
|
||||
@ -42,7 +41,7 @@ public enum AddContactError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func addContactInteractively(account: Account, peerId: PeerId, firstName: String, lastName: String, phoneNumber: String, addToPrivacyExceptions: Bool) -> Signal<Never, AddContactError> {
|
||||
func _internal_addContactInteractively(account: Account, peerId: PeerId, firstName: String, lastName: String, phoneNumber: String, addToPrivacyExceptions: Bool) -> Signal<Never, AddContactError> {
|
||||
return account.postbox.transaction { transaction -> (Api.InputUser, String)? in
|
||||
if let user = transaction.getPeer(peerId) as? TelegramUser, let inputUser = apiInputUser(user) {
|
||||
return (inputUser, user.phone == nil ? phoneNumber : "")
|
||||
@ -99,7 +98,7 @@ public enum AcceptAndShareContactError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func acceptAndShareContact(account: Account, peerId: PeerId) -> Signal<Never, AcceptAndShareContactError> {
|
||||
func _internal_acceptAndShareContact(account: Account, peerId: PeerId) -> Signal<Never, AcceptAndShareContactError> {
|
||||
return account.postbox.transaction { transaction -> Api.InputUser? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputUser)
|
||||
}
|
@ -48,7 +48,7 @@ enum TelegramDeviceContactImportIdentifier: Hashable, Comparable, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public func deviceContactsImportedByCount(postbox: Postbox, contacts: [(String, [DeviceContactNormalizedPhoneNumber])]) -> Signal<[String: Int32], NoError> {
|
||||
func _internal_deviceContactsImportedByCount(postbox: Postbox, contacts: [(String, [DeviceContactNormalizedPhoneNumber])]) -> Signal<[String: Int32], NoError> {
|
||||
return postbox.transaction { transaction -> [String: Int32] in
|
||||
var result: [String: Int32] = [:]
|
||||
for (id, numbers) in contacts {
|
@ -0,0 +1,44 @@
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
|
||||
public extension TelegramEngine {
|
||||
final class Contacts {
|
||||
private let account: Account
|
||||
|
||||
init(account: Account) {
|
||||
self.account = account
|
||||
}
|
||||
|
||||
public func deleteContactPeerInteractively(peerId: PeerId) -> Signal<Never, NoError> {
|
||||
return _internal_deleteContactPeerInteractively(account: self.account, peerId: peerId)
|
||||
}
|
||||
|
||||
public func deleteAllContacts() -> Signal<Never, NoError> {
|
||||
return _internal_deleteAllContacts(account: self.account)
|
||||
}
|
||||
|
||||
public func resetSavedContacts() -> Signal<Void, NoError> {
|
||||
return _internal_resetSavedContacts(network: self.account.network)
|
||||
}
|
||||
|
||||
public func updateContactName(peerId: PeerId, firstName: String, lastName: String) -> Signal<Void, UpdateContactNameError> {
|
||||
return _internal_updateContactName(account: self.account, peerId: peerId, firstName: firstName, lastName: lastName)
|
||||
}
|
||||
|
||||
public func deviceContactsImportedByCount(contacts: [(String, [DeviceContactNormalizedPhoneNumber])]) -> Signal<[String: Int32], NoError> {
|
||||
return _internal_deviceContactsImportedByCount(postbox: self.account.postbox, contacts: contacts)
|
||||
}
|
||||
|
||||
public func importContact(firstName: String, lastName: String, phoneNumber: String) -> Signal<PeerId?, NoError> {
|
||||
return _internal_importContact(account: self.account, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber)
|
||||
}
|
||||
|
||||
public func addContactInteractively(peerId: PeerId, firstName: String, lastName: String, phoneNumber: String, addToPrivacyExceptions: Bool) -> Signal<Never, AddContactError> {
|
||||
return _internal_addContactInteractively(account: self.account, peerId: peerId, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, addToPrivacyExceptions: addToPrivacyExceptions)
|
||||
}
|
||||
|
||||
public func acceptAndShareContact(peerId: PeerId) -> Signal<Never, AcceptAndShareContactError> {
|
||||
return _internal_acceptAndShareContact(account: self.account, peerId: peerId)
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ public enum UpdateContactNameError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func updateContactName(account: Account, peerId: PeerId, firstName: String, lastName: String) -> Signal<Void, UpdateContactNameError> {
|
||||
func _internal_updateContactName(account: Account, peerId: PeerId, firstName: String, lastName: String) -> Signal<Void, UpdateContactNameError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, UpdateContactNameError> in
|
||||
if let peer = transaction.getPeer(peerId) as? TelegramUser, let inputUser = apiInputUser(peer) {
|
||||
return account.network.request(Api.functions.contacts.addContact(flags: 0, id: inputUser, firstName: firstName, lastName: lastName, phone: ""))
|
@ -11,7 +11,7 @@ public enum GetMessagesStrategy {
|
||||
case cloud
|
||||
}
|
||||
|
||||
public func getMessagesLoadIfNecessary(_ messageIds: [MessageId], postbox: Postbox, network: Network, accountPeerId: PeerId, strategy: GetMessagesStrategy = .cloud) -> Signal <[Message], NoError> {
|
||||
func _internal_getMessagesLoadIfNecessary(_ messageIds: [MessageId], postbox: Postbox, network: Network, accountPeerId: PeerId, strategy: GetMessagesStrategy = .cloud) -> Signal <[Message], NoError> {
|
||||
let postboxSignal = postbox.transaction { transaction -> ([Message], Set<MessageId>, SimpleDictionary<PeerId, Peer>) in
|
||||
var ids = messageIds
|
||||
|
@ -6,7 +6,7 @@ import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public func markAllChatsAsRead(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
||||
func _internal_markAllChatsAsRead(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
||||
return network.request(Api.functions.messages.getDialogUnreadMarks())
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<[Api.DialogPeer]?, NoError> in
|
@ -453,7 +453,7 @@ public enum FetchChannelReplyThreadMessageError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageId, atMessageId: MessageId?) -> Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError> {
|
||||
func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: MessageId, atMessageId: MessageId?) -> Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError> {
|
||||
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
||||
}
|
@ -6,7 +6,7 @@ import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public func requestStartBot(account: Account, botPeerId: PeerId, payload: String?) -> Signal<Void, NoError> {
|
||||
func _internal_requestStartBot(account: Account, botPeerId: PeerId, payload: String?) -> Signal<Void, NoError> {
|
||||
if let payload = payload, !payload.isEmpty {
|
||||
return account.postbox.loadedPeerWithId(botPeerId)
|
||||
|> mapToSignal { botPeer -> Signal<Void, NoError> in
|
||||
@ -41,7 +41,7 @@ public enum StartBotInGroupResult {
|
||||
case channelParticipant(RenderedChannelParticipant)
|
||||
}
|
||||
|
||||
public func requestStartBotInGroup(account: Account, botPeerId: PeerId, groupPeerId: PeerId, payload: String?) -> Signal<StartBotInGroupResult, RequestStartBotInGroupError> {
|
||||
func _internal_requestStartBotInGroup(account: Account, botPeerId: PeerId, groupPeerId: PeerId, payload: String?) -> Signal<StartBotInGroupResult, RequestStartBotInGroupError> {
|
||||
return account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||
return (transaction.getPeer(botPeerId), transaction.getPeer(groupPeerId))
|
||||
}
|
@ -94,5 +94,33 @@ public extension TelegramEngine {
|
||||
public func forwardGameWithScore(messageId: MessageId, to peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return _internal_forwardGameWithScore(account: self.account, messageId: messageId, to: peerId)
|
||||
}
|
||||
|
||||
public func requestUpdatePinnedMessage(peerId: PeerId, update: PinnedMessageUpdate) -> Signal<Void, UpdatePinnedMessageError> {
|
||||
return _internal_requestUpdatePinnedMessage(account: self.account, peerId: peerId, update: update)
|
||||
}
|
||||
|
||||
public func requestUnpinAllMessages(peerId: PeerId) -> Signal<Never, UpdatePinnedMessageError> {
|
||||
return _internal_requestUnpinAllMessages(account: self.account, peerId: peerId)
|
||||
}
|
||||
|
||||
public func fetchChannelReplyThreadMessage(messageId: MessageId, atMessageId: MessageId?) -> Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError> {
|
||||
return _internal_fetchChannelReplyThreadMessage(account: self.account, messageId: messageId, atMessageId: atMessageId)
|
||||
}
|
||||
|
||||
public func requestStartBot(botPeerId: PeerId, payload: String?) -> Signal<Void, NoError> {
|
||||
return _internal_requestStartBot(account: self.account, botPeerId: botPeerId, payload: payload)
|
||||
}
|
||||
|
||||
public func requestStartBotInGroup(botPeerId: PeerId, groupPeerId: PeerId, payload: String?) -> Signal<StartBotInGroupResult, RequestStartBotInGroupError> {
|
||||
return _internal_requestStartBotInGroup(account: self.account, botPeerId: botPeerId, groupPeerId: groupPeerId, payload: payload)
|
||||
}
|
||||
|
||||
public func markAllChatsAsRead() -> Signal<Void, NoError> {
|
||||
return _internal_markAllChatsAsRead(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager)
|
||||
}
|
||||
|
||||
public func getMessagesLoadIfNecessary(_ messageIds: [MessageId], strategy: GetMessagesStrategy = .cloud) -> Signal <[Message], NoError> {
|
||||
return _internal_getMessagesLoadIfNecessary(messageIds, postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId, strategy: strategy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ public enum PinnedMessageUpdate {
|
||||
case clear(id: MessageId)
|
||||
}
|
||||
|
||||
public func requestUpdatePinnedMessage(account: Account, peerId: PeerId, update: PinnedMessageUpdate) -> Signal<Void, UpdatePinnedMessageError> {
|
||||
func _internal_requestUpdatePinnedMessage(account: Account, peerId: PeerId, update: PinnedMessageUpdate) -> Signal<Void, UpdatePinnedMessageError> {
|
||||
return account.postbox.transaction { transaction -> (Peer?, CachedPeerData?) in
|
||||
return (transaction.getPeer(peerId), transaction.getPeerCachedData(peerId: peerId))
|
||||
}
|
||||
@ -112,7 +112,7 @@ public func requestUpdatePinnedMessage(account: Account, peerId: PeerId, update:
|
||||
}
|
||||
}
|
||||
|
||||
public func requestUnpinAllMessages(account: Account, peerId: PeerId) -> Signal<Never, UpdatePinnedMessageError> {
|
||||
func _internal_requestUnpinAllMessages(account: Account, peerId: PeerId) -> Signal<Never, UpdatePinnedMessageError> {
|
||||
return account.postbox.transaction { transaction -> (Peer?, CachedPeerData?) in
|
||||
return (transaction.getPeer(peerId), transaction.getPeerCachedData(peerId: peerId))
|
||||
}
|
@ -4,7 +4,7 @@ import SwiftSignalKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public func togglePeerMuted(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
|
||||
func _internal_togglePeerMuted(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
var notificationPeerId = peerId
|
||||
@ -39,13 +39,13 @@ public func togglePeerMuted(account: Account, peerId: PeerId) -> Signal<Void, No
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePeerMuteSetting(account: Account, peerId: PeerId, muteInterval: Int32?) -> Signal<Void, NoError> {
|
||||
func _internal_updatePeerMuteSetting(account: Account, peerId: PeerId, muteInterval: Int32?) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
updatePeerMuteSetting(transaction: transaction, peerId: peerId, muteInterval: muteInterval)
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePeerMuteSetting(transaction: Transaction, peerId: PeerId, muteInterval: Int32?) {
|
||||
func updatePeerMuteSetting(transaction: Transaction, peerId: PeerId, muteInterval: Int32?) {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
var notificationPeerId = peerId
|
||||
if let associatedPeerId = peer.associatedPeerId {
|
||||
@ -82,13 +82,13 @@ public func updatePeerMuteSetting(transaction: Transaction, peerId: PeerId, mute
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePeerDisplayPreviewsSetting(account: Account, peerId: PeerId, displayPreviews: PeerNotificationDisplayPreviews) -> Signal<Void, NoError> {
|
||||
func _internal_updatePeerDisplayPreviewsSetting(account: Account, peerId: PeerId, displayPreviews: PeerNotificationDisplayPreviews) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
updatePeerDisplayPreviewsSetting(transaction: transaction, peerId: peerId, displayPreviews: displayPreviews)
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePeerDisplayPreviewsSetting(transaction: Transaction, peerId: PeerId, displayPreviews: PeerNotificationDisplayPreviews) {
|
||||
func updatePeerDisplayPreviewsSetting(transaction: Transaction, peerId: PeerId, displayPreviews: PeerNotificationDisplayPreviews) {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
var notificationPeerId = peerId
|
||||
if let associatedPeerId = peer.associatedPeerId {
|
||||
@ -108,13 +108,13 @@ public func updatePeerDisplayPreviewsSetting(transaction: Transaction, peerId: P
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePeerNotificationSoundInteractive(account: Account, peerId: PeerId, sound: PeerMessageSound) -> Signal<Void, NoError> {
|
||||
func _internal_updatePeerNotificationSoundInteractive(account: Account, peerId: PeerId, sound: PeerMessageSound) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
updatePeerNotificationSoundInteractive(transaction: transaction, peerId: peerId, sound: sound)
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePeerNotificationSoundInteractive(transaction: Transaction, peerId: PeerId, sound: PeerMessageSound) {
|
||||
func updatePeerNotificationSoundInteractive(transaction: Transaction, peerId: PeerId, sound: PeerMessageSound) {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
var notificationPeerId = peerId
|
||||
if let associatedPeerId = peer.associatedPeerId {
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user