Merge branch 'master' into features/64-bit

This commit is contained in:
Ali 2021-07-07 02:08:56 +04:00
commit ee8ec6ed30
155 changed files with 5571 additions and 6557 deletions

View File

@ -138,7 +138,7 @@ private func rootPathForBasePath(_ appGroupPath: String) -> String {
self.audioConverter = CustomAudioConverter(asbd: asbd) self.audioConverter = CustomAudioConverter(asbd: asbd)
} }
if let audioConverter = self.audioConverter { 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) self.screencastBufferClientContext?.writeAudioData(data: data)
} }
} }

View File

@ -6532,7 +6532,7 @@ Sorry for the inconvenience.";
"Settings.TryEnterPassword" = "Not sure, let me try"; "Settings.TryEnterPassword" = "Not sure, let me try";
"TwoFactorSetup.PasswordRecovery.Title" = "Create New Password"; "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.PlaceholderPassword" = "New Password";
"TwoFactorSetup.PasswordRecovery.PlaceholderConfirmPassword" = "Re-enter New Password"; "TwoFactorSetup.PasswordRecovery.PlaceholderConfirmPassword" = "Re-enter New Password";
"TwoFactorSetup.PasswordRecovery.Action" = "Continue"; "TwoFactorSetup.PasswordRecovery.Action" = "Continue";
@ -6549,3 +6549,8 @@ Sorry for the inconvenience.";
"TwoStepAuth.CancelResetTitle" = "Cancel Reset"; "TwoStepAuth.CancelResetTitle" = "Cancel Reset";
"TwoStepAuth.ResetAction" = "Reset Password"; "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.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";

View File

@ -686,7 +686,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
controller.dismiss() controller.dismiss()
} }
switch update { switch update {
case .noPassword, .awaitingEmailConfirmation: case .noPassword, .awaitingEmailConfirmation, .pendingPasswordReset:
break break
case .passwordSet: case .passwordSet:
var updatedToken = webToken var updatedToken = webToken
@ -754,7 +754,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
controller.dismiss() controller.dismiss()
} }
switch update { switch update {
case .noPassword, .awaitingEmailConfirmation: case .noPassword, .awaitingEmailConfirmation, .pendingPasswordReset:
break break
case .passwordSet: case .passwordSet:
var updatedToken = webToken var updatedToken = webToken
@ -810,7 +810,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
controller.dismiss() controller.dismiss()
} }
switch update { switch update {
case .noPassword, .awaitingEmailConfirmation: case .noPassword, .awaitingEmailConfirmation, .pendingPasswordReset:
break break
case .passwordSet: case .passwordSet:
var updatedToken = token var updatedToken = token

View File

@ -322,7 +322,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
isMuted = true 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 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: { |> deliverOnMainQueue).start(completed: {
f(.default) f(.default)
}) })
@ -332,7 +332,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
if case .search = source { if case .search = source {
if let _ = peer as? TelegramChannel { 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 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)? var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in let progressSignal = Signal<Never, NoError> { subscriber in
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }

View File

@ -688,7 +688,7 @@ public final class ChatListNode: ListView {
return return
} }
strongSelf.setCurrentRemovingPeerId(peerId) strongSelf.setCurrentRemovingPeerId(peerId)
let _ = (togglePeerMuted(account: context.account, peerId: peerId) let _ = (context.engine.peers.togglePeerMuted(peerId: peerId)
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
self?.updateState { state in self?.updateState { state in
var state = state var state = state

View File

@ -365,7 +365,7 @@ final class InviteContactsControllerNode: ASDisplayNode {
return DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phoneNumber.value)) 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 |> map { counts -> [(DeviceContactStableId, DeviceContactBasicData, Int32)]? in
var result: [(DeviceContactStableId, DeviceContactBasicData, Int32)] = [] var result: [(DeviceContactStableId, DeviceContactBasicData, Int32)] = []
var contactValues: [DeviceContactStableId: DeviceContactBasicData] = [:] var contactValues: [DeviceContactStableId: DeviceContactBasicData] = [:]

View File

@ -310,7 +310,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
@objc func joinPressed() { @objc func joinPressed() {
if let peer = self.peer, case .notJoined = self.joinState { if let peer = self.peer, case .notJoined = self.joinState {
self.updateJoinState(.inProgress) 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 let strongSelf = self {
if case .inProgress = strongSelf.joinState { if case .inProgress = strongSelf.joinState {
strongSelf.updateJoinState(.notJoined) strongSelf.updateJoinState(.notJoined)

View File

@ -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",
],
)

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
}
}

View File

@ -423,28 +423,31 @@ static NSData *encryptRSALegacy(id<EncryptionProvider> encryptionProvider, NSDat
return encryptedData; 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) { static NSData *encryptRSAModernPadding(id<EncryptionProvider> encryptionProvider, NSData *pqInnerData, NSString *publicKey) {
NSMutableData *dataWithPadding = [[NSMutableData alloc] init]; NSMutableData *dataWithPadding = [[NSMutableData alloc] init];
[dataWithPadding appendData:pqInnerData]; [dataWithPadding appendData:pqInnerData];
if (dataWithPadding.length > 176) { if (dataWithPadding.length > 192) {
return nil; return nil;
} }
if (dataWithPadding.length != 176) { if (dataWithPadding.length != 192) {
int originalLength = (int)dataWithPadding.length; int originalLength = (int)dataWithPadding.length;
int numPaddingBytes = 176 - originalLength; int numPaddingBytes = 192 - originalLength;
[dataWithPadding setLength:176]; [dataWithPadding setLength:192];
int randomResult = SecRandomCopyBytes(kSecRandomDefault, numPaddingBytes, ((uint8_t *)dataWithPadding.mutableBytes) + originalLength); int randomResult = SecRandomCopyBytes(kSecRandomDefault, numPaddingBytes, ((uint8_t *)dataWithPadding.mutableBytes) + originalLength);
if (randomResult != errSecSuccess) { if (randomResult != errSecSuccess) {
return nil; return nil;
} }
} }
NSMutableData *dataWithHash = [[NSMutableData alloc] init]; NSData *dataWithPaddingReversed = reversedBytes(dataWithPadding);
[dataWithHash appendData:dataWithPadding];
[dataWithHash appendData:MTSha256(dataWithPadding)];
if (dataWithHash.length != 208) {
return nil;
}
while (true) { while (true) {
int randomResult = 0; int randomResult = 0;
@ -454,33 +457,36 @@ static NSData *encryptRSAModernPadding(id<EncryptionProvider> encryptionProvider
return nil; return nil;
} }
NSMutableData *tempIv = [[NSMutableData alloc] initWithLength:16]; NSMutableData *tempKeyAndDataWithPadding = [[NSMutableData alloc] init];
randomResult = SecRandomCopyBytes(kSecRandomDefault, tempIv.length, tempIv.mutableBytes); [tempKeyAndDataWithPadding appendData:tempKey];
if (randomResult != errSecSuccess) { [tempKeyAndDataWithPadding appendData:dataWithPadding];
NSMutableData *dataWithHash = [[NSMutableData alloc] init];
[dataWithHash appendData:dataWithPaddingReversed];
[dataWithHash appendData:MTSha256(tempKeyAndDataWithPadding)];
if (dataWithHash.length != 224) {
return nil; 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) { if (aesEncrypted == nil) {
return nil; return nil;
} }
NSData *shaAesEncrypted = MTSha256(aesEncrypted);
NSMutableData *tempIvPlusAesEncrypted = [[NSMutableData alloc] init];
[tempIvPlusAesEncrypted appendData:tempIv];
[tempIvPlusAesEncrypted appendData:aesEncrypted];
NSData *shaTempIvPlusAesEncrypted = MTSha256(tempIvPlusAesEncrypted);
NSMutableData *tempKeyXor = [[NSMutableData alloc] initWithLength:tempKey.length]; NSMutableData *tempKeyXor = [[NSMutableData alloc] initWithLength:tempKey.length];
if (tempKeyXor.length != shaTempIvPlusAesEncrypted.length) { if (tempKeyXor.length != shaAesEncrypted.length) {
return nil; return nil;
} }
for (NSUInteger i = 0; i < tempKey.length; i++) { 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]; NSMutableData *keyAesEncrypted = [[NSMutableData alloc] init];
[keyAesEncrypted appendData:tempKeyXor]; [keyAesEncrypted appendData:tempKeyXor];
[keyAesEncrypted appendData:tempIv];
[keyAesEncrypted appendData:aesEncrypted]; [keyAesEncrypted appendData:aesEncrypted];
if (keyAesEncrypted.length != 256) { if (keyAesEncrypted.length != 256) {
return nil; return nil;

View File

@ -717,8 +717,16 @@
request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + 2.0); 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) { if (request.errorContext == nil) {
request.errorContext = [[MTRequestErrorContext alloc] init]; request.errorContext = [[MTRequestErrorContext alloc] init];
} }

View File

@ -558,7 +558,7 @@ public final class SecureIdAuthController: ViewController, StandalonePresentable
return return
} }
switch update { switch update {
case .noPassword: case .noPassword, .pendingPasswordReset:
strongSelf.updateState(animated: false, { state in strongSelf.updateState(animated: false, { state in
var state = state var state = state
if let verificationState = state.verificationState, case .noChallenge = verificationState { if let verificationState = state.verificationState, case .noChallenge = verificationState {

View File

@ -179,7 +179,7 @@ public func resetPasswordController(context: AccountContext, emailPattern: Strin
state.checking = true state.checking = true
return state 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 |> deliverOnMainQueue).start(error: { error in
updateState { state in updateState { state in
var state = state var state = state
@ -190,7 +190,7 @@ public func resetPasswordController(context: AccountContext, emailPattern: Strin
switch error { switch error {
case .invalidCode: case .invalidCode:
text = presentationData.strings.TwoStepAuth_RecoveryCodeInvalid text = presentationData.strings.TwoStepAuth_RecoveryCodeInvalid
case .codeExpired: case .expired:
text = presentationData.strings.TwoStepAuth_RecoveryCodeExpired text = presentationData.strings.TwoStepAuth_RecoveryCodeExpired
case .limitExceeded: case .limitExceeded:
text = presentationData.strings.TwoStepAuth_FloodError text = presentationData.strings.TwoStepAuth_FloodError

View File

@ -138,6 +138,7 @@ public enum SetupTwoStepVerificationStateUpdate {
case noPassword case noPassword
case awaitingEmailConfirmation(password: String, pattern: String, codeLength: Int32?) case awaitingEmailConfirmation(password: String, pattern: String, codeLength: Int32?)
case passwordSet(password: String?, hasRecoveryEmail: Bool, hasSecureValues: Bool) case passwordSet(password: String?, hasRecoveryEmail: Bool, hasSecureValues: Bool)
case pendingPasswordReset
} }
final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode { final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {

View File

@ -14,18 +14,27 @@ import AnimatedStickerNode
public enum TwoFactorDataInputMode { public enum TwoFactorDataInputMode {
public struct Recovery { public struct Recovery {
public var code: String public enum Mode {
public var syncContacts: Bool case notAuthorized(syncContacts: Bool)
public var account: UnauthorizedAccount 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.code = code
self.syncContacts = syncContacts self.mode = mode
self.account = account
} }
} }
public enum PasswordRecoveryEmailMode {
case notAuthorized(syncContacts: Bool)
case authorized
}
case password case password
case passwordRecoveryEmail(emailPattern: String, mode: PasswordRecoveryEmailMode)
case passwordRecovery(Recovery) case passwordRecovery(Recovery)
case emailAddress(password: String, hint: String) case emailAddress(password: String, hint: String)
case updateEmailAddress(password: String) case updateEmailAddress(password: String)
@ -39,8 +48,11 @@ public final class TwoFactorDataInputScreen: ViewController {
private var presentationData: PresentationData private var presentationData: PresentationData
private let mode: TwoFactorDataInputMode private let mode: TwoFactorDataInputMode
private let stateUpdated: (SetupTwoStepVerificationStateUpdate) -> Void 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.sharedContext = sharedContext
self.engine = engine self.engine = engine
self.mode = mode 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))) 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.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationPresentation = .modalInLargeLayout self.navigationPresentation = presentation
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false self.navigationBar?.intrinsicCanTransitionInline = false
@ -64,6 +76,10 @@ public final class TwoFactorDataInputScreen: ViewController {
required init(coder aDecoder: NSCoder) { required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
deinit {
self.actionDisposable.dispose()
}
@objc private func backPressed() { @objc private func backPressed() {
self.dismiss() self.dismiss()
@ -101,8 +117,57 @@ public final class TwoFactorDataInputScreen: ViewController {
} }
return true 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) 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): case let .passwordRecovery(recovery):
let values = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText let values = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText
if values.count != 2 { if values.count != 2 {
@ -120,17 +185,7 @@ public final class TwoFactorDataInputScreen: ViewController {
guard let navigationController = strongSelf.navigationController as? NavigationController else { guard let navigationController = strongSelf.navigationController as? NavigationController else {
return return
} }
var controllers = navigationController.viewControllers.filter { controller in 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)
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)
case let .emailAddress(password, hint): case let .emailAddress(password, hint):
guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else { guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
return return
@ -163,7 +218,7 @@ public final class TwoFactorDataInputScreen: ViewController {
} }
return true 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) navigationController.setViewControllers(controllers, animated: true)
} else { } else {
guard let navigationController = strongSelf.navigationController as? NavigationController else { guard let navigationController = strongSelf.navigationController as? NavigationController else {
@ -337,7 +392,7 @@ public final class TwoFactorDataInputScreen: ViewController {
if let recovery = recovery { if let recovery = recovery {
strongSelf.performRecovery(recovery: recovery, password: password, hint: value) strongSelf.performRecovery(recovery: recovery, password: password, hint: value)
} else { } 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 }, skipAction: { [weak self] in
@ -405,7 +460,7 @@ public final class TwoFactorDataInputScreen: ViewController {
if let recovery = recovery { if let recovery = recovery {
strongSelf.performRecovery(recovery: recovery, password: password, hint: "") strongSelf.performRecovery(recovery: recovery, password: password, hint: "")
} else { } 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): 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: [ 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 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) navigationController.setViewControllers(controllers, animated: true)
} else { } 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: default:
break break
} }
@ -450,29 +532,57 @@ public final class TwoFactorDataInputScreen: ViewController {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
switch strongSelf.mode {
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil)) case .passwordRecoveryEmail:
strongSelf.present(statusController, in: .window(.root)) let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
strongSelf.present(statusController, in: .window(.root))
let _ = (strongSelf.engine.auth.resendTwoStepRecoveryEmail() let _ = (strongSelf.engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
|> deliverOnMainQueue).start(error: { [weak statusController] error in |> deliverOnMainQueue).start(error: { [weak statusController] error in
statusController?.dismiss() statusController?.dismiss()
guard let strongSelf = self else { 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 return
} }
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
let text: String strongSelf.present(statusController, in: .window(.root))
switch error {
case .flood: let _ = (engine.auth.resendTwoStepRecoveryEmail()
text = strongSelf.presentationData.strings.TwoStepAuth_FloodError |> deliverOnMainQueue).start(error: { [weak statusController] error in
case .generic: statusController?.dismiss()
text = strongSelf.presentationData.strings.Login_UnknownError
} guard let strongSelf = self else {
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return
}, completed: { [weak statusController] in }
statusController?.dismiss()
}) 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() self.displayNodeDidLoad()
@ -485,42 +595,93 @@ public final class TwoFactorDataInputScreen: ViewController {
} }
private func performRecovery(recovery: TwoFactorDataInputMode.Recovery, password: String, hint: String) { 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)) let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: nil))
self.present(statusController, in: .window(.root)) 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)) switch self.engine {
|> deliverOnMainQueue).start(error: { [weak self, weak statusController] error in case let .unauthorized(engine):
statusController?.dismiss() var syncContacts = false
switch recovery.mode {
guard let strongSelf = self else { case let .notAuthorized(syncContactsValue):
return 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 guard let strongSelf = self else {
switch error { return
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)) if password.isEmpty {
}, completed: { [weak self, weak statusController] in strongSelf.stateUpdated(.noPassword)
statusController?.dismiss() } else {
strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: false))
}
guard let strongSelf = self else { (strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .recoveryDone(recoveredAccountData: recoveredAccountData, syncContacts: syncContacts)), animated: true)
return }, 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 { switch mode {
case .password, .passwordRecovery, .emailAddress, .updateEmailAddress: case .password, .passwordRecovery, .emailAddress, .updateEmailAddress:
self.monkeyNode = ManagedMonkeyAnimationNode() self.monkeyNode = ManagedMonkeyAnimationNode()
case .emailConfirmation: case .emailConfirmation, .passwordRecoveryEmail:
if let path = getAppBundle().path(forResource: "TwoFactorSetupMail", ofType: "tgs") { if let path = getAppBundle().path(forResource: "TwoFactorSetupMail", ofType: "tgs") {
let animatedStickerNode = AnimatedStickerNode() let animatedStickerNode = AnimatedStickerNode()
animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 272, height: 272, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) 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) 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, _): case let .emailConfirmation(_, emailPattern, _):
title = presentationData.strings.TwoFactorSetup_EmailVerification_Title title = presentationData.strings.TwoFactorSetup_EmailVerification_Title
let (rawText, ranges) = presentationData.strings.TwoFactorSetup_EmailVerification_Text(emailPattern) 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.changeEmailActionTitleNode.attributedText = NSAttributedString(string: changeEmailActionText, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemAccentColor)
self.changeEmailActionButtonNode = HighlightTrackingButtonNode() self.changeEmailActionButtonNode = HighlightTrackingButtonNode()
self.changeEmailActionButtonNode.isHidden = changeEmailActionText.isEmpty self.changeEmailActionButtonNode.isHidden = changeEmailActionText.isEmpty
self.changeEmailActionButtonNode.isHidden = changeEmailActionText.isEmpty
self.resendCodeActionTitleNode = ImmediateTextNode() self.resendCodeActionTitleNode = ImmediateTextNode()
self.resendCodeActionTitleNode.isUserInteractionEnabled = false self.resendCodeActionTitleNode.isUserInteractionEnabled = false
@ -1088,7 +1275,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
return return
} }
switch strongSelf.mode { switch strongSelf.mode {
case .password: case .password, .passwordRecovery:
if strongSelf.inputNodes[1].isFocused { if strongSelf.inputNodes[1].isFocused {
let maxWidth = strongSelf.inputNodes[1].bounds.width let maxWidth = strongSelf.inputNodes[1].bounds.width
@ -1129,7 +1316,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
break break
} }
} }
focusUpdated = { [weak self] node, _ in focusUpdated = { node, _ in
DispatchQueue.main.async { DispatchQueue.main.async {
updateAnimations() updateAnimations()
} }
@ -1156,6 +1343,18 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
if let codeLength = codeLength, text.count == codeLength { if let codeLength = codeLength, text.count == codeLength {
action() 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: case .passwordHint:
let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty }) let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty })
strongSelf.buttonNode.isHidden = !hasText strongSelf.buttonNode.isHidden = !hasText
@ -1171,7 +1370,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
return return
} }
switch strongSelf.mode { switch strongSelf.mode {
case .password: case .password, .passwordRecovery:
textHidden = !textHidden textHidden = !textHidden
for node in strongSelf.inputNodes { for node in strongSelf.inputNodes {
node.updateTextHidden(textHidden) node.updateTextHidden(textHidden)
@ -1320,7 +1519,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
let buttonWidth = contentAreaSize.width - buttonSideInset * 2.0 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)) 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) transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
@ -1338,7 +1537,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS
let changeEmailActionButtonFrame: CGRect let changeEmailActionButtonFrame: CGRect
let resendCodeActionFrame: CGRect let resendCodeActionFrame: CGRect
let resendCodeActionButtonFrame: 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)) 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) 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)) resendCodeActionButtonFrame = CGRect(origin: CGPoint(x: buttonFrame.minX, y: buttonFrame.maxY), size: CGSize(width: buttonFrame.width, height: buttonFrame.height))

View File

@ -15,6 +15,7 @@ import TelegramCore
public enum TwoFactorAuthSplashMode { public enum TwoFactorAuthSplashMode {
case intro case intro
case done case done
case recoveryDone(recoveredAccountData: RecoveredAccountData, syncContacts: Bool)
} }
public final class TwoFactorAuthSplashScreen: ViewController { public final class TwoFactorAuthSplashScreen: ViewController {
@ -23,7 +24,7 @@ public final class TwoFactorAuthSplashScreen: ViewController {
private var presentationData: PresentationData private var presentationData: PresentationData
private var mode: TwoFactorAuthSplashMode 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.sharedContext = sharedContext
self.engine = engine self.engine = engine
self.mode = mode 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) 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))) 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.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationPresentation = .modalInLargeLayout
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false self.navigationBar?.intrinsicCanTransitionInline = false
@ -58,12 +60,22 @@ public final class TwoFactorAuthSplashScreen: ViewController {
switch strongSelf.mode { switch strongSelf.mode {
case .intro: case .intro:
strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .password, stateUpdated: { _ in strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .password, stateUpdated: { _ in
})) }, presentation: strongSelf.navigationPresentation))
case .done: case .done:
guard let navigationController = strongSelf.navigationController as? NavigationController else { guard let navigationController = strongSelf.navigationController as? NavigationController else {
return return
} }
navigationController.filterController(strongSelf, animated: true) 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) text = NSAttributedString(string: self.presentationData.strings.TwoFactorSetup_Done_Text, font: textFont, textColor: textColor)
buttonText = self.presentationData.strings.TwoFactorSetup_Done_Action 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") { 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.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, mode: .direct(cachePathPrefix: nil))
self.animationSize = CGSize(width: 124.0, height: 124.0) self.animationSize = CGSize(width: 124.0, height: 124.0)

View File

@ -36,7 +36,7 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: PeerId) -> Si
if let firstEntry = entries.first { if let firstEntry = entries.first {
return context.account.postbox.loadedPeerWithId(peerId) return context.account.postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer -> Signal<(Bool, [AvatarGalleryEntry])?, NoError>in |> 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) |> map(Optional.init)
} }
} else { } 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) return initialAvatarGalleryEntries(account: account, peer: peer)
|> map { entries -> [AvatarGalleryEntry] in |> map { entries -> [AvatarGalleryEntry] in
return entries ?? [] return entries ?? []
@ -218,7 +218,7 @@ public func fetchedAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<
|> mapToSignal { initialEntries in |> mapToSignal { initialEntries in
return .single(initialEntries) return .single(initialEntries)
|> then( |> then(
requestPeerPhotos(postbox: account.postbox, network: account.network, peerId: peer.id) engine.peers.requestPeerPhotos(peerId: peer.id)
|> map { photos -> [AvatarGalleryEntry] in |> map { photos -> [AvatarGalleryEntry] in
var result: [AvatarGalleryEntry] = [] var result: [AvatarGalleryEntry] = []
if photos.isEmpty { 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] let initialEntries = [firstEntry]
return Signal<(Bool, [AvatarGalleryEntry]), NoError>.single((false, initialEntries)) return Signal<(Bool, [AvatarGalleryEntry]), NoError>.single((false, initialEntries))
|> then( |> then(
requestPeerPhotos(postbox: account.postbox, network: account.network, peerId: peer.id) engine.peers.requestPeerPhotos(peerId: peer.id)
|> map { photos -> (Bool, [AvatarGalleryEntry]) in |> map { photos -> (Bool, [AvatarGalleryEntry]) in
var result: [AvatarGalleryEntry] = [] var result: [AvatarGalleryEntry] = []
let initialEntries = [firstEntry] let initialEntries = [firstEntry]
@ -403,7 +403,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
if let remoteEntries = remoteEntries { if let remoteEntries = remoteEntries {
remoteEntriesSignal = remoteEntries.get() remoteEntriesSignal = remoteEntries.get()
} else { } 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) let initialSignal = initialAvatarGalleryEntries(account: context.account, peer: peer)

View File

@ -889,7 +889,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
return 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 let controller = channelOwnershipTransferController(context: context, peer: peer, member: member, initialError: error, present: { c, a in
presentControllerImpl?(c, a) presentControllerImpl?(c, a)
}, completion: { upgradedPeerId in }, completion: { upgradedPeerId in

View File

@ -665,7 +665,7 @@ public func channelAdminsController(context: AccountContext, peerId initialPeerI
|> deliverOnMainQueue).start(next: { peerId in |> deliverOnMainQueue).start(next: { peerId in
if peerId.namespace == Namespaces.Peer.CloudChannel { if peerId.namespace == Namespaces.Peer.CloudChannel {
var didReportLoadCompleted = false 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 { if case .loading = membersState.loadingState, membersState.list.isEmpty {
adminsPromise.set(.single(nil)) adminsPromise.set(.single(nil))
} else { } else {

View File

@ -506,7 +506,7 @@ public func channelBannedMemberController(context: AccountContext, peerId: PeerI
state.updating = true state.updating = true
return state 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 |> deliverOnMainQueue).start(error: { _ in
}, completed: { }, completed: {
@ -667,7 +667,7 @@ public func channelBannedMemberController(context: AccountContext, peerId: PeerI
guard let upgradedPeerId = upgradedPeerId else { guard let upgradedPeerId = upgradedPeerId else {
return .single(nil) 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 |> mapToSignal { _ -> Signal<PeerId?, NoError> in
return .complete() return .complete()
} }
@ -700,7 +700,7 @@ public func channelBannedMemberController(context: AccountContext, peerId: PeerI
} }
})) }))
} else { } 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 |> deliverOnMainQueue).start(error: { _ in
}, completed: { }, completed: {

View File

@ -324,7 +324,7 @@ public func channelBlacklistController(context: AccountContext, peerId: PeerId)
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let progress = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) let progress = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
presentControllerImpl?(progress, 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 |> deliverOnMainQueue).start(error: { [weak progress] _ in
progress?.dismiss() progress?.dismiss()
dismissController?() dismissController?()
@ -343,7 +343,7 @@ public func channelBlacklistController(context: AccountContext, peerId: PeerId)
return $0.withUpdatedRemovingPeerId(memberId) 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 { updateState {
return $0.withUpdatedRemovingPeerId(nil) return $0.withUpdatedRemovingPeerId(nil)
} }
@ -388,7 +388,7 @@ public func channelBlacklistController(context: AccountContext, peerId: PeerId)
updateState { updateState {
return $0.withUpdatedRemovingPeerId(memberId) 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 |> ignoreValues
|> then( |> then(
context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: peerId, memberId: memberId) 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) 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 { updateState {
return $0.withUpdatedRemovingPeerId(nil) 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 { if case .loading(true) = listState.loadingState, listState.list.isEmpty {
blacklistPromise.set(.single(nil)) blacklistPromise.set(.single(nil))
} else { } else {

View File

@ -230,7 +230,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
let groupPeers = Promise<[Peer]?>() let groupPeers = Promise<[Peer]?>()
groupPeers.set(.single(nil) groupPeers.set(.single(nil)
|> then( |> then(
availableGroupsForChannelDiscussion(postbox: context.account.postbox, network: context.account.network) context.engine.peers.availableGroupsForChannelDiscussion()
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<[Peer]?, NoError> in |> `catch` { _ -> Signal<[Peer]?, NoError> in
return .single(nil) return .single(nil)
@ -260,7 +260,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
} }
let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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 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)? var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in let progressSignal = Signal<Never, NoError> { subscriber in
let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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) |> castError(ChannelDiscussionGroupError.self)
|> switchToLatest |> switchToLatest
} }
} else { } 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)? var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in let progressSignal = Signal<Never, NoError> { subscriber in
@ -413,7 +413,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
case .groupHistoryIsCurrentlyPrivate: case .groupHistoryIsCurrentlyPrivate:
let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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: { 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 |> mapError { _ -> ChannelDiscussionGroupError in
return .generic return .generic
} }
@ -421,7 +421,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
return .complete() return .complete()
} }
|> then( |> 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)? var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in let progressSignal = Signal<Never, NoError> { subscriber in
@ -502,7 +502,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
return 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)? var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in let progressSignal = Signal<Never, NoError> { subscriber in
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }

View File

@ -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 = 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 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)) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}, updateSettings: { value in }, 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)) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}) })
@ -918,7 +918,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
}, aboutLinkAction: { action, itemLink in }, aboutLinkAction: { action, itemLink in
aboutLinkActionImpl?(action, itemLink) aboutLinkActionImpl?(action, itemLink)
}, toggleSignatures: { enabled in }, 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? var wasEditing: Bool?

View File

@ -443,7 +443,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
return $0.withUpdatedRemovingPeerId(memberId) 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: { |> deliverOnMainQueue).start(completed: {
updateState { updateState {
return $0.withUpdatedRemovingPeerId(nil) return $0.withUpdatedRemovingPeerId(nil)
@ -462,7 +462,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
let peerView = context.account.viewTracker.peerView(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)) peersPromise.set(.single(state.list))
}) })
actionsDisposable.add(disposable) actionsDisposable.add(disposable)

View File

@ -231,13 +231,13 @@ private func categorySignal(context: AccountContext, peerId: PeerId, category: G
} }
switch category { switch category {
case .admins: 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: 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: 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: 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 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 { |> afterDisposed {
Queue.mainQueue().async { Queue.mainQueue().async {
updateState { state in 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 |> deliverOnMainQueue
|> afterDisposed { |> afterDisposed {
updateState { state in updateState { state in
@ -608,7 +608,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
switch mode { switch mode {
case .searchMembers, .banAndPromoteActions: case .searchMembers, .banAndPromoteActions:
foundGroupMembers = Signal { subscriber in 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 { if case .ready = state.loadingState {
subscriber.putNext(state.list) subscriber.putNext(state.list)
} }
@ -619,11 +619,11 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
foundMembers = .single([]) foundMembers = .single([])
case .inviteActions: case .inviteActions:
foundGroupMembers = .single([]) 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 ?? [] } |> map { $0 ?? [] }
case .searchAdmins: case .searchAdmins:
foundGroupMembers = Signal { subscriber in 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 { if case .ready = state.loadingState {
subscriber.putNext(state.list) subscriber.putNext(state.list)
} }
@ -633,7 +633,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
foundMembers = .single([]) foundMembers = .single([])
case .searchBanned: case .searchBanned:
foundGroupMembers = Signal { subscriber in 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 { if case .ready = state.loadingState {
subscriber.putNext(state.list) subscriber.putNext(state.list)
subscriber.putCompletion() subscriber.putCompletion()
@ -643,7 +643,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
} }
|> runOn(Queue.mainQueue()) |> runOn(Queue.mainQueue())
foundMembers = Signal { subscriber in 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 { if case .ready = state.loadingState {
subscriber.putNext(state.list.filter({ participant in subscriber.putNext(state.list.filter({ participant in
return participant.peer.id != context.account.peerId return participant.peer.id != context.account.peerId
@ -655,7 +655,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|> runOn(Queue.mainQueue()) |> runOn(Queue.mainQueue())
case .searchKicked: case .searchKicked:
foundGroupMembers = Signal { subscriber in 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 { if case .ready = state.loadingState {
subscriber.putNext(state.list) subscriber.putNext(state.list)
subscriber.putCompletion() subscriber.putCompletion()

View File

@ -419,7 +419,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
} else { } else {
let membersState = Promise<ChannelMemberListState>() 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)) membersState.set(.single(state))
}) })

View File

@ -455,7 +455,7 @@ private func commitChannelOwnershipTransferController(context: AccountContext, p
let signal: Signal<PeerId?, ChannelOwnershipTransferError> let signal: Signal<PeerId?, ChannelOwnershipTransferError>
if let peer = peer as? TelegramChannel { 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() return .complete()
} }
|> then(.single(nil)) |> then(.single(nil))
@ -475,7 +475,7 @@ private func commitChannelOwnershipTransferController(context: AccountContext, p
guard let upgradedPeerId = upgradedPeerId else { guard let upgradedPeerId = upgradedPeerId else {
return .fail(.generic) 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() return .complete()
} }
|> then(.single(upgradedPeerId)) |> then(.single(upgradedPeerId))

View File

@ -536,7 +536,7 @@ public func channelPermissionsController(context: AccountContext, peerId origina
peersPromise.set(.single((peerId, nil))) peersPromise.set(.single((peerId, nil)))
} else { } else {
var loadCompletedCalled = false 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 { if case .loading(true) = state.loadingState, !updated {
peersPromise.set(.single((peerId, nil))) peersPromise.set(.single((peerId, nil)))
} else { } else {
@ -594,7 +594,7 @@ public func channelPermissionsController(context: AccountContext, peerId origina
} }
let state = stateValue.with { $0 } let state = stateValue.with { $0 }
if let modifiedRightsFlags = state.modifiedRightsFlags { 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()) |> deliverOnMainQueue).start())
} }
} else if let group = view.peers[view.peerId] as? TelegramGroup, let _ = view.cachedData as? CachedGroupData { } 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 } let state = stateValue.with { $0 }
if let modifiedRightsFlags = state.modifiedRightsFlags { 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()) |> deliverOnMainQueue).start())
} }
} }
@ -679,7 +679,7 @@ public func channelPermissionsController(context: AccountContext, peerId origina
return state 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 |> deliverOnMainQueue).start(error: { _ in
updateState { state in updateState { state in
var state = state var state = state

View File

@ -1104,7 +1104,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
switch subject { switch subject {
case let .create(peer, _, share, shareViaException, _): case let .create(peer, _, share, shareViaException, _):
if share, filteredPhoneNumbers.count <= 1, let peer = peer { 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 |> 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) presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}, completed: { }, completed: {
@ -1138,7 +1138,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
switch subject { switch subject {
case let .create(peer, _, share, shareViaException, _): case let .create(peer, _, share, shareViaException, _):
if share, let peer = peer { 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 |> mapToSignal { _ -> Signal<(DeviceContactStableId, DeviceContactExtendedData, Peer?)?, AddContactError> in
return .complete() return .complete()
} }
@ -1153,7 +1153,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
break 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) |> castError(AddContactError.self)
|> mapToSignal { peerId -> Signal<(DeviceContactStableId, DeviceContactExtendedData, Peer?)?, AddContactError> in |> mapToSignal { peerId -> Signal<(DeviceContactStableId, DeviceContactExtendedData, Peer?)?, AddContactError> in
if let peerId = peerId { if let peerId = peerId {

View File

@ -163,7 +163,7 @@ public func groupPreHistorySetupController(context: AccountContext, peerId: Peer
if peerId.namespace == Namespaces.Peer.CloudGroup { if peerId.namespace == Namespaces.Peer.CloudGroup {
let signal = context.engine.peers.convertGroupToSupergroup(peerId: peerId) let signal = context.engine.peers.convertGroupToSupergroup(peerId: peerId)
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, ConvertGroupToSupergroupError> in |> 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 |> `catch` { _ -> Signal<Void, NoError> in
return .complete() return .complete()
} }
@ -190,7 +190,7 @@ public func groupPreHistorySetupController(context: AccountContext, peerId: Peer
} }
})) }))
} else { } 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: { |> deliverOnMainQueue).start(completed: {
dismissImpl?() dismissImpl?()
})) }))

View File

@ -448,7 +448,7 @@ public func groupStickerPackSetupController(context: AccountContext, peerId: Pee
state.isSaving = true state.isSaving = true
return state 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 |> deliverOnMainQueue).start(error: { _ in
updateState { state in updateState { state in
var state = state var state = state

View File

@ -18,7 +18,7 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
|> mapToSignal { cachedData -> Signal<([Peer], Bool), NoError> in |> mapToSignal { cachedData -> Signal<([Peer], Bool), NoError> in
if case .peer = chatLocation, let cachedData = cachedData, let memberCount = cachedData.participantsSummary.memberCount, memberCount <= 64 { if case .peer = chatLocation, let cachedData = cachedData, let memberCount = cachedData.participantsSummary.memberCount, memberCount <= 64 {
return Signal { subscriber in 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 { if case .ready = state.loadingState {
let normalizedQuery = query.lowercased() let normalizedQuery = query.lowercased()
subscriber.putNext((state.list.compactMap { participant -> Peer? in subscriber.putNext((state.list.compactMap { participant -> Peer? in
@ -54,7 +54,7 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
return Signal { subscriber in return Signal { subscriber in
switch chatLocation { switch chatLocation {
case let .peer(peerId): 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 { if case .ready = state.loadingState {
subscriber.putNext((state.list.compactMap { participant in subscriber.putNext((state.list.compactMap { participant in
if participant.peer.isDeleted { if participant.peer.isDeleted {
@ -69,7 +69,7 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
disposable.dispose() disposable.dispose()
} }
case let .replyThread(replyThreadMessage): 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 { if case .ready = state.loadingState {
subscriber.putNext((state.list.compactMap { participant in subscriber.putNext((state.list.compactMap { participant in
if participant.peer.isDeleted { if participant.peer.isDeleted {
@ -117,6 +117,6 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
} }
} }
} else { } 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)
} }
} }

View File

@ -230,7 +230,7 @@ func changePhoneNumberCodeController(context: AccountContext, phoneNumber: Strin
|> take(1) |> take(1)
|> mapToSignal { _ -> Signal<Void, NoError> in |> mapToSignal { _ -> Signal<Void, NoError> in
return Signal { subscriber 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)) currentDataPromise?.set(.single(next))
}, error: { error in }, error: { error in
@ -254,7 +254,7 @@ func changePhoneNumberCodeController(context: AccountContext, phoneNumber: Strin
} }
} }
if let code = code { 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 { updateState {
return $0.withUpdatedChecking(false) return $0.withUpdatedChecking(false)
} }

View File

@ -123,7 +123,7 @@ final class ChangePhoneNumberController: ViewController, MFMailComposeViewContro
} }
if !number.isEmpty { if !number.isEmpty {
self.inProgress = true 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 { if let strongSelf = self {
strongSelf.inProgress = false strongSelf.inProgress = false
(strongSelf.navigationController as? NavigationController)?.pushViewController(changePhoneNumberCodeController(context: strongSelf.context, phoneNumber: strongSelf.controllerNode.currentNumber, codeData: next)) (strongSelf.navigationController as? NavigationController)?.pushViewController(changePhoneNumberCodeController(context: strongSelf.context, phoneNumber: strongSelf.controllerNode.currentNumber, codeData: next))

View File

@ -780,16 +780,16 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode {
let presentationData = context.sharedContext.currentPresentationData.modify {$0} let presentationData = context.sharedContext.currentPresentationData.modify {$0}
let updatePeerSound: (PeerId, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in 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 let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal<Void, NoError> = { peerId, muteInterval in
return updatePeerMuteSetting(account: context.account, peerId: peerId, muteInterval: muteInterval) |> deliverOnMainQueue return context.engine.peers.updatePeerMuteSetting(peerId: peerId, muteInterval: muteInterval) |> deliverOnMainQueue
} }
let updatePeerDisplayPreviews:(PeerId, PeerNotificationDisplayPreviews) -> Signal<Void, NoError> = { let updatePeerDisplayPreviews:(PeerId, PeerNotificationDisplayPreviews) -> Signal<Void, NoError> = {
peerId, displayPreviews in 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 self.backgroundColor = presentationData.theme.list.blocksBackgroundColor
@ -842,13 +842,11 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode {
updateNotificationsView({}) updateNotificationsView({})
}) })
}, removePeerFromExceptions: { }, removePeerFromExceptions: {
let _ = (context.account.postbox.transaction { transaction -> Peer? in let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: [peerId])
updatePeerMuteSetting(transaction: transaction, peerId: peerId, muteInterval: nil) |> map { _ -> Peer? in }
updatePeerDisplayPreviewsSetting(transaction: transaction, peerId: peerId, displayPreviews: .default) |> then(context.account.postbox.transaction { transaction -> Peer? in
updatePeerNotificationSoundInteractive(transaction: transaction, peerId: peerId, sound: .default)
return transaction.getPeer(peerId) return transaction.getPeer(peerId)
} })).start(next: { peer in
|> deliverOnMainQueue).start(next: { peer in
guard let peer = peer else { guard let peer = peer else {
return return
} }
@ -917,11 +915,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode {
updateState { value in updateState { value in
return value.withUpdatedPeerMuteInterval(peer, nil).withUpdatedPeerSound(peer, .default).withUpdatedPeerDisplayPreviews(peer, .default) return value.withUpdatedPeerMuteInterval(peer, nil).withUpdatedPeerSound(peer, .default).withUpdatedPeerDisplayPreviews(peer, .default)
} }
_ = (context.account.postbox.transaction { transaction in let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: [peer.id])
updatePeerNotificationSoundInteractive(transaction: transaction, peerId: peer.id, sound: .default)
updatePeerMuteSetting(transaction: transaction, peerId: peer.id, muteInterval: nil)
updatePeerDisplayPreviewsSetting(transaction: transaction, peerId: peer.id, displayPreviews: .default)
}
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
updateNotificationsView({}) updateNotificationsView({})
}) })
@ -953,13 +947,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode {
} }
return state return state
} }
let _ = (context.account.postbox.transaction { transaction -> Void in let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: values.map(\.peer.id))
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)
}
}
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
updateNotificationsView({}) updateNotificationsView({})
}) })

View File

@ -216,7 +216,7 @@ public func confirmPhoneNumberCodeController(context: AccountContext, phoneNumbe
|> take(1) |> take(1)
|> mapToSignal { _ -> Signal<Void, NoError> in |> mapToSignal { _ -> Signal<Void, NoError> in
return Signal { subscriber 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)) currentDataPromise?.set(.single(next))
}, error: { error in }, error: { error in
@ -242,7 +242,7 @@ public func confirmPhoneNumberCodeController(context: AccountContext, phoneNumbe
} }
} }
if let code = code { 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 |> deliverOnMainQueue).start(error: { error in
updateState { state in updateState { state in
var state = state var state = state

View File

@ -426,7 +426,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController {
}) })
}).start() }).start()
actionsDisposable.add((deleteAllContacts(account: context.account) actionsDisposable.add((context.engine.contacts.deleteAllContacts()
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
updateState { state in updateState { state in
var state = state var state = state

View File

@ -506,11 +506,11 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
actionsDisposable.add(updateAutoArchiveDisposable) actionsDisposable.add(updateAutoArchiveDisposable)
let privacySettingsPromise = Promise<AccountPrivacySettings?>() 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 blockedPeersContext = blockedPeersContext ?? BlockedPeersContext(account: context.account)
let activeSessionsContext = activeSessionsContext ?? ActiveSessionsContext(account: context.account) let activeSessionsContext = activeSessionsContext ?? context.engine.privacy.activeSessions()
let webSessionsContext = webSessionsContext ?? WebSessionsContext(account: context.account) let webSessionsContext = webSessionsContext ?? context.engine.privacy.webSessions()
let blockedPeersState = Promise<BlockedPeersContextState>() let blockedPeersState = Promise<BlockedPeersContextState>()
blockedPeersState.set(blockedPeersContext.state) blockedPeersState.set(blockedPeersContext.state)
@ -779,7 +779,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
return .complete() return .complete()
} }
updateAutoArchiveDisposable.set((updateAccountAutoArchiveChats(account: context.account, value: archiveValue) updateAutoArchiveDisposable.set((context.engine.privacy.updateAccountAutoArchiveChats(value: archiveValue)
|> mapToSignal { _ -> Signal<Void, NoError> in } |> mapToSignal { _ -> Signal<Void, NoError> in }
|> then(applyTimeout) |> then(applyTimeout)
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
@ -817,7 +817,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
} }
return .complete() return .complete()
} }
updateAccountTimeoutDisposable.set((updateAccountRemovalTimeout(account: context.account, timeout: timeout) updateAccountTimeoutDisposable.set((context.engine.privacy.updateAccountRemovalTimeout(timeout: timeout)
|> then(applyTimeout) |> then(applyTimeout)
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
updateState { state in updateState { state in

View File

@ -1038,14 +1038,14 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective
type = .phoneNumber 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() var updateCallP2PSettingsSignal: Signal<Void, NoError> = Signal.complete()
if let callP2PSettings = callP2PSettings { 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() var updatePhoneDiscoverySignal: Signal<Void, NoError> = Signal.complete()
if let phoneDiscoveryEnabled = phoneDiscoveryEnabled { if let phoneDiscoveryEnabled = phoneDiscoveryEnabled {
updatePhoneDiscoverySignal = updatePhoneNumberDiscovery(account: context.account, value: phoneDiscoveryEnabled) updatePhoneDiscoverySignal = context.engine.privacy.updatePhoneNumberDiscovery(value: phoneDiscoveryEnabled)
} }
let _ = (combineLatest(updateSettingsSignal, updateCallP2PSettingsSignal, updatePhoneDiscoverySignal) let _ = (combineLatest(updateSettingsSignal, updateCallP2PSettingsSignal, updatePhoneDiscoverySignal)

View File

@ -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
}

View File

@ -293,6 +293,7 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
var replaceControllerImpl: ((ViewController, Bool) -> Void)? var replaceControllerImpl: ((ViewController, Bool) -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
var dismissImpl: (() -> Void)? var dismissImpl: (() -> Void)?
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
@ -507,28 +508,28 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
state.checking = false state.checking = false
return state return state
} }
var completionImpl: ((Bool) -> Void)? var stateUpdated: ((SetupTwoStepVerificationStateUpdate) -> Void)?
let controller = resetPasswordController(context: context, emailPattern: emailPattern, completion: { result in let controller = TwoFactorDataInputScreen(sharedContext: context.sharedContext, engine: .authorized(context.engine), mode: .passwordRecoveryEmail(emailPattern: emailPattern, mode: .authorized), stateUpdated: { state in
completionImpl?(result) stateUpdated?(state)
}) })
completionImpl = { [weak controller] result in stateUpdated = { [weak controller] state in
if !result { controller?.view.endEditing(true)
controller?.dismiss()
switch state {
case .noPassword, .awaitingEmailConfirmation, .passwordSet:
controller?.dismiss()
dismissImpl?()
case .pendingPasswordReset:
dataPromise.set(context.engine.auth.twoStepVerificationConfiguration() dataPromise.set(context.engine.auth.twoStepVerificationConfiguration()
|> map { TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationAccessConfiguration(configuration: $0, password: nil)) |> 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 }, error: { _ in
updateState { state in updateState { state in
var state = state var state = state
@ -592,6 +593,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
case .notSet: case .notSet:
let controller = SetupTwoStepVerificationController(context: context, initialState: .createPassword, stateUpdated: { update, shouldDismiss, controller in let controller = SetupTwoStepVerificationController(context: context, initialState: .createPassword, stateUpdated: { update, shouldDismiss, controller in
switch update { switch update {
case .pendingPasswordReset:
break
case .noPassword: case .noPassword:
dataPromise.set(.single(.access(configuration: .notSet(pendingEmail: nil)))) dataPromise.set(.single(.access(configuration: .notSet(pendingEmail: nil))))
case let .awaitingEmailConfirmation(password, pattern, codeLength): case let .awaitingEmailConfirmation(password, pattern, codeLength):
@ -623,6 +626,8 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
case let .manage(password, hasRecovery, pendingEmail, hasSecureValues): 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 let controller = SetupTwoStepVerificationController(context: context, initialState: .updatePassword(current: password, hasRecoveryEmail: hasRecovery, hasSecureValues: hasSecureValues), stateUpdated: { update, shouldDismiss, controller in
switch update { switch update {
case .pendingPasswordReset:
break
case .noPassword: case .noPassword:
dataPromise.set(.single(.access(configuration: .notSet(pendingEmail: nil)))) dataPromise.set(.single(.access(configuration: .notSet(pendingEmail: nil))))
case .awaitingEmailConfirmation: case .awaitingEmailConfirmation:
@ -712,10 +717,10 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
case .access: case .access:
break break
case let .manage(password, emailSet, _, hasSecureValues): 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 let controller = SetupTwoStepVerificationController(context: context, initialState: .addEmail(hadRecoveryEmail: emailSet, hasSecureValues: hasSecureValues, password: password), stateUpdated: { update, shouldDismiss, controller in
switch update { switch update {
case .pendingPasswordReset:
break
case .noPassword: case .noPassword:
assertionFailure() assertionFailure()
break 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 let controller = SetupTwoStepVerificationController(context: context, initialState: .confirmEmail(password: password, hasSecureValues: hasSecureValues, pattern: pendingEmail.pattern, codeLength: pendingEmail.codeLength), stateUpdated: { update, shouldDismiss, controller in
switch update { switch update {
case .pendingPasswordReset:
break
case .noPassword: case .noPassword:
assertionFailure() assertionFailure()
break break
@ -941,6 +948,9 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
controller.present(c, in: .window(.root), with: p) controller.present(c, in: .window(.root), with: p)
} }
} }
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
dismissImpl = { [weak controller] in dismissImpl = { [weak controller] in
controller?.dismiss() controller?.dismiss()
} }

View File

@ -430,7 +430,7 @@ private func privacySearchableItems(context: AccountContext, privacySettings: Ac
if let privacySettings = privacySettings { if let privacySettings = privacySettings {
privacySignal = .single(privacySettings) privacySignal = .single(privacySettings)
} else { } else {
privacySignal = requestAccountPrivacySettings(account: context.account) privacySignal = context.engine.privacy.requestAccountPrivacySettings()
} }
let callsSignal: Signal<(VoiceCallSettings, VoipConfiguration)?, NoError> let callsSignal: Signal<(VoiceCallSettings, VoipConfiguration)?, NoError>
if case .voiceCalls = kind { 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))) 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 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 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 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) presentPrivacySettings(context, present, .accountTimeout)

View File

@ -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) 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 animation.fillMode = .both
if !fastOut { if !fastOut {
animation.beginTime = CACurrentMediaTime() + 0.1 animation.beginTime = contentNode.layer.convertTime(CACurrentMediaTime(), from: nil) + 0.1
} }
contentNode.layer.add(animation, forKey: "opacity") contentNode.layer.add(animation, forKey: "opacity")
} }

View File

@ -584,7 +584,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1160215659] = { return Api.InputMessage.parse_inputMessageReplyTo($0) } dict[-1160215659] = { return Api.InputMessage.parse_inputMessageReplyTo($0) }
dict[-2037963464] = { return Api.InputMessage.parse_inputMessagePinned($0) } dict[-2037963464] = { return Api.InputMessage.parse_inputMessagePinned($0) }
dict[-1392895362] = { return Api.InputMessage.parse_inputMessageCallbackQuery($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[-58224696] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($0) }
dict[-1237848657] = { return Api.StatsDateRangeDays.parse_statsDateRangeDays($0) } dict[-1237848657] = { return Api.StatsDateRangeDays.parse_statsDateRangeDays($0) }
dict[-275956116] = { return Api.messages.AffectedFoundMessages.parse_affectedFoundMessages($0) } dict[-275956116] = { return Api.messages.AffectedFoundMessages.parse_affectedFoundMessages($0) }

View File

@ -14974,13 +14974,13 @@ public extension Api {
} }
public enum GroupCallParticipantVideo: TypeConstructorDescription { 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) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .groupCallParticipantVideo(let flags, let endpoint, let sourceGroups): case .groupCallParticipantVideo(let flags, let endpoint, let sourceGroups, let audioSource):
if boxed { if boxed {
buffer.appendInt32(2028213859) buffer.appendInt32(1735736008)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(endpoint, buffer: buffer, boxed: false) serializeString(endpoint, buffer: buffer, boxed: false)
@ -14989,14 +14989,15 @@ public extension Api {
for item in sourceGroups { for item in sourceGroups {
item.serialize(buffer, true) item.serialize(buffer, true)
} }
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(audioSource!, buffer: buffer, boxed: false)}
break break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .groupCallParticipantVideo(let flags, let endpoint, let sourceGroups): case .groupCallParticipantVideo(let flags, let endpoint, let sourceGroups, let audioSource):
return ("groupCallParticipantVideo", [("flags", flags), ("endpoint", endpoint), ("sourceGroups", sourceGroups)]) return ("groupCallParticipantVideo", [("flags", flags), ("endpoint", endpoint), ("sourceGroups", sourceGroups), ("audioSource", audioSource)])
} }
} }
@ -15009,11 +15010,14 @@ public extension Api {
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipantVideoSourceGroup.self) _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 _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
if _c1 && _c2 && _c3 { let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
return Api.GroupCallParticipantVideo.groupCallParticipantVideo(flags: _1!, endpoint: _2!, sourceGroups: _3!) if _c1 && _c2 && _c3 && _c4 {
return Api.GroupCallParticipantVideo.groupCallParticipantVideo(flags: _1!, endpoint: _2!, sourceGroups: _3!, audioSource: _4)
} }
else { else {
return nil return nil

View File

@ -415,12 +415,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private var screencastBufferServerContext: IpcGroupCallBufferAppContext? private var screencastBufferServerContext: IpcGroupCallBufferAppContext?
private var screencastCapturer: OngoingCallVideoCapturer? private var screencastCapturer: OngoingCallVideoCapturer?
//private var screencastIpcContext: IpcGroupCallAppContext?
private var ssrcMapping: [UInt32: PeerId] = [:] private var ssrcMapping: [UInt32: PeerId] = [:]
private var requestedSsrcs = Set<UInt32>()
private var summaryInfoState = Promise<SummaryInfoState?>(nil) private var summaryInfoState = Promise<SummaryInfoState?>(nil)
private var summaryParticipantsState = Promise<SummaryParticipantsState?>(nil) private var summaryParticipantsState = Promise<SummaryParticipantsState?>(nil)
@ -887,7 +883,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.genericCallContext?.addExternalAudioData(data: data) strongSelf.screencastCallContext?.addExternalAudioData(data: data)
}) })
self.screencastStateDisposable = (screencastBufferServerContext.isActive self.screencastStateDisposable = (screencastBufferServerContext.isActive
|> distinctUntilChanged |> distinctUntilChanged
@ -1140,7 +1136,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
let rawAdminIds: Signal<Set<PeerId>, NoError> let rawAdminIds: Signal<Set<PeerId>, NoError>
if peerId.namespace == Namespaces.Peer.CloudChannel { if peerId.namespace == Namespaces.Peer.CloudChannel {
rawAdminIds = Signal { subscriber in 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>() var peerIds = Set<PeerId>()
for item in list.list { for item in list.list {
if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) { if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) {
@ -1429,7 +1425,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
let peerId = strongSelf.peerId let peerId = strongSelf.peerId
if strongSelf.peerId.namespace == Namespaces.Peer.CloudChannel { if strongSelf.peerId.namespace == Namespaces.Peer.CloudChannel {
peerAdminIds = Signal { subscriber in 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>() var peerIds = Set<PeerId>()
for item in list.list { for item in list.list {
if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) { 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> let rawAdminIds: Signal<Set<PeerId>, NoError>
if peerId.namespace == Namespaces.Peer.CloudChannel { if peerId.namespace == Namespaces.Peer.CloudChannel {
rawAdminIds = Signal { subscriber in 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>() var peerIds = Set<PeerId>()
for item in list.list { for item in list.list {
if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) { if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) {
@ -2050,6 +2046,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
videoDescription: nil 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
))
}
}
} }
} }

View File

@ -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 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 let strongSelf = self, let bio = bio {
if peer.id.namespace == Namespaces.Peer.CloudUser { 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 |> `catch` { _ -> Signal<Void, NoError> in
return .complete() return .complete()
}).start() }).start()
@ -1575,7 +1575,7 @@ public final class VoiceChatController: ViewController {
Queue.mainQueue().after(0.1) { 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 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 { 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 }) strongSelf.presentUndoOverlay(content: .info(text: strongSelf.presentationData.strings.VoiceChat_EditNameSuccess), action: { _ in return false })
} }
@ -1711,7 +1711,7 @@ public final class VoiceChatController: ViewController {
return 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.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 }) 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 })

View File

@ -4,7 +4,7 @@ import SwiftSignalKit
import SyncCore 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 return postbox.transaction { transaction -> Int32 in
if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.remoteStorageConfiguration) as? RemoteStorageConfiguration { if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.remoteStorageConfiguration) as? RemoteStorageConfiguration {
return entry.webDocumentsHostDatacenterId return entry.webDocumentsHostDatacenterId

View File

@ -342,30 +342,6 @@ public enum PasswordRecoveryOption {
case email(pattern: String) 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 { public enum PasswordRecoveryError {
case invalidCode case invalidCode
case limitExceeded case limitExceeded
@ -373,7 +349,7 @@ public enum PasswordRecoveryError {
case generic 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) return network.request(Api.functions.auth.checkRecoveryPassword(code: code), automaticFloodWait: false)
|> mapError { error -> PasswordRecoveryError in |> mapError { error -> PasswordRecoveryError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") { 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> { public final class RecoveredAccountData {
return _internal_twoStepAuthData(account.network) 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 |> mapError { _ -> PasswordRecoveryError in
return .generic return .generic
} }
|> mapToSignal { authData -> Signal<Void, PasswordRecoveryError> in |> mapToSignal { authData -> Signal<RecoveredAccountData, PasswordRecoveryError> in
let newSettings: Api.account.PasswordInputSettings? let newSettings: Api.account.PasswordInputSettings?
switch updatedPassword { switch updatedPassword {
case .none: case .none:
@ -405,7 +409,7 @@ func _internal_performPasswordRecovery(accountManager: AccountManager, account:
flags |= (1 << 1) 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) return .fail(.invalidCode)
} }
@ -416,7 +420,7 @@ func _internal_performPasswordRecovery(accountManager: AccountManager, account:
if newSettings != nil { if newSettings != nil {
flags |= 1 << 0 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 |> mapError { error -> PasswordRecoveryError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") { if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded return .limitExceeded
@ -426,24 +430,8 @@ func _internal_performPasswordRecovery(accountManager: AccountManager, account:
return .invalidCode return .invalidCode
} }
} }
|> mapToSignal { result -> Signal<Void, PasswordRecoveryError> in |> mapToSignal { result -> Signal<RecoveredAccountData, PasswordRecoveryError> in
return account.postbox.transaction { transaction -> Signal<Void, NoError> in return .single(RecoveredAccountData(authorization: result))
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 }
} }
} }
} }

View File

@ -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
}

View File

@ -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
}

View File

@ -461,7 +461,7 @@ final class MediaReferenceRevalidationContext {
func peerAvatars(postbox: Postbox, network: Network, background: Bool, peer: PeerReference) -> Signal<[TelegramPeerPhoto], RevalidateMediaReferenceError> { 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 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 |> mapError { _ -> RevalidateMediaReferenceError in
return .generic return .generic
}).start(next: { value in }).start(next: { value in

View File

@ -855,6 +855,7 @@ public final class GroupCallParticipantsContext {
public var endpointId: String public var endpointId: String
public var ssrcGroups: [SsrcGroup] public var ssrcGroups: [SsrcGroup]
public var audioSsrc: UInt32?
public var isPaused: Bool public var isPaused: Bool
} }
@ -1527,6 +1528,9 @@ public final class GroupCallParticipantsContext {
if let ssrc = participant.ssrc { if let ssrc = participant.ssrc {
existingSsrcs.insert(ssrc) existingSsrcs.insert(ssrc)
} }
if let presentationDescription = participant.presentationDescription, let presentationAudioSsrc = presentationDescription.audioSsrc {
existingSsrcs.insert(presentationAudioSsrc)
}
} }
for ssrc in ssrcs { for ssrc in ssrcs {
@ -2488,7 +2492,7 @@ extension GroupCallParticipantsContext.Participant {
private extension GroupCallParticipantsContext.Participant.VideoDescription { private extension GroupCallParticipantsContext.Participant.VideoDescription {
init(_ apiVideo: Api.GroupCallParticipantVideo) { init(_ apiVideo: Api.GroupCallParticipantVideo) {
switch apiVideo { switch apiVideo {
case let .groupCallParticipantVideo(flags, endpoint, sourceGroups): case let .groupCallParticipantVideo(flags, endpoint, sourceGroups, audioSource):
var parsedSsrcGroups: [SsrcGroup] = [] var parsedSsrcGroups: [SsrcGroup] = []
for group in sourceGroups { for group in sourceGroups {
switch group { switch group {
@ -2497,7 +2501,7 @@ private extension GroupCallParticipantsContext.Participant.VideoDescription {
} }
} }
let isPaused = (flags & (1 << 0)) != 0 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)
} }
} }
} }

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -65,5 +65,5 @@ public func unarchiveAutomaticallyArchivedPeer(account: Account, peerId: PeerId)
} }
|> deliverOnMainQueue).start() |> deliverOnMainQueue).start()
let _ = updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: nil).start() let _ = _internal_updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: nil).start()
} }

View File

@ -37,7 +37,7 @@ public enum RequestChangeAccountPhoneNumberVerificationError {
case generic 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) return account.network.request(Api.functions.account.sendChangePhoneCode(phoneNumber: phoneNumber, settings: .codeSettings(flags: 0)), automaticFloodWait: false)
|> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in |> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") { 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) return account.network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false)
|> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in |> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") { if error.errorDescription.hasPrefix("FLOOD_WAIT") {
@ -96,7 +96,7 @@ public enum ChangeAccountPhoneNumberError {
case limitExceeded 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) return account.network.request(Api.functions.account.changePhone(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, phoneCode: phoneCode), automaticFloodWait: false)
|> mapError { error -> ChangeAccountPhoneNumberError in |> mapError { error -> ChangeAccountPhoneNumberError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") { if error.errorDescription.hasPrefix("FLOOD_WAIT") {

View File

@ -15,5 +15,25 @@ public extension TelegramEngine {
public func resetAccountDueTermsOfService() -> Signal<Void, NoError> { public func resetAccountDueTermsOfService() -> Signal<Void, NoError> {
return _internal_resetAccountDueTermsOfService(network: self.account.network) 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)
}
} }
} }

View File

@ -6,7 +6,7 @@ import MtProtoKit
import SyncCore 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)) return account.network.request(Api.functions.account.updateProfile(flags: (1 << 0) | (1 << 1), firstName: firstName, lastName: lastName, about: nil))
|> map { result -> Api.User? in |> map { result -> Api.User? in
return result 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)) return account.network.request(Api.functions.account.updateProfile(flags: about == nil ? 0 : (1 << 2), firstName: nil, lastName: nil, about: about))
|> mapError { _ -> UpdateAboutError in |> mapError { _ -> UpdateAboutError in
return .generic return .generic

View File

@ -18,7 +18,7 @@ public enum RequestCancelAccountResetDataError {
case generic 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) return network.request(Api.functions.account.sendConfirmPhoneCode(hash: hash, settings: .codeSettings(flags: 0)), automaticFloodWait: false)
|> mapError { error -> RequestCancelAccountResetDataError in |> mapError { error -> RequestCancelAccountResetDataError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") { 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) return network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false)
|> mapError { error -> RequestCancelAccountResetDataError in |> mapError { error -> RequestCancelAccountResetDataError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") { if error.errorDescription.hasPrefix("FLOOD_WAIT") {
@ -67,7 +67,7 @@ public enum CancelAccountResetError {
case limitExceeded 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)) return network.request(Api.functions.account.confirmPhone(phoneCodeHash: phoneCodeHash, phoneCode: phoneCode))
|> mapError { error -> CancelAccountResetError in |> mapError { error -> CancelAccountResetError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") { if error.errorDescription.hasPrefix("FLOOD_WAIT") {

View File

@ -24,8 +24,16 @@ public extension TelegramEngineUnauthorized {
return _internal_updateTwoStepVerificationPassword(network: self.account.network, currentPassword: currentPassword, updatedPassword: updatedPassword) 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> { public func requestTwoStepVerificationPasswordRecoveryCode() -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
return _internal_performPasswordRecovery(accountManager: accountManager, account: self.account, code: code, syncContacts: syncContacts, updatedPassword: updatedPassword) 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> { public func resendTwoStepRecoveryEmail() -> Signal<Never, ResendTwoStepRecoveryEmailError> {
@ -90,8 +98,8 @@ public extension TelegramEngine {
return _internal_requestTwoStepVerificationPasswordRecoveryCode(network: self.account.network) return _internal_requestTwoStepVerificationPasswordRecoveryCode(network: self.account.network)
} }
public func recoverTwoStepVerificationPassword(code: String) -> Signal<Void, RecoverTwoStepVerificationPasswordError> { public func performPasswordRecovery(code: String, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<RecoveredAccountData, PasswordRecoveryError> {
return _internal_recoverTwoStepVerificationPassword(network: self.account.network, code: code) return _internal_performPasswordRecovery(network: self.account.network, code: code, updatedPassword: updatedPassword)
} }
public func cachedTwoStepPasswordToken() -> Signal<TemporaryTwoStepPasswordToken?, NoError> { 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) 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> { public func requestTwoStepPasswordReset() -> Signal<RequestTwoStepPasswordResetResult, NoError> {
return _internal_requestTwoStepPasswordReset(network: self.account.network) return _internal_requestTwoStepPasswordReset(network: self.account.network)
} }
@ -113,6 +125,18 @@ public extension TelegramEngine {
public func declineTwoStepPasswordReset() -> Signal<Never, NoError> { public func declineTwoStepPasswordReset() -> Signal<Never, NoError> {
return _internal_declineTwoStepPasswordReset(network: self.account.network) 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 { switch self.engine {
case let .authorized(engine): case let .authorized(engine):
return engine.auth.resendTwoStepRecoveryEmail() return engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
case let .unauthorized(engine): 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)
} }
} }
} }

View File

@ -312,19 +312,26 @@ func _internal_updateTwoStepVerificationEmail(network: Network, currentPassword:
public enum RequestTwoStepVerificationPasswordRecoveryCodeError { public enum RequestTwoStepVerificationPasswordRecoveryCodeError {
case generic case generic
case limitExceeded
} }
func _internal_requestTwoStepVerificationPasswordRecoveryCode(network: Network) -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> { func _internal_requestTwoStepVerificationPasswordRecoveryCode(network: Network) -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
return network.request(Api.functions.auth.requestPasswordRecovery(), automaticFloodWait: false) 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 return .generic
} }
|> map { result -> String in }
switch result { |> map { result -> String in
case let .passwordRecovery(emailPattern): switch result {
return emailPattern case let .passwordRecovery(emailPattern):
} return emailPattern
} }
}
} }
public enum RecoverTwoStepVerificationPasswordError { public enum RecoverTwoStepVerificationPasswordError {
@ -334,35 +341,6 @@ public enum RecoverTwoStepVerificationPasswordError {
case invalidCode 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> { func _internal_cachedTwoStepPasswordToken(postbox: Postbox) -> Signal<TemporaryTwoStepPasswordToken?, NoError> {
return postbox.transaction { transaction -> TemporaryTwoStepPasswordToken? in return postbox.transaction { transaction -> TemporaryTwoStepPasswordToken? in
let key = ValueBoxKey(length: 1) let key = ValueBoxKey(length: 1)

View File

@ -105,7 +105,7 @@ func syncContactsOnce(network: Network, postbox: Postbox, accountPeerId: PeerId)
return appliedUpdatedPeers 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 return account.postbox.transaction { transaction -> Signal<Never, NoError> in
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) { if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
return account.network.request(Api.functions.contacts.deleteContacts(id: [inputUser])) return account.network.request(Api.functions.contacts.deleteContacts(id: [inputUser]))
@ -133,7 +133,7 @@ public func deleteContactPeerInteractively(account: Account, peerId: PeerId) ->
|> switchToLatest |> 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 account.postbox.transaction { transaction -> [Api.InputUser] in
return transaction.getContactPeerIds().compactMap(transaction.getPeer).compactMap({ apiInputUser($0) }).compactMap({ $0 }) 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()) return network.request(Api.functions.contacts.resetSaved())
|> `catch` { _ -> Signal<Api.Bool, NoError> in |> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse) return .single(.boolFalse)

View File

@ -4,8 +4,7 @@ import SwiftSignalKit
import SyncCore 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) let input = Api.InputContact.inputPhoneContact(clientId: 1, phone: phoneNumber, firstName: firstName, lastName: lastName)
return account.network.request(Api.functions.contacts.importContacts(contacts: [input])) return account.network.request(Api.functions.contacts.importContacts(contacts: [input]))
@ -42,7 +41,7 @@ public enum AddContactError {
case generic 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 return account.postbox.transaction { transaction -> (Api.InputUser, String)? in
if let user = transaction.getPeer(peerId) as? TelegramUser, let inputUser = apiInputUser(user) { if let user = transaction.getPeer(peerId) as? TelegramUser, let inputUser = apiInputUser(user) {
return (inputUser, user.phone == nil ? phoneNumber : "") return (inputUser, user.phone == nil ? phoneNumber : "")
@ -99,7 +98,7 @@ public enum AcceptAndShareContactError {
case generic 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 account.postbox.transaction { transaction -> Api.InputUser? in
return transaction.getPeer(peerId).flatMap(apiInputUser) return transaction.getPeer(peerId).flatMap(apiInputUser)
} }

View File

@ -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 return postbox.transaction { transaction -> [String: Int32] in
var result: [String: Int32] = [:] var result: [String: Int32] = [:]
for (id, numbers) in contacts { for (id, numbers) in contacts {

View File

@ -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)
}
}
}

View File

@ -10,7 +10,7 @@ public enum UpdateContactNameError {
case generic 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 return account.postbox.transaction { transaction -> Signal<Void, UpdateContactNameError> in
if let peer = transaction.getPeer(peerId) as? TelegramUser, let inputUser = apiInputUser(peer) { 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: "")) return account.network.request(Api.functions.contacts.addContact(flags: 0, id: inputUser, firstName: firstName, lastName: lastName, phone: ""))

View File

@ -11,7 +11,7 @@ public enum GetMessagesStrategy {
case cloud 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 let postboxSignal = postbox.transaction { transaction -> ([Message], Set<MessageId>, SimpleDictionary<PeerId, Peer>) in
var ids = messageIds var ids = messageIds

View File

@ -6,7 +6,7 @@ import MtProtoKit
import SyncCore 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()) return network.request(Api.functions.messages.getDialogUnreadMarks())
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<[Api.DialogPeer]?, NoError> in |> `catch` { _ -> Signal<[Api.DialogPeer]?, NoError> in

View File

@ -453,7 +453,7 @@ public enum FetchChannelReplyThreadMessageError {
case generic 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 account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
} }

View File

@ -6,7 +6,7 @@ import MtProtoKit
import SyncCore 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 { if let payload = payload, !payload.isEmpty {
return account.postbox.loadedPeerWithId(botPeerId) return account.postbox.loadedPeerWithId(botPeerId)
|> mapToSignal { botPeer -> Signal<Void, NoError> in |> mapToSignal { botPeer -> Signal<Void, NoError> in
@ -41,7 +41,7 @@ public enum StartBotInGroupResult {
case channelParticipant(RenderedChannelParticipant) 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 account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(botPeerId), transaction.getPeer(groupPeerId)) return (transaction.getPeer(botPeerId), transaction.getPeer(groupPeerId))
} }

View File

@ -94,5 +94,33 @@ public extension TelegramEngine {
public func forwardGameWithScore(messageId: MessageId, to peerId: PeerId) -> Signal<Void, NoError> { public func forwardGameWithScore(messageId: MessageId, to peerId: PeerId) -> Signal<Void, NoError> {
return _internal_forwardGameWithScore(account: self.account, messageId: messageId, to: peerId) 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)
}
} }
} }

View File

@ -15,7 +15,7 @@ public enum PinnedMessageUpdate {
case clear(id: MessageId) 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 account.postbox.transaction { transaction -> (Peer?, CachedPeerData?) in
return (transaction.getPeer(peerId), transaction.getPeerCachedData(peerId: peerId)) 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 account.postbox.transaction { transaction -> (Peer?, CachedPeerData?) in
return (transaction.getPeer(peerId), transaction.getPeerCachedData(peerId: peerId)) return (transaction.getPeer(peerId), transaction.getPeerCachedData(peerId: peerId))
} }

View File

@ -4,7 +4,7 @@ import SwiftSignalKit
import SyncCore 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 return account.postbox.transaction { transaction -> Void in
if let peer = transaction.getPeer(peerId) { if let peer = transaction.getPeer(peerId) {
var notificationPeerId = 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 return account.postbox.transaction { transaction -> Void in
updatePeerMuteSetting(transaction: transaction, peerId: peerId, muteInterval: muteInterval) 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) { if let peer = transaction.getPeer(peerId) {
var notificationPeerId = peerId var notificationPeerId = peerId
if let associatedPeerId = peer.associatedPeerId { 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 return account.postbox.transaction { transaction -> Void in
updatePeerDisplayPreviewsSetting(transaction: transaction, peerId: peerId, displayPreviews: displayPreviews) 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) { if let peer = transaction.getPeer(peerId) {
var notificationPeerId = peerId var notificationPeerId = peerId
if let associatedPeerId = peer.associatedPeerId { 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 return account.postbox.transaction { transaction -> Void in
updatePeerNotificationSoundInteractive(transaction: transaction, peerId: peerId, sound: sound) 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) { if let peer = transaction.getPeer(peerId) {
var notificationPeerId = peerId var notificationPeerId = peerId
if let associatedPeerId = peer.associatedPeerId { if let associatedPeerId = peer.associatedPeerId {

Some files were not shown because too many files have changed in this diff Show More