mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
b28b67eef2
@ -6555,3 +6555,8 @@ Sorry for the inconvenience.";
|
||||
"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";
|
||||
|
||||
"TwoFactorSetup.ResetDone.TitleNoPassword" = "Password Removed";
|
||||
"TwoFactorSetup.ResetDone.TextNoPassword" = "You can always set a new password in\n\n\nSettings>Privacy & Security>Two-Step Verification";
|
||||
|
||||
"TwoFactorSetup.ResetFloodWait" = "You have recently requested a password reset that was canceled. Please wait for %@ before making a new request.";
|
||||
|
@ -12,4 +12,6 @@ public protocol StatusBarHost {
|
||||
|
||||
func setStatusBarStyle(_ style: UIStatusBarStyle, animated: Bool)
|
||||
func setStatusBarHidden(_ value: Bool, animated: Bool)
|
||||
|
||||
var shouldChangeStatusBarStyle: ((UIStatusBarStyle) -> Bool)? { get set }
|
||||
}
|
||||
|
@ -776,12 +776,11 @@ public class Window1 {
|
||||
self?.inCallNavigate?()
|
||||
}
|
||||
}
|
||||
self.hostView.containerView.insertSubview(rootController.view, at: 0)
|
||||
if !self.windowLayout.size.width.isZero && !self.windowLayout.size.height.isZero {
|
||||
rootController.displayNode.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
|
||||
rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, deviceMetrics: self.deviceMetrics), transition: .immediate)
|
||||
}
|
||||
|
||||
self.hostView.containerView.insertSubview(rootController.view, at: 0)
|
||||
}
|
||||
|
||||
self.hostView.eventView.setNeedsLayout()
|
||||
|
@ -264,13 +264,13 @@ public final class GradientBackgroundNode: ASDisplayNode {
|
||||
|
||||
var steps: [[CGPoint]] = []
|
||||
if backwards {
|
||||
let phaseCount = extendAnimation ? 4 : 1
|
||||
let phaseCount = extendAnimation ? 6 : 1
|
||||
self.phase = (self.phase + phaseCount) % 8
|
||||
self.validPhase = self.phase
|
||||
|
||||
var stepPhase = self.phase - phaseCount
|
||||
if stepPhase < 0 {
|
||||
stepPhase = 7
|
||||
stepPhase = 8 + stepPhase
|
||||
}
|
||||
for _ in 0 ... phaseCount {
|
||||
steps.append(gatherPositions(shiftArray(array: GradientBackgroundNode.basePositions, offset: stepPhase)))
|
||||
|
@ -58,7 +58,7 @@ public final class PasscodeEntryController: ViewController {
|
||||
private var inBackground: Bool = false
|
||||
private var inBackgroundDisposable: Disposable?
|
||||
|
||||
private let statusBarHost: StatusBarHost?
|
||||
private var statusBarHost: StatusBarHost?
|
||||
private var previousStatusBarStyle: UIStatusBarStyle?
|
||||
|
||||
public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, appLockContext: AppLockContext, presentationData: PresentationData, presentationDataSignal: Signal<PresentationData, NoError>, statusBarHost: StatusBarHost?, challengeData: PostboxAccessChallengeData, biometrics: PasscodeEntryControllerBiometricsMode, arguments: PasscodeEntryControllerPresentationArguments) {
|
||||
@ -76,7 +76,14 @@ public final class PasscodeEntryController: ViewController {
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
statusBarHost?.setStatusBarStyle(.lightContent, animated: true)
|
||||
self.statusBarHost?.setStatusBarStyle(.lightContent, animated: true)
|
||||
self.statusBarHost?.shouldChangeStatusBarStyle = { [weak self] style in
|
||||
if let strongSelf = self {
|
||||
strongSelf.previousStatusBarStyle = style
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
self.presentationDataDisposable = (presentationDataSignal
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
@ -276,6 +283,7 @@ public final class PasscodeEntryController: ViewController {
|
||||
}
|
||||
|
||||
public override func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.statusBarHost?.shouldChangeStatusBarStyle = nil
|
||||
if let statusBarHost = self.statusBarHost, let previousStatusBarStyle = self.previousStatusBarStyle {
|
||||
statusBarHost.setStatusBarStyle(previousStatusBarStyle, animated: true)
|
||||
}
|
||||
|
Binary file not shown.
@ -513,7 +513,19 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
case .declined:
|
||||
break
|
||||
case let .error(reason):
|
||||
break
|
||||
let text: String
|
||||
switch reason {
|
||||
case let .limitExceeded(retryAtTimestamp):
|
||||
if let retryAtTimestamp = retryAtTimestamp {
|
||||
let remainingSeconds = retryAtTimestamp - Int32(Date().timeIntervalSince1970)
|
||||
text = strongSelf.presentationData.strings.TwoFactorSetup_ResetFloodWait(timeIntervalString(strings: strongSelf.presentationData.strings, value: remainingSeconds)).0
|
||||
} else {
|
||||
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))
|
||||
}
|
||||
})
|
||||
})]), in: .window(.root))
|
||||
@ -621,7 +633,7 @@ public final class TwoFactorDataInputScreen: ViewController {
|
||||
strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: false))
|
||||
}
|
||||
|
||||
(strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .recoveryDone(recoveredAccountData: recoveredAccountData, syncContacts: syncContacts)), animated: true)
|
||||
(strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .recoveryDone(recoveredAccountData: recoveredAccountData, syncContacts: syncContacts, isPasswordSet: !password.isEmpty)), animated: true)
|
||||
}, error: { [weak self, weak statusController] error in
|
||||
statusController?.dismiss()
|
||||
|
||||
|
@ -15,7 +15,7 @@ import TelegramCore
|
||||
public enum TwoFactorAuthSplashMode {
|
||||
case intro
|
||||
case done
|
||||
case recoveryDone(recoveredAccountData: RecoveredAccountData, syncContacts: Bool)
|
||||
case recoveryDone(recoveredAccountData: RecoveredAccountData?, syncContacts: Bool, isPasswordSet: Bool)
|
||||
}
|
||||
|
||||
public final class TwoFactorAuthSplashScreen: ViewController {
|
||||
@ -66,13 +66,15 @@ public final class TwoFactorAuthSplashScreen: ViewController {
|
||||
return
|
||||
}
|
||||
navigationController.filterController(strongSelf, animated: true)
|
||||
case let .recoveryDone(recoveredAccountData, syncContacts):
|
||||
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()
|
||||
if let recoveredAccountData = recoveredAccountData {
|
||||
let _ = loginWithRecoveredAccountData(accountManager: strongSelf.sharedContext.accountManager, account: engine.account, recoveredAccountData: recoveredAccountData, syncContacts: syncContacts).start()
|
||||
}
|
||||
case .authorized:
|
||||
navigationController.filterController(strongSelf, animated: true)
|
||||
}
|
||||
@ -97,7 +99,8 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
|
||||
private var animationOffset: CGPoint = CGPoint()
|
||||
private let animationNode: AnimatedStickerNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private let textNodes: [ImmediateTextNode]
|
||||
private let textArrowNodes: [ASImageNode]
|
||||
let buttonNode: SolidRoundedButtonNode
|
||||
|
||||
var inProgress: Bool = false {
|
||||
@ -114,7 +117,7 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
|
||||
self.animationNode = AnimatedStickerNode()
|
||||
|
||||
let title: String
|
||||
let text: NSAttributedString
|
||||
let texts: [NSAttributedString]
|
||||
let buttonText: String
|
||||
|
||||
let textFont = Font.regular(16.0)
|
||||
@ -123,7 +126,7 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
|
||||
switch mode {
|
||||
case .intro:
|
||||
title = self.presentationData.strings.TwoFactorSetup_Intro_Title
|
||||
text = NSAttributedString(string: self.presentationData.strings.TwoFactorSetup_Intro_Text, font: textFont, textColor: textColor)
|
||||
texts = [NSAttributedString(string: self.presentationData.strings.TwoFactorSetup_Intro_Text, font: textFont, textColor: textColor)]
|
||||
buttonText = self.presentationData.strings.TwoFactorSetup_Intro_Action
|
||||
|
||||
if let path = getAppBundle().path(forResource: "TwoFactorSetupIntro", ofType: "tgs") {
|
||||
@ -133,7 +136,7 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
|
||||
}
|
||||
case .done:
|
||||
title = self.presentationData.strings.TwoFactorSetup_Done_Title
|
||||
text = NSAttributedString(string: self.presentationData.strings.TwoFactorSetup_Done_Text, font: textFont, textColor: textColor)
|
||||
texts = [NSAttributedString(string: self.presentationData.strings.TwoFactorSetup_Done_Text, font: textFont, textColor: textColor)]
|
||||
buttonText = self.presentationData.strings.TwoFactorSetup_Done_Action
|
||||
|
||||
if let path = getAppBundle().path(forResource: "TwoFactorSetupDone", ofType: "tgs") {
|
||||
@ -141,13 +144,28 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
|
||||
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)
|
||||
case let .recoveryDone(_, _, isPasswordSet):
|
||||
title = isPasswordSet ? self.presentationData.strings.TwoFactorSetup_ResetDone_Title : self.presentationData.strings.TwoFactorSetup_ResetDone_TitleNoPassword
|
||||
|
||||
let rawText = isPasswordSet ? self.presentationData.strings.TwoFactorSetup_ResetDone_Text : self.presentationData.strings.TwoFactorSetup_ResetDone_TextNoPassword
|
||||
|
||||
var splitTexts: [String] = [""]
|
||||
var index = rawText.startIndex
|
||||
while index != rawText.endIndex {
|
||||
let c = rawText[index]
|
||||
if c == ">" {
|
||||
splitTexts.append("")
|
||||
} else {
|
||||
splitTexts[splitTexts.count - 1].append(c)
|
||||
}
|
||||
index = rawText.index(after: index)
|
||||
}
|
||||
|
||||
texts = splitTexts.map { NSAttributedString(string: $0, font: textFont, textColor: textColor) }
|
||||
buttonText = self.presentationData.strings.TwoFactorSetup_ResetDone_Action
|
||||
|
||||
if let path = getAppBundle().path(forResource: "TwoFactorSetupDone", ofType: "tgs") {
|
||||
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, mode: .direct(cachePathPrefix: nil))
|
||||
if let path = getAppBundle().path(forResource: isPasswordSet ? "TwoFactorSetupDone" : "TwoFactorRemovePasswordDone", ofType: "tgs") {
|
||||
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, playbackMode: isPasswordSet ? .loop : .once, mode: .direct(cachePathPrefix: nil))
|
||||
self.animationSize = CGSize(width: 124.0, height: 124.0)
|
||||
self.animationNode.visibility = true
|
||||
}
|
||||
@ -159,12 +177,27 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
|
||||
self.titleNode.maximumNumberOfLines = 0
|
||||
self.titleNode.textAlignment = .center
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.attributedText = text
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
self.textNode.lineSpacing = 0.1
|
||||
self.textNode.textAlignment = .center
|
||||
self.textNodes = texts.map { text in
|
||||
let textNode = ImmediateTextNode()
|
||||
|
||||
textNode.displaysAsynchronously = false
|
||||
textNode.attributedText = text
|
||||
textNode.maximumNumberOfLines = 0
|
||||
textNode.lineSpacing = 0.1
|
||||
textNode.textAlignment = .center
|
||||
|
||||
return textNode
|
||||
}
|
||||
|
||||
let arrowImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/DownButton"), color: presentationData.theme.list.itemPrimaryTextColor)
|
||||
self.textArrowNodes = (0 ..< self.textNodes.count - 1).map { _ in
|
||||
let iconNode = ASImageNode()
|
||||
|
||||
iconNode.image = arrowImage
|
||||
iconNode.alpha = 0.34
|
||||
|
||||
return iconNode
|
||||
}
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
self.buttonNode.isHidden = buttonText.isEmpty
|
||||
@ -175,7 +208,8 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
|
||||
|
||||
self.addSubnode(self.animationNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.textNodes.forEach(self.addSubnode)
|
||||
self.textArrowNodes.forEach(self.addSubnode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.buttonNode.pressed = {
|
||||
@ -204,9 +238,17 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
|
||||
let textSizes = self.textNodes.map {
|
||||
$0.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
|
||||
}
|
||||
var combinedTextHeight: CGFloat = 0.0
|
||||
let textSpacing: CGFloat = 32.0
|
||||
for textSize in textSizes {
|
||||
combinedTextHeight += textSize.height
|
||||
}
|
||||
combinedTextHeight += CGFloat(max(0, textSizes.count - 1)) * textSpacing
|
||||
|
||||
let contentHeight = iconSize.height + iconSpacing + titleSize.height + titleSpacing + textSize.height
|
||||
let contentHeight = iconSize.height + iconSpacing + titleSize.height + titleSpacing + combinedTextHeight
|
||||
var contentVerticalOrigin = floor((layout.size.height - contentHeight - iconSize.height / 2.0) / 2.0)
|
||||
|
||||
let minimalBottomInset: CGFloat = 60.0
|
||||
@ -227,7 +269,20 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode {
|
||||
transition.updateFrameAdditive(node: self.animationNode, frame: iconFrame)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleSize)
|
||||
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize)
|
||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||
|
||||
var nextTextOrigin: CGFloat = titleFrame.maxY + titleSpacing
|
||||
for i in 0 ..< self.textNodes.count {
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSizes[i].width) / 2.0), y: nextTextOrigin), size: textSizes[i])
|
||||
transition.updateFrameAdditive(node: self.textNodes[i], frame: textFrame)
|
||||
|
||||
if i != 0 {
|
||||
if let image = self.textArrowNodes[i - 1].image {
|
||||
let scaledImageSize = CGSize(width: floor(image.size.width * 0.7), height: floor(image.size.height * 0.7))
|
||||
self.textArrowNodes[i - 1].frame = CGRect(origin: CGPoint(x: floor((layout.size.width - scaledImageSize.width) / 2.0), y: nextTextOrigin - textSpacing + floor((textSpacing - scaledImageSize.height) / 2.0)), size: scaledImageSize)
|
||||
}
|
||||
}
|
||||
|
||||
nextTextOrigin = textFrame.maxY + textSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -733,9 +733,9 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
case .set:
|
||||
break
|
||||
case let .notSet(pendingEmail):
|
||||
//intro = pendingEmail == nil
|
||||
if pendingEmail == nil {
|
||||
let controller = TwoFactorAuthSplashScreen(sharedContext: context.sharedContext, engine: .authorized(context.engine), mode: .intro)
|
||||
|
||||
pushControllerImpl?(controller, true)
|
||||
return
|
||||
} else {
|
||||
|
@ -1,456 +0,0 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
|
||||
private final class TwoStepVerificationPasswordEntryControllerArguments {
|
||||
let updateEntryText: (String) -> Void
|
||||
let next: () -> Void
|
||||
|
||||
init(updateEntryText: @escaping (String) -> Void, next: @escaping () -> Void) {
|
||||
self.updateEntryText = updateEntryText
|
||||
self.next = next
|
||||
}
|
||||
}
|
||||
|
||||
private enum TwoStepVerificationPasswordEntrySection: Int32 {
|
||||
case password
|
||||
}
|
||||
|
||||
private enum TwoStepVerificationPasswordEntryTag: ItemListItemTag {
|
||||
case input
|
||||
|
||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? TwoStepVerificationPasswordEntryTag {
|
||||
switch self {
|
||||
case .input:
|
||||
if case .input = other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum TwoStepVerificationPasswordEntryEntry: ItemListNodeEntry {
|
||||
case passwordEntryTitle(PresentationTheme, String)
|
||||
case passwordEntry(PresentationTheme, PresentationStrings, String)
|
||||
|
||||
case hintTitle(PresentationTheme, String)
|
||||
case hintEntry(PresentationTheme, PresentationStrings, String)
|
||||
|
||||
case emailEntry(PresentationTheme, PresentationStrings, String)
|
||||
case emailInfo(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
return TwoStepVerificationPasswordEntrySection.password.rawValue
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .passwordEntryTitle:
|
||||
return 0
|
||||
case .passwordEntry:
|
||||
return 1
|
||||
case .hintTitle:
|
||||
return 2
|
||||
case .hintEntry:
|
||||
return 3
|
||||
case .emailEntry:
|
||||
return 5
|
||||
case .emailInfo:
|
||||
return 6
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: TwoStepVerificationPasswordEntryEntry, rhs: TwoStepVerificationPasswordEntryEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .passwordEntryTitle(lhsTheme, lhsText):
|
||||
if case let .passwordEntryTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .passwordEntry(lhsTheme, lhsStrings, lhsText):
|
||||
if case let .passwordEntry(rhsTheme, rhsStrings, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .hintTitle(lhsTheme, lhsText):
|
||||
if case let .hintTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .hintEntry(lhsTheme, lhsStrings, lhsText):
|
||||
if case let .hintEntry(rhsTheme, rhsStrings, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .emailEntry(lhsTheme, lhsStrings, lhsText):
|
||||
if case let .emailEntry(rhsTheme, rhsStrings, rhsText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .emailInfo(lhsTheme, lhsText):
|
||||
if case let .emailInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: TwoStepVerificationPasswordEntryEntry, rhs: TwoStepVerificationPasswordEntryEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! TwoStepVerificationPasswordEntryControllerArguments
|
||||
switch self {
|
||||
case let .passwordEntryTitle(theme, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .passwordEntry(theme, strings, text):
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: "", type: .password, spacing: 0.0, tag: TwoStepVerificationPasswordEntryTag.input, sectionId: self.section, textUpdated: { updatedText in
|
||||
arguments.updateEntryText(updatedText)
|
||||
}, action: {
|
||||
arguments.next()
|
||||
})
|
||||
case let .hintTitle(theme, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .hintEntry(theme, strings, text):
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: "", type: .password, spacing: 0.0, tag: TwoStepVerificationPasswordEntryTag.input, sectionId: self.section, textUpdated: { updatedText in
|
||||
arguments.updateEntryText(updatedText)
|
||||
}, action: {
|
||||
arguments.next()
|
||||
})
|
||||
case let .emailEntry(theme, strings, text):
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: strings.TwoStepAuth_Email, textColor: .black), text: text, placeholder: "", type: .email, spacing: 10.0, tag: TwoStepVerificationPasswordEntryTag.input, sectionId: self.section, textUpdated: { updatedText in
|
||||
arguments.updateEntryText(updatedText)
|
||||
}, action: {
|
||||
arguments.next()
|
||||
})
|
||||
case let .emailInfo(theme, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum PasswordEntryStage: Equatable {
|
||||
case entry(text: String)
|
||||
case reentry(first: String, text: String)
|
||||
case hint(password: String, text: String)
|
||||
case email(password: String, hint: String, text: String)
|
||||
|
||||
func updateCurrentText(_ text: String) -> PasswordEntryStage {
|
||||
switch self {
|
||||
case .entry:
|
||||
return .entry(text: text)
|
||||
case let .reentry(first, _):
|
||||
return .reentry(first: first, text: text)
|
||||
case let .hint(password, _):
|
||||
return .hint(password: password, text: text)
|
||||
case let .email(password, hint, _):
|
||||
return .email(password: password, hint: hint, text: text)
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: PasswordEntryStage, rhs: PasswordEntryStage) -> Bool {
|
||||
switch lhs {
|
||||
case let .entry(text):
|
||||
if case .entry(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .reentry(first, text):
|
||||
if case .reentry(first, text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .hint(password, text):
|
||||
if case .hint(password, text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .email(password, hint, text):
|
||||
if case .email(password, hint, text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TwoStepVerificationPasswordEntryControllerState: Equatable {
|
||||
let stage: PasswordEntryStage
|
||||
let updating: Bool
|
||||
|
||||
init(stage: PasswordEntryStage, updating: Bool) {
|
||||
self.stage = stage
|
||||
self.updating = updating
|
||||
}
|
||||
|
||||
static func ==(lhs: TwoStepVerificationPasswordEntryControllerState, rhs: TwoStepVerificationPasswordEntryControllerState) -> Bool {
|
||||
if lhs.stage != rhs.stage {
|
||||
return false
|
||||
}
|
||||
if lhs.updating != rhs.updating {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func withUpdatedStage(_ stage: PasswordEntryStage) -> TwoStepVerificationPasswordEntryControllerState {
|
||||
return TwoStepVerificationPasswordEntryControllerState(stage: stage, updating: self.updating)
|
||||
}
|
||||
|
||||
func withUpdatedUpdating(_ updating: Bool) -> TwoStepVerificationPasswordEntryControllerState {
|
||||
return TwoStepVerificationPasswordEntryControllerState(stage: self.stage, updating: updating)
|
||||
}
|
||||
}
|
||||
|
||||
private func twoStepVerificationPasswordEntryControllerEntries(presentationData: PresentationData, state: TwoStepVerificationPasswordEntryControllerState, mode: TwoStepVerificationPasswordEntryMode) -> [TwoStepVerificationPasswordEntryEntry] {
|
||||
var entries: [TwoStepVerificationPasswordEntryEntry] = []
|
||||
|
||||
switch state.stage {
|
||||
case let .entry(text):
|
||||
entries.append(.passwordEntryTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupPasswordEnterPasswordNew))
|
||||
entries.append(.passwordEntry(presentationData.theme, presentationData.strings, text))
|
||||
case let .reentry(_, text):
|
||||
entries.append(.passwordEntryTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupPasswordConfirmPassword))
|
||||
entries.append(.passwordEntry(presentationData.theme, presentationData.strings, text))
|
||||
case let .hint(_, text):
|
||||
entries.append(.hintTitle(presentationData.theme, presentationData.strings.TwoStepAuth_SetupHint))
|
||||
entries.append(.hintEntry(presentationData.theme, presentationData.strings, text))
|
||||
case let .email(_, _, text):
|
||||
entries.append(.emailEntry(presentationData.theme, presentationData.strings, text))
|
||||
entries.append(.emailInfo(presentationData.theme, presentationData.strings.TwoStepAuth_EmailHelp))
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
enum TwoStepVerificationPasswordEntryMode {
|
||||
case setup
|
||||
case change(current: String)
|
||||
case setupEmail(password: String)
|
||||
}
|
||||
|
||||
struct TwoStepVerificationPasswordEntryResult {
|
||||
let password: String
|
||||
let pendingEmail: TwoStepVerificationPendingEmail?
|
||||
}
|
||||
|
||||
func twoStepVerificationPasswordEntryController(context: AccountContext, mode: TwoStepVerificationPasswordEntryMode, result: Promise<TwoStepVerificationPasswordEntryResult?>) -> ViewController {
|
||||
let initialStage: PasswordEntryStage
|
||||
switch mode {
|
||||
case .setup, .change:
|
||||
initialStage = .entry(text: "")
|
||||
case .setupEmail:
|
||||
initialStage = .email(password: "", hint: "", text: "")
|
||||
}
|
||||
let initialState = TwoStepVerificationPasswordEntryControllerState(stage: initialStage, updating: false)
|
||||
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let updateState: ((TwoStepVerificationPasswordEntryControllerState) -> TwoStepVerificationPasswordEntryControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments) -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let updatePasswordDisposable = MetaDisposable()
|
||||
actionsDisposable.add(updatePasswordDisposable)
|
||||
|
||||
let checkPassword: () -> Void = {
|
||||
var passwordHintEmail: (String, String, String)?
|
||||
var invalidReentry = false
|
||||
updateState { state in
|
||||
if state.updating {
|
||||
return state
|
||||
} else {
|
||||
switch state.stage {
|
||||
case let .entry(text):
|
||||
if text.isEmpty {
|
||||
return state
|
||||
} else {
|
||||
return state.withUpdatedStage(.reentry(first: text, text: ""))
|
||||
}
|
||||
case let .reentry(first, text):
|
||||
if text.isEmpty {
|
||||
return state
|
||||
} else if text != first {
|
||||
invalidReentry = true
|
||||
return state.withUpdatedStage(.entry(text: ""))
|
||||
} else {
|
||||
return state.withUpdatedStage(.hint(password: text, text: ""))
|
||||
}
|
||||
case let .hint(password, text):
|
||||
switch mode {
|
||||
case .setup:
|
||||
return state.withUpdatedStage(.email(password: password, hint: text, text: ""))
|
||||
case .change:
|
||||
passwordHintEmail = (password, text, "")
|
||||
return state.withUpdatedUpdating(true)
|
||||
case .setupEmail:
|
||||
preconditionFailure()
|
||||
}
|
||||
case let .email(password, hint, text):
|
||||
passwordHintEmail = (password, hint, text)
|
||||
return state.withUpdatedUpdating(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (password, hint, email) = passwordHintEmail {
|
||||
switch mode {
|
||||
case .setup, .change:
|
||||
var currentPassword: String?
|
||||
if case let .change(current) = mode {
|
||||
currentPassword = current
|
||||
}
|
||||
updatePasswordDisposable.set((context.engine.auth.updateTwoStepVerificationPassword(currentPassword: currentPassword, updatedPassword: .password(password: password, hint: hint, email: email)) |> deliverOnMainQueue).start(next: { update in
|
||||
updateState {
|
||||
$0.withUpdatedUpdating(false)
|
||||
}
|
||||
switch update {
|
||||
case let .password(password, pendingEmail):
|
||||
result.set(.single(TwoStepVerificationPasswordEntryResult(password: password, pendingEmail: pendingEmail)))
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
}, error: { error in
|
||||
updateState {
|
||||
$0.withUpdatedUpdating(false)
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let alertText: String
|
||||
switch error {
|
||||
case .generic:
|
||||
alertText = presentationData.strings.Login_UnknownError
|
||||
case .invalidEmail:
|
||||
alertText = presentationData.strings.TwoStepAuth_EmailInvalid
|
||||
}
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}))
|
||||
case let .setupEmail(password):
|
||||
updatePasswordDisposable.set((context.engine.auth.updateTwoStepVerificationEmail(currentPassword: password, updatedEmail: email) |> deliverOnMainQueue).start(next: { update in
|
||||
updateState {
|
||||
$0.withUpdatedUpdating(false)
|
||||
}
|
||||
switch update {
|
||||
case let .password(password, pendingEmail):
|
||||
result.set(.single(TwoStepVerificationPasswordEntryResult(password: password, pendingEmail: pendingEmail)))
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
}, error: { error in
|
||||
updateState {
|
||||
$0.withUpdatedUpdating(false)
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let alertText: String
|
||||
switch error {
|
||||
case .generic:
|
||||
alertText = presentationData.strings.Login_UnknownError
|
||||
case .invalidEmail:
|
||||
alertText = presentationData.strings.TwoStepAuth_EmailInvalid
|
||||
}
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}))
|
||||
}
|
||||
} else if invalidReentry {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
}
|
||||
|
||||
let arguments = TwoStepVerificationPasswordEntryControllerArguments(updateEntryText: { updatedText in
|
||||
updateState {
|
||||
$0.withUpdatedStage($0.stage.updateCurrentText(updatedText))
|
||||
}
|
||||
}, next: {
|
||||
checkPassword()
|
||||
})
|
||||
|
||||
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.updating {
|
||||
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||
} else {
|
||||
var nextEnabled = true
|
||||
switch state.stage {
|
||||
case let .entry(text):
|
||||
if text.isEmpty {
|
||||
nextEnabled = false
|
||||
}
|
||||
case let.reentry(_, text):
|
||||
if text.isEmpty {
|
||||
nextEnabled = false
|
||||
}
|
||||
case .hint, .email:
|
||||
break
|
||||
}
|
||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Next), style: .bold, enabled: nextEnabled, action: {
|
||||
checkPassword()
|
||||
})
|
||||
}
|
||||
|
||||
let title: String
|
||||
switch mode {
|
||||
case .setup, .change:
|
||||
title = presentationData.strings.TwoStepAuth_EnterPasswordTitle
|
||||
case .setupEmail:
|
||||
title = presentationData.strings.TwoStepAuth_EmailTitle
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: twoStepVerificationPasswordEntryControllerEntries(presentationData: presentationData, state: state, mode: mode), style: .blocks, focusItemTag: TwoStepVerificationPasswordEntryTag.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?.view.endEditing(true)
|
||||
controller?.dismiss()
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
@ -552,7 +552,19 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
|
||||
case .declined:
|
||||
break
|
||||
case let .error(reason):
|
||||
break
|
||||
let text: String
|
||||
switch reason {
|
||||
case let .limitExceeded(retryAtTimestamp):
|
||||
if let retryAtTimestamp = retryAtTimestamp {
|
||||
let remainingSeconds = retryAtTimestamp - Int32(Date().timeIntervalSince1970)
|
||||
text = presentationData.strings.TwoFactorSetup_ResetFloodWait(timeIntervalString(strings: presentationData.strings, value: remainingSeconds)).0
|
||||
} else {
|
||||
text = presentationData.strings.TwoStepAuth_FloodError
|
||||
}
|
||||
case .generic:
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
}
|
||||
presentControllerImpl?(textAlertController(sharedContext: context.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -568,7 +580,19 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
|
||||
case .declined:
|
||||
break
|
||||
case let .error(reason):
|
||||
break
|
||||
let text: String
|
||||
switch reason {
|
||||
case let .limitExceeded(retryAtTimestamp):
|
||||
if let retryAtTimestamp = retryAtTimestamp {
|
||||
let remainingSeconds = retryAtTimestamp - Int32(Date().timeIntervalSince1970)
|
||||
text = presentationData.strings.TwoFactorSetup_ResetFloodWait(timeIntervalString(strings: presentationData.strings, value: remainingSeconds)).0
|
||||
} else {
|
||||
text = presentationData.strings.TwoStepAuth_FloodError
|
||||
}
|
||||
case .generic:
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
}
|
||||
presentControllerImpl?(textAlertController(sharedContext: context.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
})
|
||||
})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
@ -858,7 +882,20 @@ func twoStepVerificationUnlockSettingsController(context: AccountContext, mode:
|
||||
case .declined:
|
||||
break
|
||||
case let .error(reason):
|
||||
break
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let text: String
|
||||
switch reason {
|
||||
case let .limitExceeded(retryAtTimestamp):
|
||||
if let retryAtTimestamp = retryAtTimestamp {
|
||||
let remainingSeconds = retryAtTimestamp - Int32(Date().timeIntervalSince1970)
|
||||
text = presentationData.strings.TwoFactorSetup_ResetFloodWait(timeIntervalString(strings: presentationData.strings, value: remainingSeconds)).0
|
||||
} else {
|
||||
text = presentationData.strings.TwoStepAuth_FloodError
|
||||
}
|
||||
case .generic:
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
}
|
||||
presentControllerImpl?(textAlertController(sharedContext: context.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -7,7 +7,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1255641564] = { return parseString($0) }
|
||||
dict[-1240849242] = { return Api.messages.StickerSet.parse_stickerSet($0) }
|
||||
dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) }
|
||||
dict[1698544301] = { return Api.GroupCall.parse_groupCall($0) }
|
||||
dict[-711498484] = { return Api.GroupCall.parse_groupCall($0) }
|
||||
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
|
||||
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
|
||||
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
|
||||
|
@ -1,7 +1,7 @@
|
||||
public extension Api {
|
||||
public enum GroupCall: TypeConstructorDescription {
|
||||
case groupCallDiscarded(id: Int64, accessHash: Int64, duration: Int32)
|
||||
case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, title: String?, streamDcId: Int32?, recordStartDate: Int32?, scheduleDate: Int32?, version: Int32)
|
||||
case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, title: String?, streamDcId: Int32?, recordStartDate: Int32?, scheduleDate: Int32?, unmutedVideoCount: Int32?, unmutedVideoLimit: Int32, version: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -13,9 +13,9 @@ public extension Api {
|
||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||
serializeInt32(duration, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let version):
|
||||
case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version):
|
||||
if boxed {
|
||||
buffer.appendInt32(1698544301)
|
||||
buffer.appendInt32(-711498484)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
@ -25,6 +25,8 @@ public extension Api {
|
||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt32(streamDcId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 5) != 0 {serializeInt32(recordStartDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 7) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(unmutedVideoCount!, buffer: buffer, boxed: false)}
|
||||
serializeInt32(unmutedVideoLimit, buffer: buffer, boxed: false)
|
||||
serializeInt32(version, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
@ -34,8 +36,8 @@ public extension Api {
|
||||
switch self {
|
||||
case .groupCallDiscarded(let id, let accessHash, let duration):
|
||||
return ("groupCallDiscarded", [("id", id), ("accessHash", accessHash), ("duration", duration)])
|
||||
case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let version):
|
||||
return ("groupCall", [("flags", flags), ("id", id), ("accessHash", accessHash), ("participantsCount", participantsCount), ("title", title), ("streamDcId", streamDcId), ("recordStartDate", recordStartDate), ("scheduleDate", scheduleDate), ("version", version)])
|
||||
case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version):
|
||||
return ("groupCall", [("flags", flags), ("id", id), ("accessHash", accessHash), ("participantsCount", participantsCount), ("title", title), ("streamDcId", streamDcId), ("recordStartDate", recordStartDate), ("scheduleDate", scheduleDate), ("unmutedVideoCount", unmutedVideoCount), ("unmutedVideoLimit", unmutedVideoLimit), ("version", version)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,7 +76,11 @@ public extension Api {
|
||||
var _8: Int32?
|
||||
if Int(_1!) & Int(1 << 7) != 0 {_8 = reader.readInt32() }
|
||||
var _9: Int32?
|
||||
_9 = reader.readInt32()
|
||||
if Int(_1!) & Int(1 << 10) != 0 {_9 = reader.readInt32() }
|
||||
var _10: Int32?
|
||||
_10 = reader.readInt32()
|
||||
var _11: Int32?
|
||||
_11 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -83,9 +89,11 @@ public extension Api {
|
||||
let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil
|
||||
let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil
|
||||
let _c9 = _9 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||
return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, participantsCount: _4!, title: _5, streamDcId: _6, recordStartDate: _7, scheduleDate: _8, version: _9!)
|
||||
let _c9 = (Int(_1!) & Int(1 << 10) == 0) || _9 != nil
|
||||
let _c10 = _10 != nil
|
||||
let _c11 = _11 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
|
||||
return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, participantsCount: _4!, title: _5, streamDcId: _6, recordStartDate: _7, scheduleDate: _8, unmutedVideoCount: _9, unmutedVideoLimit: _10!, version: _11!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -106,7 +106,8 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
||||
recordingStartTimestamp: nil,
|
||||
sortAscending: true,
|
||||
defaultParticipantsAreMuted: nil,
|
||||
isVideoEnabled: false
|
||||
isVideoEnabled: false,
|
||||
unmutedVideoLimit: 0
|
||||
),
|
||||
topParticipants: [],
|
||||
participantCount: 0,
|
||||
@ -148,7 +149,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
||||
}
|
||||
return GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
info: GroupCallInfo(id: call.id, accessHash: call.accessHash, participantCount: state.totalCount, streamDcId: nil, title: state.title, scheduleTimestamp: state.scheduleTimestamp, subscribedToScheduled: state.subscribedToScheduled, recordingStartTimestamp: nil, sortAscending: state.sortAscending, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, isVideoEnabled: state.isVideoEnabled),
|
||||
info: GroupCallInfo(id: call.id, accessHash: call.accessHash, participantCount: state.totalCount, streamDcId: nil, title: state.title, scheduleTimestamp: state.scheduleTimestamp, subscribedToScheduled: state.subscribedToScheduled, recordingStartTimestamp: nil, sortAscending: state.sortAscending, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, isVideoEnabled: state.isVideoEnabled, unmutedVideoLimit: state.unmutedVideoLimit),
|
||||
topParticipants: topParticipants,
|
||||
participantCount: state.totalCount,
|
||||
activeSpeakers: activeSpeakers,
|
||||
@ -415,7 +416,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
private var screencastBufferServerContext: IpcGroupCallBufferAppContext?
|
||||
private var screencastCapturer: OngoingCallVideoCapturer?
|
||||
|
||||
private var ssrcMapping: [UInt32: PeerId] = [:]
|
||||
private struct SsrcMapping {
|
||||
var peerId: PeerId
|
||||
var isPresentation: Bool
|
||||
}
|
||||
private var ssrcMapping: [UInt32: SsrcMapping] = [:]
|
||||
|
||||
private var summaryInfoState = Promise<SummaryInfoState?>(nil)
|
||||
private var summaryParticipantsState = Promise<SummaryParticipantsState?>(nil)
|
||||
@ -1206,6 +1211,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
subscribedToScheduled: self.stateValue.subscribedToScheduled,
|
||||
totalCount: 0,
|
||||
isVideoEnabled: callInfo.isVideoEnabled,
|
||||
unmutedVideoLimit: callInfo.unmutedVideoLimit,
|
||||
version: 0
|
||||
),
|
||||
previousServiceState: nil
|
||||
@ -1293,7 +1299,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
strongSelf.stateValue.scheduleTimestamp = strongSelf.isScheduledStarted ? nil : state.scheduleTimestamp
|
||||
if state.scheduleTimestamp == nil && !strongSelf.isScheduledStarted {
|
||||
strongSelf.updateSessionState(internalState: .active(GroupCallInfo(id: callInfo.id, accessHash: callInfo.accessHash, participantCount: state.totalCount, streamDcId: callInfo.streamDcId, title: state.title, scheduleTimestamp: nil, subscribedToScheduled: false, recordingStartTimestamp: nil, sortAscending: true, defaultParticipantsAreMuted: callInfo.defaultParticipantsAreMuted ?? state.defaultParticipantsAreMuted, isVideoEnabled: callInfo.isVideoEnabled)), audioSessionControl: strongSelf.audioSessionControl)
|
||||
strongSelf.updateSessionState(internalState: .active(GroupCallInfo(id: callInfo.id, accessHash: callInfo.accessHash, participantCount: state.totalCount, streamDcId: callInfo.streamDcId, title: state.title, scheduleTimestamp: nil, subscribedToScheduled: false, recordingStartTimestamp: nil, sortAscending: true, defaultParticipantsAreMuted: callInfo.defaultParticipantsAreMuted ?? state.defaultParticipantsAreMuted, isVideoEnabled: callInfo.isVideoEnabled, unmutedVideoLimit: callInfo.unmutedVideoLimit)), audioSessionControl: strongSelf.audioSessionControl)
|
||||
} else {
|
||||
strongSelf.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
|
||||
id: callInfo.id,
|
||||
@ -1306,7 +1312,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
recordingStartTimestamp: state.recordingStartTimestamp,
|
||||
sortAscending: state.sortAscending,
|
||||
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
||||
isVideoEnabled: state.isVideoEnabled
|
||||
isVideoEnabled: state.isVideoEnabled,
|
||||
unmutedVideoLimit: state.unmutedVideoLimit
|
||||
))))
|
||||
|
||||
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||
@ -1484,7 +1491,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
strongSelf.ssrcMapping.removeAll()
|
||||
for participant in joinCallResult.state.participants {
|
||||
if let ssrc = participant.ssrc {
|
||||
strongSelf.ssrcMapping[ssrc] = participant.peer.id
|
||||
strongSelf.ssrcMapping[ssrc] = SsrcMapping(peerId: participant.peer.id, isPresentation: false)
|
||||
}
|
||||
if let presentationSsrc = participant.presentationDescription?.audioSsrc {
|
||||
strongSelf.ssrcMapping[presentationSsrc] = SsrcMapping(peerId: participant.peer.id, isPresentation: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1616,8 +1626,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
peerId = strongSelf.joinAsPeerId
|
||||
ssrcValue = 0
|
||||
case let .source(ssrc):
|
||||
peerId = strongSelf.ssrcMapping[ssrc]
|
||||
ssrcValue = ssrc
|
||||
if let mapping = strongSelf.ssrcMapping[ssrc] {
|
||||
if mapping.isPresentation {
|
||||
peerId = nil
|
||||
ssrcValue = 0
|
||||
} else {
|
||||
peerId = mapping.peerId
|
||||
ssrcValue = ssrc
|
||||
}
|
||||
} else {
|
||||
ssrcValue = ssrc
|
||||
}
|
||||
}
|
||||
if let peerId = peerId {
|
||||
if case .local = ssrcKey {
|
||||
@ -1843,6 +1862,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) })
|
||||
}
|
||||
}
|
||||
|
||||
var otherParticipantsWithVideo = 0
|
||||
|
||||
for participant in participants {
|
||||
var participant = participant
|
||||
@ -1852,7 +1873,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
if let ssrc = participant.ssrc {
|
||||
strongSelf.ssrcMapping[ssrc] = participant.peer.id
|
||||
strongSelf.ssrcMapping[ssrc] = SsrcMapping(peerId: participant.peer.id, isPresentation: false)
|
||||
}
|
||||
if let presentationSsrc = participant.presentationDescription?.audioSsrc {
|
||||
strongSelf.ssrcMapping[presentationSsrc] = SsrcMapping(peerId: participant.peer.id, isPresentation: true)
|
||||
}
|
||||
|
||||
if participant.peer.id == strongSelf.joinAsPeerId {
|
||||
@ -1939,6 +1963,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
strongSelf.genericCallContext?.setVolume(ssrc: ssrc, volume: 0.0)
|
||||
}
|
||||
}
|
||||
if let presentationSsrc = participant.presentationDescription?.audioSsrc {
|
||||
if let volume = participant.volume {
|
||||
strongSelf.genericCallContext?.setVolume(ssrc: presentationSsrc, volume: Double(volume) / 10000.0)
|
||||
} else if participant.muteState?.mutedByYou == true {
|
||||
strongSelf.genericCallContext?.setVolume(ssrc: presentationSsrc, volume: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
if participant.videoDescription != nil || participant.presentationDescription != nil {
|
||||
otherParticipantsWithVideo += 1
|
||||
}
|
||||
}
|
||||
|
||||
if let index = updatedInvitedPeers.firstIndex(of: participant.peer.id) {
|
||||
@ -1963,7 +1998,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
strongSelf.stateValue.recordingStartTimestamp = state.recordingStartTimestamp
|
||||
strongSelf.stateValue.title = state.title
|
||||
strongSelf.stateValue.scheduleTimestamp = state.scheduleTimestamp
|
||||
strongSelf.stateValue.isVideoEnabled = state.isVideoEnabled
|
||||
strongSelf.stateValue.isVideoEnabled = state.isVideoEnabled && otherParticipantsWithVideo < state.unmutedVideoLimit
|
||||
|
||||
strongSelf.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
|
||||
id: callInfo.id,
|
||||
@ -1976,7 +2011,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
recordingStartTimestamp: state.recordingStartTimestamp,
|
||||
sortAscending: state.sortAscending,
|
||||
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
||||
isVideoEnabled: state.isVideoEnabled
|
||||
isVideoEnabled: state.isVideoEnabled,
|
||||
unmutedVideoLimit: state.unmutedVideoLimit
|
||||
))))
|
||||
|
||||
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||
@ -2673,15 +2709,16 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
public func setVolume(peerId: PeerId, volume: Int32, sync: Bool) {
|
||||
for (ssrc, id) in self.ssrcMapping {
|
||||
if id == peerId {
|
||||
var found = false
|
||||
for (ssrc, mapping) in self.ssrcMapping {
|
||||
if mapping.peerId == peerId {
|
||||
self.genericCallContext?.setVolume(ssrc: ssrc, volume: Double(volume) / 10000.0)
|
||||
if sync {
|
||||
self.participantsContext?.updateMuteState(peerId: peerId, muteState: nil, volume: volume, raiseHand: nil)
|
||||
}
|
||||
break
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if found && sync {
|
||||
self.participantsContext?.updateMuteState(peerId: peerId, muteState: nil, volume: volume, raiseHand: nil)
|
||||
}
|
||||
}
|
||||
|
||||
public func setRequestedVideoList(items: [PresentationGroupCallRequestedVideo]) {
|
||||
|
@ -4368,11 +4368,15 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.actionButton.isDisabled = !actionButtonEnabled
|
||||
self.actionButton.update(size: centralButtonSize, buttonSize: CGSize(width: 112.0, height: 112.0), state: actionButtonState, title: actionButtonTitle, subtitle: actionButtonSubtitle, dark: self.isFullscreen, small: smallButtons, animated: true)
|
||||
|
||||
var hasCameraButton = self.callState?.isVideoEnabled ?? false
|
||||
|
||||
let isVideoEnabled = self.callState?.isVideoEnabled ?? false
|
||||
var hasCameraButton = isVideoEnabled
|
||||
if let joinedVideo = self.joinedVideo {
|
||||
hasCameraButton = joinedVideo
|
||||
}
|
||||
if !isVideoEnabled {
|
||||
hasCameraButton = false
|
||||
}
|
||||
switch actionButtonState {
|
||||
case let .active(state):
|
||||
switch state {
|
||||
|
@ -16,6 +16,7 @@ public struct GroupCallInfo: Equatable {
|
||||
public var sortAscending: Bool
|
||||
public var defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?
|
||||
public var isVideoEnabled: Bool
|
||||
public var unmutedVideoLimit: Int
|
||||
|
||||
public init(
|
||||
id: Int64,
|
||||
@ -28,7 +29,8 @@ public struct GroupCallInfo: Equatable {
|
||||
recordingStartTimestamp: Int32?,
|
||||
sortAscending: Bool,
|
||||
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?,
|
||||
isVideoEnabled: Bool
|
||||
isVideoEnabled: Bool,
|
||||
unmutedVideoLimit: Int
|
||||
) {
|
||||
self.id = id
|
||||
self.accessHash = accessHash
|
||||
@ -41,6 +43,7 @@ public struct GroupCallInfo: Equatable {
|
||||
self.sortAscending = sortAscending
|
||||
self.defaultParticipantsAreMuted = defaultParticipantsAreMuted
|
||||
self.isVideoEnabled = isVideoEnabled
|
||||
self.unmutedVideoLimit = unmutedVideoLimit
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +55,7 @@ public struct GroupCallSummary: Equatable {
|
||||
extension GroupCallInfo {
|
||||
init?(_ call: Api.GroupCall) {
|
||||
switch call {
|
||||
case let .groupCall(flags, id, accessHash, participantsCount, title, streamDcId, recordStartDate, scheduleDate, _):
|
||||
case let .groupCall(flags, id, accessHash, participantsCount, title, streamDcId, recordStartDate, scheduleDate, _, unmutedVideoLimit, _):
|
||||
self.init(
|
||||
id: id,
|
||||
accessHash: accessHash,
|
||||
@ -64,7 +67,8 @@ extension GroupCallInfo {
|
||||
recordingStartTimestamp: recordStartDate,
|
||||
sortAscending: (flags & (1 << 6)) != 0,
|
||||
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: (flags & (1 << 1)) != 0, canChange: (flags & (1 << 2)) != 0),
|
||||
isVideoEnabled: (flags & (1 << 9)) != 0
|
||||
isVideoEnabled: (flags & (1 << 9)) != 0,
|
||||
unmutedVideoLimit: Int(unmutedVideoLimit)
|
||||
)
|
||||
case .groupCallDiscarded:
|
||||
return nil
|
||||
@ -332,22 +336,17 @@ public enum GetGroupCallParticipantsError {
|
||||
}
|
||||
|
||||
public func getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
|
||||
let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool), GetGroupCallParticipantsError>
|
||||
// if let sortAscending = sortAscending {
|
||||
// sortAscendingValue = .single((sortAscending, nil, false, nil, false))
|
||||
// } else {
|
||||
//
|
||||
// }
|
||||
let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int), GetGroupCallParticipantsError>
|
||||
|
||||
sortAscendingValue = getCurrentGroupCall(account: account, callId: callId, accessHash: accessHash)
|
||||
|> mapError { _ -> GetGroupCallParticipantsError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool), GetGroupCallParticipantsError> in
|
||||
|> mapToSignal { result -> Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int), GetGroupCallParticipantsError> in
|
||||
guard let result = result else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return .single((sortAscending ?? result.info.sortAscending, result.info.scheduleTimestamp, result.info.subscribedToScheduled, result.info.defaultParticipantsAreMuted, result.info.isVideoEnabled))
|
||||
return .single((sortAscending ?? result.info.sortAscending, result.info.scheduleTimestamp, result.info.subscribedToScheduled, result.info.defaultParticipantsAreMuted, result.info.isVideoEnabled, result.info.unmutedVideoLimit))
|
||||
}
|
||||
|
||||
return combineLatest(
|
||||
@ -364,8 +363,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
|
||||
let version: Int32
|
||||
let nextParticipantsFetchOffset: String?
|
||||
|
||||
let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled) = sortAscendingAndScheduleTimestamp
|
||||
|
||||
let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit) = sortAscendingAndScheduleTimestamp
|
||||
|
||||
switch result {
|
||||
case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion):
|
||||
@ -418,6 +416,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
|
||||
subscribedToScheduled: subscribedToScheduled,
|
||||
totalCount: totalCount,
|
||||
isVideoEnabled: isVideoEnabled,
|
||||
unmutedVideoLimit: unmutedVideoLimit,
|
||||
version: version
|
||||
)
|
||||
}
|
||||
@ -508,7 +507,7 @@ public func joinGroupCall(account: Account, peerId: PeerId, joinAs: PeerId?, cal
|
||||
joinRequest,
|
||||
getParticipantsRequest
|
||||
)
|
||||
|> mapToSignal { updates, participantsState -> Signal<JoinGroupCallResult, JoinGroupCallError> in
|
||||
|> mapToSignal { updates, participantsState -> Signal<JoinGroupCallResult, JoinGroupCallError> in
|
||||
let peer = account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(peerId)
|
||||
}
|
||||
@ -544,7 +543,7 @@ public func joinGroupCall(account: Account, peerId: PeerId, joinAs: PeerId?, cal
|
||||
maybeParsedCall = GroupCallInfo(call)
|
||||
|
||||
switch call {
|
||||
case let .groupCall(flags, _, _, _, title, _, recordStartDate, scheduleDate, _):
|
||||
case let .groupCall(flags, _, _, _, title, _, recordStartDate, scheduleDate, _, unmutedVideoLimit, _):
|
||||
let isMuted = (flags & (1 << 1)) != 0
|
||||
let canChange = (flags & (1 << 2)) != 0
|
||||
let isVideoEnabled = (flags & (1 << 9)) != 0
|
||||
@ -553,6 +552,7 @@ public func joinGroupCall(account: Account, peerId: PeerId, joinAs: PeerId?, cal
|
||||
state.recordingStartTimestamp = recordStartDate
|
||||
state.scheduleTimestamp = scheduleDate
|
||||
state.isVideoEnabled = isVideoEnabled
|
||||
state.unmutedVideoLimit = Int(unmutedVideoLimit)
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -1029,6 +1029,7 @@ public final class GroupCallParticipantsContext {
|
||||
public var subscribedToScheduled: Bool
|
||||
public var totalCount: Int
|
||||
public var isVideoEnabled: Bool
|
||||
public var unmutedVideoLimit: Int
|
||||
public var version: Int32
|
||||
|
||||
public mutating func mergeActivity(from other: State, myPeerId: PeerId?, previousMyPeerId: PeerId?, mergeActivityTimestamps: Bool) {
|
||||
@ -1062,6 +1063,7 @@ public final class GroupCallParticipantsContext {
|
||||
subscribedToScheduled: Bool,
|
||||
totalCount: Int,
|
||||
isVideoEnabled: Bool,
|
||||
unmutedVideoLimit: Int,
|
||||
version: Int32
|
||||
) {
|
||||
self.participants = participants
|
||||
@ -1076,6 +1078,7 @@ public final class GroupCallParticipantsContext {
|
||||
self.subscribedToScheduled = subscribedToScheduled
|
||||
self.totalCount = totalCount
|
||||
self.isVideoEnabled = isVideoEnabled
|
||||
self.unmutedVideoLimit = unmutedVideoLimit
|
||||
self.version = version
|
||||
}
|
||||
}
|
||||
@ -1374,6 +1377,7 @@ public final class GroupCallParticipantsContext {
|
||||
subscribedToScheduled: strongSelf.stateValue.state.subscribedToScheduled,
|
||||
totalCount: strongSelf.stateValue.state.totalCount,
|
||||
isVideoEnabled: strongSelf.stateValue.state.isVideoEnabled,
|
||||
unmutedVideoLimit: strongSelf.stateValue.state.unmutedVideoLimit,
|
||||
version: strongSelf.stateValue.state.version
|
||||
),
|
||||
overlayState: strongSelf.stateValue.overlayState
|
||||
@ -1511,6 +1515,7 @@ public final class GroupCallParticipantsContext {
|
||||
subscribedToScheduled: strongSelf.stateValue.state.subscribedToScheduled,
|
||||
totalCount: strongSelf.stateValue.state.totalCount,
|
||||
isVideoEnabled: strongSelf.stateValue.state.isVideoEnabled,
|
||||
unmutedVideoLimit: strongSelf.stateValue.state.unmutedVideoLimit,
|
||||
version: strongSelf.stateValue.state.version
|
||||
),
|
||||
overlayState: strongSelf.stateValue.overlayState
|
||||
@ -1732,6 +1737,7 @@ public final class GroupCallParticipantsContext {
|
||||
let scheduleTimestamp = strongSelf.stateValue.state.scheduleTimestamp
|
||||
let subscribedToScheduled = strongSelf.stateValue.state.subscribedToScheduled
|
||||
let isVideoEnabled = strongSelf.stateValue.state.isVideoEnabled
|
||||
let unmutedVideoLimit = strongSelf.stateValue.state.unmutedVideoLimit
|
||||
|
||||
updatedParticipants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: strongSelf.stateValue.state.sortAscending) })
|
||||
|
||||
@ -1749,6 +1755,7 @@ public final class GroupCallParticipantsContext {
|
||||
subscribedToScheduled: subscribedToScheduled,
|
||||
totalCount: updatedTotalCount,
|
||||
isVideoEnabled: isVideoEnabled,
|
||||
unmutedVideoLimit: unmutedVideoLimit,
|
||||
version: update.version
|
||||
),
|
||||
overlayState: updatedOverlayState
|
||||
|
@ -3023,7 +3023,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
||||
})
|
||||
|
||||
switch call {
|
||||
case let .groupCall(flags, _, _, _, title, _, recordStartDate, scheduleDate, _):
|
||||
case let .groupCall(flags, _, _, _, title, _, recordStartDate, scheduleDate, _, _, _):
|
||||
let isMuted = (flags & (1 << 1)) != 0
|
||||
let canChange = (flags & (1 << 2)) != 0
|
||||
let isVideoEnabled = (flags & (1 << 9)) != 0
|
||||
|
@ -395,7 +395,7 @@ func _internal_requestTemporaryTwoStepPasswordToken(account: Account, password:
|
||||
public enum RequestTwoStepPasswordResetResult {
|
||||
public enum ErrorReason {
|
||||
case generic
|
||||
case limitExceeded
|
||||
case limitExceeded(retryAtTimestamp: Int32?)
|
||||
}
|
||||
|
||||
case done
|
||||
@ -406,12 +406,19 @@ public enum RequestTwoStepPasswordResetResult {
|
||||
|
||||
func _internal_requestTwoStepPasswordReset(network: Network) -> Signal<RequestTwoStepPasswordResetResult, NoError> {
|
||||
return network.request(Api.functions.account.resetPassword(), automaticFloodWait: false)
|
||||
|> map { _ -> RequestTwoStepPasswordResetResult in
|
||||
return .done
|
||||
|> map { result -> RequestTwoStepPasswordResetResult in
|
||||
switch result {
|
||||
case let .resetPasswordFailedWait(retryDate):
|
||||
return .error(reason: .limitExceeded(retryAtTimestamp: retryDate))
|
||||
case .resetPasswordOk:
|
||||
return .done
|
||||
case let .resetPasswordRequestedWait(untilDate):
|
||||
return .waitingForReset(resetAtTimestamp: untilDate)
|
||||
}
|
||||
}
|
||||
|> `catch` { error -> Signal<RequestTwoStepPasswordResetResult, NoError> in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .single(.error(reason: .limitExceeded))
|
||||
return .single(.error(reason: .limitExceeded(retryAtTimestamp: nil)))
|
||||
} else if error.errorDescription.hasPrefix("RESET_WAIT_") {
|
||||
if let remainingSeconds = Int32(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "RESET_WAIT_".count)...]) {
|
||||
let timestamp = Int32(network.globalTime)
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -99,9 +99,13 @@ private class ApplicationStatusBarHost: StatusBarHost {
|
||||
}
|
||||
|
||||
func setStatusBarStyle(_ style: UIStatusBarStyle, animated: Bool) {
|
||||
self.application.setStatusBarStyle(style, animated: animated)
|
||||
if self.shouldChangeStatusBarStyle?(style) ?? true {
|
||||
self.application.setStatusBarStyle(style, animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
var shouldChangeStatusBarStyle: ((UIStatusBarStyle) -> Bool)?
|
||||
|
||||
func setStatusBarHidden(_ value: Bool, animated: Bool) {
|
||||
self.application.setStatusBarHidden(value, with: animated ? .fade : .none)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user