mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-14 18:29:51 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
7d05d1f521
@ -5148,7 +5148,7 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"AuthSessions.AddDevice.ScanTitle" = "Scan QR Code";
|
"AuthSessions.AddDevice.ScanTitle" = "Scan QR Code";
|
||||||
"AuthSessions.AddDevice.InvalidQRCode" = "Invalid QR Code";
|
"AuthSessions.AddDevice.InvalidQRCode" = "Invalid QR Code";
|
||||||
"AuthSessions.AddDeviceIntro.Title" = "Log in by QR Code";
|
"AuthSessions.AddDeviceIntro.Title" = "Log in by QR Code";
|
||||||
"AuthSessions.AddDeviceIntro.Text1" = "[Download Telegram]() on your computer";
|
"AuthSessions.AddDeviceIntro.Text1" = "Download Telegram on your computer from [desktop.telegram.org]()";
|
||||||
"AuthSessions.AddDeviceIntro.Text2" = "Run Telegram on your computer to get the QR code";
|
"AuthSessions.AddDeviceIntro.Text2" = "Run Telegram on your computer to get the QR code";
|
||||||
"AuthSessions.AddDeviceIntro.Text3" = "Scan the QR code to connect your account";
|
"AuthSessions.AddDeviceIntro.Text3" = "Scan the QR code to connect your account";
|
||||||
"AuthSessions.AddDeviceIntro.Action" = "Scan QR Code";
|
"AuthSessions.AddDeviceIntro.Action" = "Scan QR Code";
|
||||||
@ -5183,3 +5183,5 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"EditTheme.ChangeColors" = "Change Colors";
|
"EditTheme.ChangeColors" = "Change Colors";
|
||||||
|
|
||||||
"Theme.Colors.Proceed" = "Proceed";
|
"Theme.Colors.Proceed" = "Proceed";
|
||||||
|
|
||||||
|
"AuthSessions.AddDevice.UrlLoginHint" = "This code can be used to allow someone to log in to your Telegram account.\n\nTo confirm Telegram login, please go to Settings > Devices > Scan QR and scan the code.";
|
||||||
|
|||||||
@ -46,6 +46,35 @@ public final class AnimationNode : ASDisplayNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public init(animationData: Data, colors: [String: UIColor]? = nil, scale: CGFloat = 1.0) {
|
||||||
|
self.scale = scale
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.setViewBlock({
|
||||||
|
if let json = try? JSONSerialization.jsonObject(with: animationData, options: []) as? [AnyHashable: Any] {
|
||||||
|
let composition = LOTComposition(json: json)
|
||||||
|
|
||||||
|
let view = LOTAnimationView(model: composition, in: getAppBundle())
|
||||||
|
view.animationSpeed = self.speed
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
view.isOpaque = false
|
||||||
|
|
||||||
|
if let colors = colors {
|
||||||
|
for (key, value) in colors {
|
||||||
|
let colorCallback = LOTColorValueCallback(color: value.cgColor)
|
||||||
|
self.colorCallbacks.append(colorCallback)
|
||||||
|
view.setValueDelegate(colorCallback, for: LOTKeypath(string: "\(key).Color"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view
|
||||||
|
} else {
|
||||||
|
return LOTAnimationView()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public func setAnimation(name: String) {
|
public func setAnimation(name: String) {
|
||||||
if let url = getAppBundle().url(forResource: name, withExtension: "json"), let composition = LOTComposition(filePath: url.path) {
|
if let url = getAppBundle().url(forResource: name, withExtension: "json"), let composition = LOTComposition(filePath: url.path) {
|
||||||
self.animationView()?.sceneModel = composition
|
self.animationView()?.sceneModel = composition
|
||||||
|
|||||||
@ -24,6 +24,7 @@ static_library(
|
|||||||
"//submodules/Markdown:Markdown",
|
"//submodules/Markdown:Markdown",
|
||||||
"//submodules/AnimationUI:AnimationUI",
|
"//submodules/AnimationUI:AnimationUI",
|
||||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||||
|
"//submodules/DeviceAccess:DeviceAccess",
|
||||||
],
|
],
|
||||||
frameworks = [
|
frameworks = [
|
||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
|
|||||||
@ -12,6 +12,68 @@ import TelegramPresentationData
|
|||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import Markdown
|
import Markdown
|
||||||
|
import DeviceAccess
|
||||||
|
|
||||||
|
private let colorKeyRegex = try? NSRegularExpression(pattern: "\"k\":\\[[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\]")
|
||||||
|
|
||||||
|
private func transformedWithTheme(data: Data, theme: PresentationTheme) -> Data {
|
||||||
|
if var string = String(data: data, encoding: .utf8) {
|
||||||
|
var colors: [UIColor] = [0x333333, 0xFFFFFF, 0x50A7EA, 0x212121].map { UIColor(rgb: $0) }
|
||||||
|
let replacementColors: [UIColor] = [theme.list.itemPrimaryTextColor.mixedWith(.white, alpha: 0.2), theme.list.plainBackgroundColor, theme.list.itemAccentColor, theme.list.itemPrimaryTextColor.mixedWith(.white, alpha: 0.12)]
|
||||||
|
|
||||||
|
func colorToString(_ color: UIColor) -> String {
|
||||||
|
var r: CGFloat = 0.0
|
||||||
|
var g: CGFloat = 0.0
|
||||||
|
var b: CGFloat = 0.0
|
||||||
|
if color.getRed(&r, green: &g, blue: &b, alpha: nil) {
|
||||||
|
return "\"k\":[\(r),\(g),\(b),1]"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func match(_ a: Double, _ b: Double, eps: Double) -> Bool {
|
||||||
|
return abs(a - b) < eps
|
||||||
|
}
|
||||||
|
|
||||||
|
var replacements: [(NSTextCheckingResult, String)] = []
|
||||||
|
|
||||||
|
if let colorKeyRegex = colorKeyRegex {
|
||||||
|
let results = colorKeyRegex.matches(in: string, range: NSRange(string.startIndex..., in: string))
|
||||||
|
for result in results.reversed() {
|
||||||
|
if let range = Range(result.range, in: string) {
|
||||||
|
let substring = String(string[range])
|
||||||
|
let color = substring[substring.index(string.startIndex, offsetBy: "\"k\":[".count) ..< substring.index(before: substring.endIndex)]
|
||||||
|
let components = color.split(separator: ",")
|
||||||
|
if components.count == 4, let r = Double(components[0]), let g = Double(components[1]), let b = Double(components[2]), let a = Double(components[3]) {
|
||||||
|
if match(a, 1.0, eps: 0.01) {
|
||||||
|
for i in 0 ..< colors.count {
|
||||||
|
let color = colors[i]
|
||||||
|
var cr: CGFloat = 0.0
|
||||||
|
var cg: CGFloat = 0.0
|
||||||
|
var cb: CGFloat = 0.0
|
||||||
|
if color.getRed(&cr, green: &cg, blue: &cb, alpha: nil) {
|
||||||
|
if match(r, Double(cr), eps: 0.01) && match(g, Double(cg), eps: 0.01) && match(b, Double(cb), eps: 0.01) {
|
||||||
|
replacements.append((result, colorToString(replacementColors[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (result, text) in replacements {
|
||||||
|
if let range = Range(result.range, in: string) {
|
||||||
|
string = string.replacingCharacters(in: range, with: text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.data(using: .utf8) ?? data
|
||||||
|
} else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final class AuthDataTransferSplashScreen: ViewController {
|
public final class AuthDataTransferSplashScreen: ViewController {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
@ -49,7 +111,24 @@ public final class AuthDataTransferSplashScreen: ViewController {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
(strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: AuthTransferScanScreen(context: strongSelf.context, activeSessionsContext: strongSelf.activeSessionsContext), animated: true)
|
|
||||||
|
DeviceAccess.authorizeAccess(to: .camera, presentationData: strongSelf.presentationData, present: { c, a in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.presentationArguments = a
|
||||||
|
strongSelf.context.sharedContext.mainWindow?.present(c, on: .root)
|
||||||
|
}, openSettings: {
|
||||||
|
self?.context.sharedContext.applicationBindings.openSettings()
|
||||||
|
}, { granted in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard granted else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
(strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: AuthTransferScanScreen(context: strongSelf.context, activeSessionsContext: strongSelf.activeSessionsContext), animated: true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
@ -67,13 +146,15 @@ private final class AuthDataTransferSplashScreenNode: ViewControllerTracingNode
|
|||||||
|
|
||||||
private var animationSize: CGSize = CGSize()
|
private var animationSize: CGSize = CGSize()
|
||||||
private var animationOffset: CGPoint = CGPoint()
|
private var animationOffset: CGPoint = CGPoint()
|
||||||
private let animationNode: AnimationNode
|
private let animationNode: AnimationNode?
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
private let badgeBackgroundNodes: [ASImageNode]
|
private let badgeBackgroundNodes: [ASImageNode]
|
||||||
private let badgeTextNodes: [ImmediateTextNode]
|
private let badgeTextNodes: [ImmediateTextNode]
|
||||||
private let textNodes: [ImmediateTextNode]
|
private let textNodes: [ImmediateTextNode]
|
||||||
let buttonNode: SolidRoundedButtonNode
|
let buttonNode: SolidRoundedButtonNode
|
||||||
|
|
||||||
|
private let hierarchyTrackingNode: HierarchyTrackingNode
|
||||||
|
|
||||||
var inProgress: Bool = false {
|
var inProgress: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
self.buttonNode.isUserInteractionEnabled = !self.inProgress
|
self.buttonNode.isUserInteractionEnabled = !self.inProgress
|
||||||
@ -86,12 +167,16 @@ private final class AuthDataTransferSplashScreenNode: ViewControllerTracingNode
|
|||||||
init(context: AccountContext, presentationData: PresentationData, action: @escaping () -> Void) {
|
init(context: AccountContext, presentationData: PresentationData, action: @escaping () -> Void) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
self.animationNode = AnimationNode(animation: "anim_qr", colors: nil, scale: UIScreenScale)
|
if let url = getAppBundle().url(forResource: "anim_qr", withExtension: "json"), let data = try? Data(contentsOf: url) {
|
||||||
|
self.animationNode = AnimationNode(animationData: transformedWithTheme(data: data, theme: presentationData.theme))
|
||||||
|
} else {
|
||||||
|
self.animationNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
let buttonText: String
|
let buttonText: String
|
||||||
|
|
||||||
let badgeFont = Font.with(size: 14.0, design: .round, traits: [.bold])
|
let badgeFont = Font.with(size: 13.0, design: .round, traits: [.bold])
|
||||||
let textFont = Font.regular(18.0)
|
let textFont = Font.regular(16.0)
|
||||||
let textColor = self.presentationData.theme.list.itemPrimaryTextColor
|
let textColor = self.presentationData.theme.list.itemPrimaryTextColor
|
||||||
|
|
||||||
var badgeBackgroundNodes: [ASImageNode] = []
|
var badgeBackgroundNodes: [ASImageNode] = []
|
||||||
@ -147,18 +232,27 @@ private final class AuthDataTransferSplashScreenNode: ViewControllerTracingNode
|
|||||||
|
|
||||||
self.titleNode = ImmediateTextNode()
|
self.titleNode = ImmediateTextNode()
|
||||||
self.titleNode.displaysAsynchronously = false
|
self.titleNode.displaysAsynchronously = false
|
||||||
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.AuthSessions_AddDeviceIntro_Title, font: Font.bold(28.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.AuthSessions_AddDeviceIntro_Title, font: Font.bold(24.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||||
self.titleNode.maximumNumberOfLines = 0
|
self.titleNode.maximumNumberOfLines = 0
|
||||||
self.titleNode.textAlignment = .center
|
self.titleNode.textAlignment = .center
|
||||||
|
|
||||||
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 = 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
|
self.buttonNode.isHidden = buttonText.isEmpty
|
||||||
|
|
||||||
|
var updateInHierarchy: ((Bool) -> Void)?
|
||||||
|
self.hierarchyTrackingNode = HierarchyTrackingNode({ value in
|
||||||
|
updateInHierarchy?(value)
|
||||||
|
})
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||||
|
|
||||||
self.addSubnode(self.animationNode)
|
self.addSubnode(self.hierarchyTrackingNode)
|
||||||
|
|
||||||
|
if let animationNode = self.animationNode {
|
||||||
|
self.addSubnode(animationNode)
|
||||||
|
}
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
|
|
||||||
self.badgeBackgroundNodes.forEach(self.addSubnode)
|
self.badgeBackgroundNodes.forEach(self.addSubnode)
|
||||||
@ -182,10 +276,16 @@ private final class AuthDataTransferSplashScreenNode: ViewControllerTracingNode
|
|||||||
}
|
}
|
||||||
textNode.tapAttributeAction = { attributes in
|
textNode.tapAttributeAction = { attributes in
|
||||||
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
||||||
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://telegram.org/desktop", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://desktop.telegram.org", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateInHierarchy = { [weak self] value in
|
||||||
|
if value {
|
||||||
|
self?.animationNode?.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
@ -198,15 +298,15 @@ private final class AuthDataTransferSplashScreenNode: ViewControllerTracingNode
|
|||||||
|
|
||||||
let sideInset: CGFloat = 22.0
|
let sideInset: CGFloat = 22.0
|
||||||
let textSideInset: CGFloat = 54.0
|
let textSideInset: CGFloat = 54.0
|
||||||
let buttonSideInset: CGFloat = 48.0
|
let buttonSideInset: CGFloat = 16.0
|
||||||
let titleSpacing: CGFloat = 30.0
|
let titleSpacing: CGFloat = 25.0
|
||||||
let buttonHeight: CGFloat = 50.0
|
let buttonHeight: CGFloat = 50.0
|
||||||
let buttonSpacing: CGFloat = 10.0
|
let buttonSpacing: CGFloat = 16.0
|
||||||
let textSpacing: CGFloat = 26.0
|
let textSpacing: CGFloat = 25.0
|
||||||
let badgeSize: CGFloat = 20.0
|
let badgeSize: CGFloat = 20.0
|
||||||
|
|
||||||
let animationFitSize = CGSize(width: min(500.0, layout.size.width - sideInset), height: 500.0)
|
let animationFitSize = CGSize(width: min(500.0, layout.size.width - sideInset + 20.0), height: 500.0)
|
||||||
let animationSize = self.animationNode.preferredSize()?.fitted(animationFitSize) ?? animationFitSize
|
let animationSize = self.animationNode?.preferredSize()?.fitted(animationFitSize) ?? animationFitSize
|
||||||
let iconSize: CGSize = animationSize
|
let iconSize: CGSize = animationSize
|
||||||
var iconOffset = CGPoint()
|
var iconOffset = CGPoint()
|
||||||
|
|
||||||
@ -233,7 +333,7 @@ private final class AuthDataTransferSplashScreenNode: ViewControllerTracingNode
|
|||||||
let contentTopInset = navigationHeight
|
let contentTopInset = navigationHeight
|
||||||
let contentBottomInset = bottomInset + buttonHeight + buttonSpacing
|
let contentBottomInset = bottomInset + buttonHeight + buttonSpacing
|
||||||
|
|
||||||
let iconSpacing: CGFloat = max(20.0, min(64.0, layout.size.height - contentTopInset - contentBottomInset - contentHeight - 40.0))
|
let iconSpacing: CGFloat = max(20.0, min(61.0, layout.size.height - contentTopInset - contentBottomInset - contentHeight - 40.0))
|
||||||
|
|
||||||
contentHeight += iconSpacing
|
contentHeight += iconSpacing
|
||||||
|
|
||||||
@ -252,7 +352,9 @@ private final class AuthDataTransferSplashScreenNode: ViewControllerTracingNode
|
|||||||
var contentY = contentVerticalOrigin
|
var contentY = contentVerticalOrigin
|
||||||
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0) + self.animationOffset.x, y: contentY), size: iconSize).offsetBy(dx: iconOffset.x, dy: iconOffset.y)
|
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0) + self.animationOffset.x, y: contentY), size: iconSize).offsetBy(dx: iconOffset.x, dy: iconOffset.y)
|
||||||
contentY += iconSize.height + iconSpacing
|
contentY += iconSize.height + iconSpacing
|
||||||
transition.updateFrameAdditive(node: self.animationNode, frame: iconFrame)
|
if let animationNode = self.animationNode {
|
||||||
|
transition.updateFrameAdditive(node: animationNode, frame: iconFrame)
|
||||||
|
}
|
||||||
|
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: contentY), size: titleSize)
|
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: contentY), size: titleSize)
|
||||||
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
|
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
|
||||||
@ -269,16 +371,23 @@ private final class AuthDataTransferSplashScreenNode: ViewControllerTracingNode
|
|||||||
let textFrame = CGRect(origin: CGPoint(x: textSideInset, y: contentY), size: textSize)
|
let textFrame = CGRect(origin: CGPoint(x: textSideInset, y: contentY), size: textSize)
|
||||||
transition.updateFrameAdditive(node: self.textNodes[i], frame: textFrame)
|
transition.updateFrameAdditive(node: self.textNodes[i], frame: textFrame)
|
||||||
|
|
||||||
let badgeFrame = CGRect(origin: CGPoint(x: sideInset, y: textFrame.minY + floor((textFrame.height - badgeSize) / 2.0)), size: CGSize(width: badgeSize, height: badgeSize))
|
let badgeFrame = CGRect(origin: CGPoint(x: sideInset, y: textFrame.minY), size: CGSize(width: badgeSize, height: badgeSize))
|
||||||
transition.updateFrameAdditive(node: self.badgeBackgroundNodes[i], frame: badgeFrame)
|
transition.updateFrameAdditive(node: self.badgeBackgroundNodes[i], frame: badgeFrame)
|
||||||
|
|
||||||
transition.updateFrameAdditive(node: self.badgeTextNodes[i], frame: CGRect(origin: CGPoint(x: badgeFrame.minX + floor((badgeFrame.width - badgeTextSize.width) / 2.0) + 0.5, y: badgeFrame.minY + floor((badgeFrame.height - badgeTextSize.height) / 2.0) + 0.5), size: badgeTextSize))
|
let badgeTextOffsetX: CGFloat
|
||||||
|
if i == 0 {
|
||||||
|
badgeTextOffsetX = 0.5
|
||||||
|
} else {
|
||||||
|
badgeTextOffsetX = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrameAdditive(node: self.badgeTextNodes[i], frame: CGRect(origin: CGPoint(x: badgeFrame.minX + floor((badgeFrame.width - badgeTextSize.width) / 2.0) + badgeTextOffsetX, y: badgeFrame.minY + floor((badgeFrame.height - badgeTextSize.height) / 2.0) + 0.5), size: badgeTextSize))
|
||||||
|
|
||||||
contentY += textSize.height
|
contentY += textSize.height
|
||||||
}
|
}
|
||||||
|
|
||||||
if firstTime {
|
if firstTime {
|
||||||
self.animationNode.play()
|
self.animationNode?.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -274,7 +274,7 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
|
|||||||
self.errorTextNode.attributedText = NSAttributedString(string: presentationData.strings.AuthSessions_AddDevice_InvalidQRCode, font: Font.medium(16.0), textColor: .white)
|
self.errorTextNode.attributedText = NSAttributedString(string: presentationData.strings.AuthSessions_AddDevice_InvalidQRCode, font: Font.medium(16.0), textColor: .white)
|
||||||
self.errorTextNode.maximumNumberOfLines = 0
|
self.errorTextNode.maximumNumberOfLines = 0
|
||||||
self.errorTextNode.textAlignment = .center
|
self.errorTextNode.textAlignment = .center
|
||||||
//self.errorTextNode.isHidden = true
|
self.errorTextNode.isHidden = true
|
||||||
|
|
||||||
self.camera = Camera(configuration: .init(preset: .hd1920x1080, position: .back, audio: false))
|
self.camera = Camera(configuration: .init(preset: .hd1920x1080, position: .back, audio: false))
|
||||||
|
|
||||||
@ -409,19 +409,28 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
|
|||||||
transition.updateFrame(node: self.centerDimNode, frame: dimRect)
|
transition.updateFrame(node: self.centerDimNode, frame: dimRect)
|
||||||
|
|
||||||
let buttonSize = CGSize(width: 72.0, height: 72.0)
|
let buttonSize = CGSize(width: 72.0, height: 72.0)
|
||||||
transition.updateFrame(node: self.torchButtonNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0), y: dimHeight + frameSide + 98.0), size: buttonSize))
|
var torchFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0), y: dimHeight + frameSide + 98.0), size: buttonSize)
|
||||||
|
let updatedTorchY = min(torchFrame.minY, layout.size.height - torchFrame.height - 10.0)
|
||||||
|
let additionalTorchOffset: CGFloat = updatedTorchY - torchFrame.minY
|
||||||
|
torchFrame.origin.y = updatedTorchY
|
||||||
|
transition.updateFrame(node: self.torchButtonNode, frame: torchFrame)
|
||||||
|
|
||||||
transition.updateAlpha(node: self.titleNode, alpha: controlsAlpha)
|
|
||||||
transition.updateAlpha(node: self.textNode, alpha: controlsAlpha)
|
transition.updateAlpha(node: self.textNode, alpha: controlsAlpha)
|
||||||
transition.updateAlpha(node: self.errorTextNode, alpha: controlsAlpha)
|
transition.updateAlpha(node: self.errorTextNode, alpha: controlsAlpha)
|
||||||
transition.updateAlpha(node: self.torchButtonNode, alpha: controlsAlpha)
|
transition.updateAlpha(node: self.torchButtonNode, alpha: controlsAlpha)
|
||||||
|
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
|
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: layout.size.height))
|
||||||
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
|
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: layout.size.height))
|
||||||
let errorTextSize = self.errorTextNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
|
let errorTextSize = self.errorTextNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: layout.size.height))
|
||||||
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: dimHeight - textSize.height - titleSpacing), size: textSize)
|
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: dimHeight - textSize.height - titleSpacing), size: textSize)
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: textFrame.minY - 18.0 - titleSize.height), size: titleSize)
|
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: textFrame.minY - 18.0 - titleSize.height), size: titleSize)
|
||||||
let errorTextFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - errorTextSize.width) / 2.0), y: dimHeight + frameSide + 48.0), size: errorTextSize)
|
var errorTextFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - errorTextSize.width) / 2.0), y: dimHeight + frameSide + 48.0), size: errorTextSize)
|
||||||
|
errorTextFrame.origin.y += floor(additionalTorchOffset / 2.0)
|
||||||
|
if titleFrame.minY < navigationHeight {
|
||||||
|
transition.updateAlpha(node: self.titleNode, alpha: 0.0)
|
||||||
|
} else {
|
||||||
|
transition.updateAlpha(node: self.titleNode, alpha: controlsAlpha)
|
||||||
|
}
|
||||||
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
|
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
|
||||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||||
transition.updateFrameAdditive(node: self.errorTextNode, frame: errorTextFrame)
|
transition.updateFrameAdditive(node: self.errorTextNode, frame: errorTextFrame)
|
||||||
|
|||||||
@ -23,10 +23,10 @@ func isViewVisibleInHierarchy(_ view: UIView, _ initial: Bool = true) -> Bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class HierarchyTrackingNode: ASDisplayNode {
|
public final class HierarchyTrackingNode: ASDisplayNode {
|
||||||
private let f: (Bool) -> Void
|
private let f: (Bool) -> Void
|
||||||
|
|
||||||
init(_ f: @escaping (Bool) -> Void) {
|
public init(_ f: @escaping (Bool) -> Void) {
|
||||||
self.f = f
|
self.f = f
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
@ -34,13 +34,13 @@ private final class HierarchyTrackingNode: ASDisplayNode {
|
|||||||
self.isLayerBacked = true
|
self.isLayerBacked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didEnterHierarchy() {
|
override public func didEnterHierarchy() {
|
||||||
super.didEnterHierarchy()
|
super.didEnterHierarchy()
|
||||||
|
|
||||||
self.f(true)
|
self.f(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didExitHierarchy() {
|
override public func didExitHierarchy() {
|
||||||
super.didExitHierarchy()
|
super.didExitHierarchy()
|
||||||
|
|
||||||
self.f(false)
|
self.f(false)
|
||||||
|
|||||||
@ -146,14 +146,117 @@ public final class MediaPlayerNode: ASDisplayNode {
|
|||||||
|
|
||||||
private func poll(completion: @escaping (PollStatus) -> Void) {
|
private func poll(completion: @escaping (PollStatus) -> Void) {
|
||||||
if let (takeFrameQueue, takeFrame) = self.takeFrameAndQueue, let videoLayer = self.videoLayer, let (timebase, _, _, _) = self.state {
|
if let (takeFrameQueue, takeFrame) = self.takeFrameAndQueue, let videoLayer = self.videoLayer, let (timebase, _, _, _) = self.state {
|
||||||
let layerRef = Unmanaged.passRetained(videoLayer)
|
let layerTime = CMTimeGetSeconds(CMTimebaseGetTime(timebase))
|
||||||
|
let rate = CMTimebaseGetRate(timebase)
|
||||||
|
|
||||||
|
struct PollState {
|
||||||
|
var numFrames: Int
|
||||||
|
var maxTakenTime: Double
|
||||||
|
}
|
||||||
|
|
||||||
|
var loop: ((PollState) -> Void)?
|
||||||
|
let loopImpl: (PollState) -> Void = { [weak self] state in
|
||||||
|
assert(Queue.mainQueue().isCurrent())
|
||||||
|
|
||||||
|
guard let strongSelf = self, let videoLayer = strongSelf.videoLayer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !videoLayer.isReadyForMoreMediaData {
|
||||||
|
completion(.delay(max(1.0 / 30.0, state.maxTakenTime - layerTime)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = state
|
||||||
|
|
||||||
|
takeFrameQueue.async {
|
||||||
|
switch takeFrame() {
|
||||||
|
case let .restoreState(frames, atTime):
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self, let videoLayer = strongSelf.videoLayer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
videoLayer.flush()
|
||||||
|
}
|
||||||
|
for i in 0 ..< frames.count {
|
||||||
|
let frame = frames[i]
|
||||||
|
let frameTime = CMTimeGetSeconds(frame.position)
|
||||||
|
state.maxTakenTime = frameTime
|
||||||
|
let attachments = CMSampleBufferGetSampleAttachmentsArray(frame.sampleBuffer, createIfNecessary: true)! as NSArray
|
||||||
|
let dict = attachments[0] as! NSMutableDictionary
|
||||||
|
if i == 0 {
|
||||||
|
CMSetAttachment(frame.sampleBuffer, key: kCMSampleBufferAttachmentKey_ResetDecoderBeforeDecoding as NSString, value: kCFBooleanTrue as AnyObject, attachmentMode: kCMAttachmentMode_ShouldPropagate)
|
||||||
|
CMSetAttachment(frame.sampleBuffer, key: kCMSampleBufferAttachmentKey_EndsPreviousSampleDuration as NSString, value: kCFBooleanTrue as AnyObject, attachmentMode: kCMAttachmentMode_ShouldPropagate)
|
||||||
|
}
|
||||||
|
if CMTimeCompare(frame.position, atTime) < 0 {
|
||||||
|
dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleAttachmentKey_DoNotDisplay as NSString as String)
|
||||||
|
} else if CMTimeCompare(frame.position, atTime) == 0 {
|
||||||
|
dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleAttachmentKey_DisplayImmediately as NSString as String)
|
||||||
|
dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleBufferAttachmentKey_EndsPreviousSampleDuration as NSString as String)
|
||||||
|
}
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self, let videoLayer = strongSelf.videoLayer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
videoLayer.enqueue(frame.sampleBuffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
loop?(state)
|
||||||
|
}
|
||||||
|
case let .frame(frame):
|
||||||
|
state.numFrames += 1
|
||||||
|
let frameTime = CMTimeGetSeconds(frame.position)
|
||||||
|
if frame.resetDecoder {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self, let videoLayer = strongSelf.videoLayer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
videoLayer.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if frame.decoded && frameTime < layerTime {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
loop?(state)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.maxTakenTime = frameTime
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self, let videoLayer = strongSelf.videoLayer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
videoLayer.enqueue(frame.sampleBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
loop?(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .skipFrame:
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
loop?(state)
|
||||||
|
}
|
||||||
|
case .noFrames:
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion(.finished)
|
||||||
|
}
|
||||||
|
case .finished:
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion(.finished)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop = loopImpl
|
||||||
|
loop?(PollState(numFrames: 0, maxTakenTime: layerTime + 0.1))
|
||||||
|
|
||||||
|
/*let layerRef = Unmanaged.passRetained(videoLayer)
|
||||||
takeFrameQueue.async {
|
takeFrameQueue.async {
|
||||||
let status: PollStatus
|
let status: PollStatus
|
||||||
do {
|
do {
|
||||||
var numFrames = 0
|
var numFrames = 0
|
||||||
let layer = layerRef.takeUnretainedValue()
|
let layer = layerRef.takeUnretainedValue()
|
||||||
let layerTime = CMTimeGetSeconds(CMTimebaseGetTime(timebase))
|
|
||||||
let rate = CMTimebaseGetRate(timebase)
|
|
||||||
var maxTakenTime = layerTime + 0.1
|
var maxTakenTime = layerTime + 0.1
|
||||||
var finised = false
|
var finised = false
|
||||||
loop: while true {
|
loop: while true {
|
||||||
@ -230,7 +333,7 @@ public final class MediaPlayerNode: ASDisplayNode {
|
|||||||
|
|
||||||
completion(status)
|
completion(status)
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,10 +389,6 @@ public final class MediaPlayerNode: ASDisplayNode {
|
|||||||
|
|
||||||
strongSelf.layer.addSublayer(videoLayer)
|
strongSelf.layer.addSublayer(videoLayer)
|
||||||
|
|
||||||
/*let testLayer = RuntimeUtils.makeLayerHostCopy(videoLayer.sublayers![0].sublayers![0])*/
|
|
||||||
//testLayer.frame = CGRect(origin: CGPoint(x: -500.0, y: -300.0), size: CGSize(width: 60.0, height: 60.0))
|
|
||||||
//strongSelf.layer.addSublayer(testLayer)
|
|
||||||
|
|
||||||
strongSelf.updateState()
|
strongSelf.updateState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -302,13 +401,11 @@ public final class MediaPlayerNode: ASDisplayNode {
|
|||||||
|
|
||||||
if let (takeFrameQueue, _) = self.takeFrameAndQueue {
|
if let (takeFrameQueue, _) = self.takeFrameAndQueue {
|
||||||
if let videoLayer = self.videoLayer {
|
if let videoLayer = self.videoLayer {
|
||||||
takeFrameQueue.async {
|
videoLayer.flushAndRemoveImage()
|
||||||
|
|
||||||
|
Queue.mainQueue().after(1.0, {
|
||||||
videoLayer.flushAndRemoveImage()
|
videoLayer.flushAndRemoveImage()
|
||||||
|
})
|
||||||
takeFrameQueue.after(1.0, {
|
|
||||||
videoLayer.flushAndRemoveImage()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -15,9 +15,13 @@ public struct HolesViewMedia: Comparable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct MessageOfInterestHole: Hashable, Equatable {
|
public struct MessageOfInterestHole: Hashable, Equatable, CustomStringConvertible {
|
||||||
public let hole: MessageHistoryViewHole
|
public let hole: MessageHistoryViewHole
|
||||||
public let direction: MessageHistoryViewRelativeHoleDirection
|
public let direction: MessageHistoryViewRelativeHoleDirection
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
return "hole: \(self.hole), direction: \(self.direction)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MessageOfInterestViewLocation: Hashable {
|
public enum MessageOfInterestViewLocation: Hashable {
|
||||||
@ -29,6 +33,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
|||||||
private let count: Int
|
private let count: Int
|
||||||
private var anchor: HistoryViewInputAnchor
|
private var anchor: HistoryViewInputAnchor
|
||||||
private var wrappedView: MutableMessageHistoryView
|
private var wrappedView: MutableMessageHistoryView
|
||||||
|
private var peerIds: MessageHistoryViewPeerIds
|
||||||
|
|
||||||
fileprivate var closestHole: MessageOfInterestHole?
|
fileprivate var closestHole: MessageOfInterestHole?
|
||||||
fileprivate var closestLaterMedia: [HolesViewMedia] = []
|
fileprivate var closestLaterMedia: [HolesViewMedia] = []
|
||||||
@ -44,6 +49,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
|||||||
mainPeerId = id
|
mainPeerId = id
|
||||||
peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil)
|
peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil)
|
||||||
}
|
}
|
||||||
|
self.peerIds = peerIds
|
||||||
var anchor: HistoryViewInputAnchor = .upperBound
|
var anchor: HistoryViewInputAnchor = .upperBound
|
||||||
if let combinedState = postbox.readStateTable.getCombinedState(mainPeerId), let state = combinedState.states.first, state.1.count != 0 {
|
if let combinedState = postbox.readStateTable.getCombinedState(mainPeerId), let state = combinedState.states.first, state.1.count != 0 {
|
||||||
switch state.1 {
|
switch state.1 {
|
||||||
@ -107,7 +113,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
|||||||
}
|
}
|
||||||
var anchor: HistoryViewInputAnchor = self.anchor
|
var anchor: HistoryViewInputAnchor = self.anchor
|
||||||
if transaction.alteredInitialPeerCombinedReadStates[peerId] != nil {
|
if transaction.alteredInitialPeerCombinedReadStates[peerId] != nil {
|
||||||
var anchor: HistoryViewInputAnchor = .upperBound
|
var updatedAnchor: HistoryViewInputAnchor = .upperBound
|
||||||
if let combinedState = postbox.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 {
|
if let combinedState = postbox.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 {
|
||||||
switch state.1 {
|
switch state.1 {
|
||||||
case let .idBased(maxIncomingReadId, _, _, _, _):
|
case let .idBased(maxIncomingReadId, _, _, _, _):
|
||||||
@ -116,6 +122,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
|||||||
anchor = .index(maxIncomingReadIndex)
|
anchor = .index(maxIncomingReadIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
anchor = updatedAnchor
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.anchor != anchor {
|
if self.anchor != anchor {
|
||||||
@ -128,6 +135,34 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
|||||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||||
return self.updateFromView()
|
return self.updateFromView()
|
||||||
} else if self.wrappedView.replay(postbox: postbox, transaction: transaction) {
|
} else if self.wrappedView.replay(postbox: postbox, transaction: transaction) {
|
||||||
|
var reloadView = false
|
||||||
|
if !transaction.currentPeerHoleOperations.isEmpty {
|
||||||
|
var allPeerIds: [PeerId]
|
||||||
|
switch peerIds {
|
||||||
|
case let .single(peerId):
|
||||||
|
allPeerIds = [peerId]
|
||||||
|
case let .associated(peerId, attachedMessageId):
|
||||||
|
allPeerIds = [peerId]
|
||||||
|
if let attachedMessageId = attachedMessageId {
|
||||||
|
allPeerIds.append(attachedMessageId.peerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (key, _) in transaction.currentPeerHoleOperations {
|
||||||
|
if allPeerIds.contains(key.peerId) {
|
||||||
|
reloadView = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reloadView {
|
||||||
|
let peerIds: MessageHistoryViewPeerIds
|
||||||
|
switch self.location {
|
||||||
|
case let .peer(id):
|
||||||
|
peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil)
|
||||||
|
}
|
||||||
|
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||||
|
}
|
||||||
|
|
||||||
return self.updateFromView()
|
return self.updateFromView()
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -132,6 +132,22 @@ public func arePeerDictionariesEqual(_ lhs: SimpleDictionary<PeerId, Peer>, _ rh
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func arePeerDictionariesEqual(_ lhs: [PeerId: Peer], _ rhs: [PeerId: Peer]) -> Bool {
|
||||||
|
if lhs.count != rhs.count {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (id, lhsPeer) in lhs {
|
||||||
|
if let rhsPeer = rhs[id] {
|
||||||
|
if !lhsPeer.isEqual(rhsPeer) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
public struct PeerSummaryCounterTags: OptionSet, Sequence, Hashable {
|
public struct PeerSummaryCounterTags: OptionSet, Sequence, Hashable {
|
||||||
public var rawValue: Int32
|
public var rawValue: Int32
|
||||||
|
|
||||||
|
|||||||
@ -427,7 +427,7 @@ private func privacyAndSecurityControllerEntries(presentationData: PresentationD
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil, focusOnItemTag: PrivacyAndSecurityEntryTag? = nil, activeSessionsContext: ActiveSessionsContext? = nil) -> ViewController {
|
public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil, focusOnItemTag: PrivacyAndSecurityEntryTag? = nil, activeSessionsContext: ActiveSessionsContext? = nil, webSessionsContext: WebSessionsContext? = nil) -> ViewController {
|
||||||
let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true)
|
let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: PrivacyAndSecurityControllerState())
|
let stateValue = Atomic(value: PrivacyAndSecurityControllerState())
|
||||||
let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in
|
let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in
|
||||||
@ -451,6 +451,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
|||||||
|
|
||||||
let blockedPeersContext = BlockedPeersContext(account: context.account)
|
let blockedPeersContext = BlockedPeersContext(account: context.account)
|
||||||
let activeSessionsContext = activeSessionsContext ?? ActiveSessionsContext(account: context.account)
|
let activeSessionsContext = activeSessionsContext ?? ActiveSessionsContext(account: context.account)
|
||||||
|
let webSessionsContext = webSessionsContext ?? WebSessionsContext(account: context.account)
|
||||||
|
|
||||||
let updateTwoStepAuthDisposable = MetaDisposable()
|
let updateTwoStepAuthDisposable = MetaDisposable()
|
||||||
actionsDisposable.add(updateTwoStepAuthDisposable)
|
actionsDisposable.add(updateTwoStepAuthDisposable)
|
||||||
@ -669,7 +670,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
|||||||
pushControllerImpl?(controller, true)
|
pushControllerImpl?(controller, true)
|
||||||
}
|
}
|
||||||
}, openActiveSessions: {
|
}, openActiveSessions: {
|
||||||
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext), true)
|
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext), true)
|
||||||
}, setupAccountAutoremove: {
|
}, setupAccountAutoremove: {
|
||||||
let signal = privacySettingsPromise.get()
|
let signal = privacySettingsPromise.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|
|||||||
@ -447,7 +447,7 @@ private func recentSessionsControllerEntries(presentationData: PresentationData,
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
public func recentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController {
|
public func recentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext, webSessionsContext: WebSessionsContext) -> ViewController {
|
||||||
let statePromise = ValuePromise(RecentSessionsControllerState(), ignoreRepeated: true)
|
let statePromise = ValuePromise(RecentSessionsControllerState(), ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: RecentSessionsControllerState())
|
let stateValue = Atomic(value: RecentSessionsControllerState())
|
||||||
let updateState: ((RecentSessionsControllerState) -> RecentSessionsControllerState) -> Void = { f in
|
let updateState: ((RecentSessionsControllerState) -> RecentSessionsControllerState) -> Void = { f in
|
||||||
@ -455,6 +455,7 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||||||
}
|
}
|
||||||
|
|
||||||
activeSessionsContext.loadMore()
|
activeSessionsContext.loadMore()
|
||||||
|
webSessionsContext.loadMore()
|
||||||
|
|
||||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
@ -545,33 +546,11 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||||||
return $0.withUpdatedRemovingSessionId(sessionId)
|
return $0.withUpdatedRemovingSessionId(sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
let applySessions: Signal<Void, NoError> = websitesPromise.get()
|
removeSessionDisposable.set(((webSessionsContext.remove(hash: sessionId)
|
||||||
|> filter { $0 != nil }
|
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||||
|> take(1)
|
return .complete()
|
||||||
|> deliverOnMainQueue
|
})
|
||||||
|> mapToSignal { websitesAndPeers -> Signal<Void, NoError> in
|
|> deliverOnMainQueue).start(error: { _ in
|
||||||
if let websites = websitesAndPeers?.0, let peers = websitesAndPeers?.1 {
|
|
||||||
var updatedWebsites = websites
|
|
||||||
for i in 0 ..< updatedWebsites.count {
|
|
||||||
if updatedWebsites[i].hash == sessionId {
|
|
||||||
updatedWebsites.remove(at: i)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if updatedWebsites.isEmpty {
|
|
||||||
mode.set(.sessions)
|
|
||||||
}
|
|
||||||
websitesPromise.set(.single((updatedWebsites, peers)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
|
|
||||||
removeSessionDisposable.set(((terminateWebSession(network: context.account.network, hash: sessionId)
|
|
||||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
|
||||||
return .complete()
|
|
||||||
}) |> then(applySessions) |> deliverOnMainQueue).start(error: { _ in
|
|
||||||
updateState {
|
updateState {
|
||||||
return $0.withUpdatedRemovingSessionId(nil)
|
return $0.withUpdatedRemovingSessionId(nil)
|
||||||
}
|
}
|
||||||
@ -595,7 +574,8 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||||||
return $0.withUpdatedTerminatingOtherSessions(true)
|
return $0.withUpdatedTerminatingOtherSessions(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
terminateOtherSessionsDisposable.set((terminateAllWebSessions(network: context.account.network) |> deliverOnMainQueue).start(error: { _ in
|
terminateOtherSessionsDisposable.set((webSessionsContext.removeAll()
|
||||||
|
|> deliverOnMainQueue).start(error: { _ in
|
||||||
updateState {
|
updateState {
|
||||||
return $0.withUpdatedTerminatingOtherSessions(false)
|
return $0.withUpdatedTerminatingOtherSessions(false)
|
||||||
}
|
}
|
||||||
@ -604,22 +584,18 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||||||
return $0.withUpdatedTerminatingOtherSessions(false)
|
return $0.withUpdatedTerminatingOtherSessions(false)
|
||||||
}
|
}
|
||||||
mode.set(.sessions)
|
mode.set(.sessions)
|
||||||
websitesPromise.set(.single(([], [:])))
|
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||||
])
|
])
|
||||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}, addDevice: {
|
}, addDevice: {
|
||||||
pushControllerImpl?(AuthDataTransferSplashScreen(context: context, activeSessionsContext: activeSessionsContext))
|
pushControllerImpl?(AuthDataTransferSplashScreen(context: context, activeSessionsContext: activeSessionsContext))
|
||||||
}, openOtherAppsUrl: {
|
}, openOtherAppsUrl: {
|
||||||
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://telegram.org/desktop", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://desktop.telegram.org", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
||||||
})
|
})
|
||||||
|
|
||||||
let websitesSignal: Signal<([WebAuthorization], [PeerId : Peer])?, NoError> = .single(nil) |> then(webSessions(network: context.account.network) |> map(Optional.init))
|
|
||||||
websitesPromise.set(websitesSignal)
|
|
||||||
|
|
||||||
let previousMode = Atomic<RecentSessionsMode>(value: .sessions)
|
let previousMode = Atomic<RecentSessionsMode>(value: .sessions)
|
||||||
|
|
||||||
let enableQRLogin = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
let enableQRLogin = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||||
@ -634,12 +610,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|
|
||||||
let signal = combineLatest(context.sharedContext.presentationData, mode.get(), statePromise.get(), activeSessionsContext.state, websitesPromise.get(), enableQRLogin)
|
let signal = combineLatest(context.sharedContext.presentationData, mode.get(), statePromise.get(), activeSessionsContext.state, webSessionsContext.state, enableQRLogin)
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> map { presentationData, mode, state, sessionsState, websitesAndPeers, enableQRLogin -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> map { presentationData, mode, state, sessionsState, websitesAndPeers, enableQRLogin -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||||
var rightNavigationButton: ItemListNavigationButton?
|
var rightNavigationButton: ItemListNavigationButton?
|
||||||
let websites = websitesAndPeers?.0
|
let websites = websitesAndPeers.sessions
|
||||||
let peers = websitesAndPeers?.1
|
let peers = websitesAndPeers.peers
|
||||||
|
|
||||||
if sessionsState.sessions.count > 1 {
|
if sessionsState.sessions.count > 1 {
|
||||||
if state.terminatingOtherSessions {
|
if state.terminatingOtherSessions {
|
||||||
@ -659,16 +635,16 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var emptyStateItem: ItemListControllerEmptyStateItem?
|
let emptyStateItem: ItemListControllerEmptyStateItem? = nil
|
||||||
if sessionsState.sessions.isEmpty {
|
/*if sessionsState.sessions.isEmpty {
|
||||||
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
|
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
|
||||||
} else if sessionsState.sessions.count == 1 && mode == .sessions {
|
} else if sessionsState.sessions.count == 1 && mode == .sessions {
|
||||||
emptyStateItem = RecentSessionsEmptyStateItem(theme: presentationData.theme, strings: presentationData.strings)
|
emptyStateItem = RecentSessionsEmptyStateItem(theme: presentationData.theme, strings: presentationData.strings)
|
||||||
}
|
}*/
|
||||||
|
|
||||||
let title: ItemListControllerTitle
|
let title: ItemListControllerTitle
|
||||||
let entries: [RecentSessionsEntry]
|
let entries: [RecentSessionsEntry]
|
||||||
if let websites = websites, !websites.isEmpty {
|
if !websites.isEmpty {
|
||||||
title = .sectionControl([presentationData.strings.AuthSessions_Sessions, presentationData.strings.AuthSessions_LoggedIn], mode.rawValue)
|
title = .sectionControl([presentationData.strings.AuthSessions_Sessions, presentationData.strings.AuthSessions_LoggedIn], mode.rawValue)
|
||||||
} else {
|
} else {
|
||||||
title = .text(presentationData.strings.AuthSessions_DevicesTitle)
|
title = .text(presentationData.strings.AuthSessions_DevicesTitle)
|
||||||
|
|||||||
@ -545,7 +545,7 @@ 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)))
|
||||||
}),
|
}),
|
||||||
SettingsSearchableItem(id: .privacy(9), title: strings.PrivacySettings_AuthSessions, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_AuthSessions), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
|
SettingsSearchableItem(id: .privacy(9), title: strings.PrivacySettings_AuthSessions, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_AuthSessions), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
|
||||||
present(.push, recentSessionsController(context: context, activeSessionsContext: ActiveSessionsContext(account: context.account)))
|
present(.push, recentSessionsController(context: context, activeSessionsContext: ActiveSessionsContext(account: context.account), webSessionsContext: WebSessionsContext(account: context.account)))
|
||||||
}),
|
}),
|
||||||
SettingsSearchableItem(id: .privacy(10), title: strings.PrivacySettings_DeleteAccountTitle, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_DeleteAccountIfAwayFor), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
|
SettingsSearchableItem(id: .privacy(10), 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)
|
||||||
|
|||||||
@ -671,7 +671,7 @@ private func settingsEntries(account: Account, presentationData: PresentationDat
|
|||||||
entries.append(.savedMessages(presentationData.theme, PresentationResourcesSettings.savedMessages, presentationData.strings.Settings_SavedMessages))
|
entries.append(.savedMessages(presentationData.theme, PresentationResourcesSettings.savedMessages, presentationData.strings.Settings_SavedMessages))
|
||||||
entries.append(.recentCalls(presentationData.theme, PresentationResourcesSettings.recentCalls, presentationData.strings.CallSettings_RecentCalls))
|
entries.append(.recentCalls(presentationData.theme, PresentationResourcesSettings.recentCalls, presentationData.strings.CallSettings_RecentCalls))
|
||||||
if enableQRLogin {
|
if enableQRLogin {
|
||||||
entries.append(.devices(presentationData.theme, UIImage(bundleImageName: "Settings/MenuIcons/Sessions")?.precomposed(), presentationData.strings.Settings_Devices, otherSessionCount == 0 ? presentationData.strings.Settings_AddDevice : "\(otherSessionCount)"))
|
entries.append(.devices(presentationData.theme, UIImage(bundleImageName: "Settings/MenuIcons/Sessions")?.precomposed(), presentationData.strings.Settings_Devices, otherSessionCount == 0 ? presentationData.strings.Settings_AddDevice : "\(otherSessionCount + 1)"))
|
||||||
} else {
|
} else {
|
||||||
entries.append(.stickers(presentationData.theme, PresentationResourcesSettings.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks))
|
entries.append(.stickers(presentationData.theme, PresentationResourcesSettings.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks))
|
||||||
}
|
}
|
||||||
@ -859,8 +859,9 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
|||||||
|
|
||||||
let activeSessionsContextAndCountSignal = contextValue.get()
|
let activeSessionsContextAndCountSignal = contextValue.get()
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> mapToSignal { context -> Signal<(ActiveSessionsContext, Int), NoError> in
|
|> mapToSignal { context -> Signal<(ActiveSessionsContext, Int, WebSessionsContext), NoError> in
|
||||||
let activeSessionsContext = ActiveSessionsContext(account: context.account)
|
let activeSessionsContext = ActiveSessionsContext(account: context.account)
|
||||||
|
let webSessionsContext = WebSessionsContext(account: context.account)
|
||||||
let otherSessionCount = activeSessionsContext.state
|
let otherSessionCount = activeSessionsContext.state
|
||||||
|> map { state -> Int in
|
|> map { state -> Int in
|
||||||
return state.sessions.filter({ !$0.isCurrent }).count
|
return state.sessions.filter({ !$0.isCurrent }).count
|
||||||
@ -868,10 +869,10 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
|||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
return otherSessionCount
|
return otherSessionCount
|
||||||
|> map { value in
|
|> map { value in
|
||||||
return (activeSessionsContext, value)
|
return (activeSessionsContext, value, webSessionsContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let activeSessionsContextAndCount = Promise<(ActiveSessionsContext, Int)>()
|
let activeSessionsContextAndCount = Promise<(ActiveSessionsContext, Int, WebSessionsContext)>()
|
||||||
activeSessionsContextAndCount.set(activeSessionsContextAndCountSignal)
|
activeSessionsContextAndCount.set(activeSessionsContextAndCountSignal)
|
||||||
|
|
||||||
let arguments = SettingsItemArguments(sharedContext: context.sharedContext, avatarAndNameInfoContext: avatarAndNameInfoContext, avatarTapAction: {
|
let arguments = SettingsItemArguments(sharedContext: context.sharedContext, avatarAndNameInfoContext: avatarAndNameInfoContext, avatarTapAction: {
|
||||||
@ -933,10 +934,10 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
|||||||
|> take(1)).start(next: { context in
|
|> take(1)).start(next: { context in
|
||||||
let _ = (activeSessionsContextAndCount.get()
|
let _ = (activeSessionsContextAndCount.get()
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> take(1)).start(next: { activeSessionsContext, _ in
|
|> take(1)).start(next: { activeSessionsContext, _, webSessionsContext in
|
||||||
pushControllerImpl?(privacyAndSecurityController(context: context, initialSettings: privacySettingsValue, updatedSettings: { settings in
|
pushControllerImpl?(privacyAndSecurityController(context: context, initialSettings: privacySettingsValue, updatedSettings: { settings in
|
||||||
privacySettings.set(.single(settings))
|
privacySettings.set(.single(settings))
|
||||||
}, activeSessionsContext: activeSessionsContext))
|
}, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, openDataAndStorage: {
|
}, openDataAndStorage: {
|
||||||
@ -1108,11 +1109,11 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
|||||||
}, openDevices: {
|
}, openDevices: {
|
||||||
let _ = (activeSessionsContextAndCount.get()
|
let _ = (activeSessionsContextAndCount.get()
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> take(1)).start(next: { activeSessionsContext, count in
|
|> take(1)).start(next: { activeSessionsContext, count, webSessionsContext in
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
pushControllerImpl?(AuthDataTransferSplashScreen(context: context, activeSessionsContext: activeSessionsContext))
|
pushControllerImpl?(AuthDataTransferSplashScreen(context: context, activeSessionsContext: activeSessionsContext))
|
||||||
} else {
|
} else {
|
||||||
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext))
|
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1761,7 +1761,13 @@ private func resetChannels(network: Network, peers: [Peer], state: AccountMutabl
|
|||||||
|
|
||||||
private func pollChannel(network: Network, peer: Peer, state: AccountMutableState) -> Signal<(AccountMutableState, Bool, Int32?), NoError> {
|
private func pollChannel(network: Network, peer: Peer, state: AccountMutableState) -> Signal<(AccountMutableState, Bool, Int32?), NoError> {
|
||||||
if let inputChannel = apiInputChannel(peer) {
|
if let inputChannel = apiInputChannel(peer) {
|
||||||
let limit: Int32 = 20
|
let limit: Int32
|
||||||
|
#if DEBUG
|
||||||
|
limit = 1
|
||||||
|
#else
|
||||||
|
limit = 20
|
||||||
|
#endif
|
||||||
|
|
||||||
let pollPts: Int32
|
let pollPts: Int32
|
||||||
if let channelState = state.chatStates[peer.id] as? ChannelState {
|
if let channelState = state.chatStates[peer.id] as? ChannelState {
|
||||||
pollPts = channelState.pts
|
pollPts = channelState.pts
|
||||||
|
|||||||
@ -97,3 +97,111 @@ public final class ActiveSessionsContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct WebSessionsContextState: Equatable {
|
||||||
|
public var isLoadingMore: Bool
|
||||||
|
public var sessions: [WebAuthorization]
|
||||||
|
public var peers: [PeerId: Peer]
|
||||||
|
|
||||||
|
public static func ==(lhs: WebSessionsContextState, rhs: WebSessionsContextState) -> Bool {
|
||||||
|
if lhs.isLoadingMore != rhs.isLoadingMore {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.sessions != rhs.sessions {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !arePeerDictionariesEqual(lhs.peers, rhs.peers) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class WebSessionsContext {
|
||||||
|
private let account: Account
|
||||||
|
private var _state: WebSessionsContextState {
|
||||||
|
didSet {
|
||||||
|
if self._state != oldValue {
|
||||||
|
self._statePromise.set(.single(self._state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private let _statePromise = Promise<WebSessionsContextState>()
|
||||||
|
public var state: Signal<WebSessionsContextState, NoError> {
|
||||||
|
return self._statePromise.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
private let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
public init(account: Account) {
|
||||||
|
assert(Queue.mainQueue().isCurrent())
|
||||||
|
|
||||||
|
self.account = account
|
||||||
|
self._state = WebSessionsContextState(isLoadingMore: false, sessions: [], peers: [:])
|
||||||
|
self._statePromise.set(.single(self._state))
|
||||||
|
|
||||||
|
self.loadMore()
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
assert(Queue.mainQueue().isCurrent())
|
||||||
|
self.disposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func loadMore() {
|
||||||
|
assert(Queue.mainQueue().isCurrent())
|
||||||
|
|
||||||
|
if self._state.isLoadingMore {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self._state = WebSessionsContextState(isLoadingMore: true, sessions: self._state.sessions, peers: self._state.peers)
|
||||||
|
self.disposable.set((webSessions(network: account.network)
|
||||||
|
|> map { result -> (sessions: [WebAuthorization], peers: [PeerId: Peer], canLoadMore: Bool) in
|
||||||
|
return (result.0, result.1, false)
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] (sessions, peers, canLoadMore) in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf._state = WebSessionsContextState(isLoadingMore: false, sessions: sessions, peers: peers)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func remove(hash: Int64) -> Signal<Never, NoError> {
|
||||||
|
assert(Queue.mainQueue().isCurrent())
|
||||||
|
|
||||||
|
return terminateWebSession(network: self.account.network, hash: hash)
|
||||||
|
|> deliverOnMainQueue
|
||||||
|
|> mapToSignal { [weak self] _ -> Signal<Never, NoError> in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
var mergedSessions = strongSelf._state.sessions
|
||||||
|
for i in 0 ..< mergedSessions.count {
|
||||||
|
if mergedSessions[i].hash == hash {
|
||||||
|
mergedSessions.remove(at: i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf._state = WebSessionsContextState(isLoadingMore: strongSelf._state.isLoadingMore, sessions: mergedSessions, peers: strongSelf._state.peers)
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func removeAll() -> Signal<Never, NoError> {
|
||||||
|
return terminateAllWebSessions(network: self.account.network)
|
||||||
|
|> deliverOnMainQueue
|
||||||
|
|> mapToSignal { [weak self] _ -> Signal<Never, NoError> in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf._state = WebSessionsContextState(isLoadingMore: strongSelf._state.isLoadingMore, sessions: [], peers: [:])
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import SwiftSignalKit
|
|||||||
|
|
||||||
import SyncCore
|
import SyncCore
|
||||||
|
|
||||||
public struct HistoryPreloadIndex: Comparable {
|
public struct HistoryPreloadIndex: Comparable, CustomStringConvertible {
|
||||||
public let index: ChatListIndex?
|
public let index: ChatListIndex?
|
||||||
public let hasUnread: Bool
|
public let hasUnread: Bool
|
||||||
public let isMuted: Bool
|
public let isMuted: Bool
|
||||||
@ -49,9 +49,13 @@ public struct HistoryPreloadIndex: Comparable {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
return "index: \(String(describing: self.index)), hasUnread: \(self.hasUnread), isMuted: \(self.isMuted), isPriority: \(self.isPriority)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct HistoryPreloadHole: Hashable, Comparable {
|
private struct HistoryPreloadHole: Hashable, Comparable, CustomStringConvertible {
|
||||||
let preloadIndex: HistoryPreloadIndex
|
let preloadIndex: HistoryPreloadIndex
|
||||||
let hole: MessageOfInterestHole
|
let hole: MessageOfInterestHole
|
||||||
|
|
||||||
@ -66,6 +70,10 @@ private struct HistoryPreloadHole: Hashable, Comparable {
|
|||||||
var hashValue: Int {
|
var hashValue: Int {
|
||||||
return self.preloadIndex.index.hashValue &* 31 &+ self.hole.hashValue
|
return self.preloadIndex.index.hashValue &* 31 &+ self.hole.hashValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
return "(preloadIndex: \(self.preloadIndex), hole: \(self.hole))"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class HistoryPreloadEntry: Comparable {
|
private final class HistoryPreloadEntry: Comparable {
|
||||||
@ -90,6 +98,9 @@ private final class HistoryPreloadEntry: Comparable {
|
|||||||
self.isStarted = true
|
self.isStarted = true
|
||||||
|
|
||||||
let hole = self.hole.hole
|
let hole = self.hole.hole
|
||||||
|
|
||||||
|
Logger.shared.log("HistoryPreload", "start hole \(hole)")
|
||||||
|
|
||||||
let signal: Signal<Never, NoError> = .complete()
|
let signal: Signal<Never, NoError> = .complete()
|
||||||
|> delay(0.3, queue: queue)
|
|> delay(0.3, queue: queue)
|
||||||
|> then(
|
|> then(
|
||||||
@ -98,8 +109,8 @@ private final class HistoryPreloadEntry: Comparable {
|
|||||||
|> deliverOn(queue)
|
|> deliverOn(queue)
|
||||||
|> mapToSignal { download -> Signal<Never, NoError> in
|
|> mapToSignal { download -> Signal<Never, NoError> in
|
||||||
switch hole.hole {
|
switch hole.hole {
|
||||||
case let .peer(peerHole):
|
case let .peer(peerHole):
|
||||||
return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerId: peerHole.peerId, namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, count: 60)
|
return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerId: peerHole.peerId, namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, count: 60)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -421,6 +432,14 @@ final class ChatHistoryPreloadManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let updatedHole = view.currentHole
|
let updatedHole = view.currentHole
|
||||||
|
|
||||||
|
let holeIsUpdated = previousHole != updatedHole
|
||||||
|
|
||||||
|
switch index.entity {
|
||||||
|
case let .peer(peerId):
|
||||||
|
Logger.shared.log("HistoryPreload", "view \(peerId) hole \(updatedHole) isUpdated: \(holeIsUpdated)")
|
||||||
|
}
|
||||||
|
|
||||||
if previousHole != updatedHole {
|
if previousHole != updatedHole {
|
||||||
strongSelf.update(from: previousHole, to: updatedHole)
|
strongSelf.update(from: previousHole, to: updatedHole)
|
||||||
}
|
}
|
||||||
@ -448,7 +467,11 @@ final class ChatHistoryPreloadManager {
|
|||||||
|
|
||||||
private func update(from previousHole: HistoryPreloadHole?, to updatedHole: HistoryPreloadHole?) {
|
private func update(from previousHole: HistoryPreloadHole?, to updatedHole: HistoryPreloadHole?) {
|
||||||
assert(self.queue.isCurrent())
|
assert(self.queue.isCurrent())
|
||||||
if previousHole == updatedHole {
|
let isHoleUpdated = previousHole != updatedHole
|
||||||
|
|
||||||
|
Logger.shared.log("HistoryPreload", "update from \(String(describing: previousHole)) to \(String(describing: updatedHole)), isUpdated: \(isHoleUpdated)")
|
||||||
|
|
||||||
|
if !isHoleUpdated {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,9 +505,12 @@ final class ChatHistoryPreloadManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.canPreloadHistoryValue {
|
if self.canPreloadHistoryValue {
|
||||||
|
Logger.shared.log("HistoryPreload", "will start")
|
||||||
for i in 0 ..< min(3, self.entries.count) {
|
for i in 0 ..< min(3, self.entries.count) {
|
||||||
self.entries[i].startIfNeeded(postbox: self.postbox, accountPeerId: self.accountPeerId, download: self.download.get() |> take(1), queue: self.queue)
|
self.entries[i].startIfNeeded(postbox: self.postbox, accountPeerId: self.accountPeerId, download: self.download.get() |> take(1), queue: self.queue)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Logger.shared.log("HistoryPreload", "will not start, canPreloadHistoryValue = false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -288,7 +288,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
|||||||
|
|
||||||
return signal |> then(contextBot)
|
return signal |> then(contextBot)
|
||||||
case let .emojiSearch(query, languageCode, range):
|
case let .emojiSearch(query, languageCode, range):
|
||||||
var signal = searchEmojiKeywords(postbox: context.account.postbox, inputLanguageCode: languageCode, query: query, completeMatch: query.count < 3)
|
var signal = searchEmojiKeywords(postbox: context.account.postbox, inputLanguageCode: languageCode, query: query, completeMatch: query.count < 2)
|
||||||
if !languageCode.lowercased().hasPrefix("en") {
|
if !languageCode.lowercased().hasPrefix("en") {
|
||||||
signal = signal
|
signal = signal
|
||||||
|> mapToSignal { keywords in
|
|> mapToSignal { keywords in
|
||||||
|
|||||||
@ -103,6 +103,7 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
|
|
||||||
private var enqueuedTransitions: [(EmojisChatInputContextPanelTransition, Bool)] = []
|
private var enqueuedTransitions: [(EmojisChatInputContextPanelTransition, Bool)] = []
|
||||||
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
|
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
|
||||||
|
private var presentationInterfaceState: ChatPresentationInterfaceState?
|
||||||
|
|
||||||
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings) {
|
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
self.backgroundNode = ASImageNode()
|
self.backgroundNode = ASImageNode()
|
||||||
@ -191,6 +192,10 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
})
|
})
|
||||||
self.currentEntries = to
|
self.currentEntries = to
|
||||||
self.enqueueTransition(transition, firstTime: firstTime)
|
self.enqueueTransition(transition, firstTime: firstTime)
|
||||||
|
|
||||||
|
if let presentationInterfaceState = presentationInterfaceState, let (size, leftInset, rightInset, bottomInset) = self.validLayout {
|
||||||
|
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, transition: .immediate, interfaceState: presentationInterfaceState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func enqueueTransition(_ transition: EmojisChatInputContextPanelTransition, firstTime: Bool) {
|
private func enqueueTransition(_ transition: EmojisChatInputContextPanelTransition, firstTime: Bool) {
|
||||||
@ -208,12 +213,7 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
self.enqueuedTransitions.remove(at: 0)
|
self.enqueuedTransitions.remove(at: 0)
|
||||||
|
|
||||||
var options = ListViewDeleteAndInsertOptions()
|
var options = ListViewDeleteAndInsertOptions()
|
||||||
if firstTime {
|
options.insert(.Synchronous)
|
||||||
//options.insert(.Synchronous)
|
|
||||||
//options.insert(.LowLatency)
|
|
||||||
} else {
|
|
||||||
options.insert(.AnimateCrossfade)
|
|
||||||
}
|
|
||||||
|
|
||||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: validLayout.0, insets: UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0), duration: 0.0, curve: .Default(duration: nil))
|
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: validLayout.0, insets: UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0), duration: 0.0, curve: .Default(duration: nil))
|
||||||
|
|
||||||
@ -224,6 +224,7 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
|
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
|
||||||
let hadValidLayout = self.validLayout != nil
|
let hadValidLayout = self.validLayout != nil
|
||||||
self.validLayout = (size, leftInset, rightInset, bottomInset)
|
self.validLayout = (size, leftInset, rightInset, bottomInset)
|
||||||
|
self.presentationInterfaceState = interfaceState
|
||||||
|
|
||||||
let sideInsets: CGFloat = 10.0 + leftInset
|
let sideInsets: CGFloat = 10.0 + leftInset
|
||||||
let contentWidth = min(size.width - sideInsets - sideInsets, max(24.0, CGFloat(self.currentEntries?.count ?? 0) * 45.0))
|
let contentWidth = min(size.width - sideInsets - sideInsets, max(24.0, CGFloat(self.currentEntries?.count ?? 0) * 45.0))
|
||||||
|
|||||||
@ -500,6 +500,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
} else if parsedUrl.host == "login" {
|
} else if parsedUrl.host == "login" {
|
||||||
if let components = URLComponents(string: "/?" + query) {
|
if let components = URLComponents(string: "/?" + query) {
|
||||||
var code: String?
|
var code: String?
|
||||||
|
var isToken: Bool = false
|
||||||
if let queryItems = components.queryItems {
|
if let queryItems = components.queryItems {
|
||||||
for queryItem in queryItems {
|
for queryItem in queryItems {
|
||||||
if let value = queryItem.value {
|
if let value = queryItem.value {
|
||||||
@ -507,8 +508,18 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
code = value
|
code = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if queryItem.name == "token" {
|
||||||
|
isToken = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if isToken {
|
||||||
|
context.sharedContext.presentGlobalController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.AuthSessions_AddDevice_UrlLoginHint, actions: [
|
||||||
|
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||||
|
}),
|
||||||
|
], parseMarkdown: true), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
if let code = code {
|
if let code = code {
|
||||||
convertedUrl = "https://t.me/login/\(code)"
|
convertedUrl = "https://t.me/login/\(code)"
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -448,12 +448,12 @@ public final class WalletStrings: Equatable {
|
|||||||
public var Wallet_SecureStorageReset_Title: String { return self._s[218]! }
|
public var Wallet_SecureStorageReset_Title: String { return self._s[218]! }
|
||||||
public var Wallet_Receive_CommentHeader: String { return self._s[219]! }
|
public var Wallet_Receive_CommentHeader: String { return self._s[219]! }
|
||||||
public var Wallet_Info_ReceiveGrams: String { return self._s[220]! }
|
public var Wallet_Info_ReceiveGrams: String { return self._s[220]! }
|
||||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||||
let form = getPluralizationForm(self.lc, value)
|
let form = getPluralizationForm(self.lc, value)
|
||||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||||
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
|
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
|
||||||
}
|
}
|
||||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||||
let form = getPluralizationForm(self.lc, value)
|
let form = getPluralizationForm(self.lc, value)
|
||||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||||
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)
|
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user