mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '7357c597070cb3c5fd200a20b90c1ad881f29da9'
This commit is contained in:
commit
b4e1173279
@ -12063,7 +12063,14 @@ Sorry for the inconvenience.";
|
||||
"Login.EnterPhraseBeginningText" = "We've sent you an SMS with a secret phrase that starts with **\"%1$@\"** to your phone **%2$@**.";
|
||||
"Login.EnterPhrasePlaceholder" = "Enter Phrase from SMS";
|
||||
|
||||
"Login.EnterPhraseHint" = "You can copy and paste the phrase here.";
|
||||
"Login.BackToWord" = "Back to entering word";
|
||||
"Login.BackToPhrase" = "Back to entering phrase";
|
||||
|
||||
"Login.WrongPhraseError" = "Incorrect, please try again.";
|
||||
"Login.Paste" = "Paste";
|
||||
|
||||
"Login.ReturnToCode" = "Return to entering the code";
|
||||
|
||||
"Map.LiveLocationPrivateNewDescription" = "Choose for how long **%@** will see your accurate location, including when the app is closed.";
|
||||
"Map.LiveLocationGroupNewDescription" = "Choose for how long people in this chat will see your accurate location, including when the app is closed.";
|
||||
@ -12077,3 +12084,8 @@ Sorry for the inconvenience.";
|
||||
"Map.LiveLocationForHours_any" = "For %@ hours";
|
||||
|
||||
"Map.LiveLocationIndefinite" = "Until I turn it off";
|
||||
|
||||
"Map.TapToAddTime" = "tap to add time";
|
||||
"Map.SharingLocation" = "Sharing Location...";
|
||||
|
||||
"Channel.AdminLog.Settings" = "Settings";
|
||||
|
@ -23,10 +23,11 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
|
||||
var reset: (() -> Void)?
|
||||
public var requestNextOption: (() -> Void)?
|
||||
public var requestPreviousOption: (() -> Void)?
|
||||
var resetEmail: (() -> Void)?
|
||||
var retryResetEmail: (() -> Void)?
|
||||
|
||||
var data: (String, String?, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?)?
|
||||
var data: (String, String?, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?, Bool, Bool)?
|
||||
var termsOfService: (UnauthorizedAccountTermsOfService, Bool)?
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
@ -40,6 +41,19 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
var isWordOrPhrase: Bool {
|
||||
if let type = self.data?.2 {
|
||||
switch type {
|
||||
case .word, .phrase:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public init(presentationData: PresentationData, back: @escaping () -> Void) {
|
||||
self.strings = presentationData.strings
|
||||
self.theme = presentationData.theme
|
||||
@ -60,7 +74,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
let proceed: String
|
||||
let stop: String
|
||||
|
||||
if let (_, _, type, _, _) = self?.data, case .email = type {
|
||||
if let (_, _, type, _, _, _, _) = self?.data, case .email = type {
|
||||
text = presentationData.strings.Login_CancelEmailVerification
|
||||
proceed = presentationData.strings.Login_CancelEmailVerificationContinue
|
||||
stop = presentationData.strings.Login_CancelEmailVerificationStop
|
||||
@ -107,6 +121,10 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
self?.requestNextOption?()
|
||||
}
|
||||
|
||||
self.controllerNode.requestPreviousOption = { [weak self] in
|
||||
self?.requestPreviousOption?()
|
||||
}
|
||||
|
||||
self.controllerNode.updateNextEnabled = { [weak self] value in
|
||||
self?.navigationItem.rightBarButtonItem?.isEnabled = value
|
||||
}
|
||||
@ -123,12 +141,12 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
|
||||
if let (number, email, codeType, nextType, timeout) = self.data {
|
||||
if let (number, email, codeType, nextType, timeout, hasPreviousCode, previousIsPhrase) = self.data {
|
||||
var appleSignInAllowed = false
|
||||
if case let .email(_, _, _, _, appleSignInAllowedValue, _) = codeType {
|
||||
appleSignInAllowed = appleSignInAllowedValue
|
||||
}
|
||||
self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed)
|
||||
self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed, hasPreviousCode: hasPreviousCode, previousIsPhrase: previousIsPhrase)
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,6 +173,10 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
self.controllerNode.animateError(text: text)
|
||||
}
|
||||
|
||||
func updateAppIsActive(_ isActive: Bool) {
|
||||
self.controllerNode.updatePasteVisibility()
|
||||
}
|
||||
|
||||
func updateNavigationItems() {
|
||||
guard let layout = self.validLayout, layout.size.width < 360.0 else {
|
||||
return
|
||||
@ -168,10 +190,10 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) {
|
||||
public func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?, hasPreviousCode: Bool, previousIsPhrase: Bool) {
|
||||
self.termsOfService = termsOfService
|
||||
if self.data?.0 != number || self.data?.1 != email || self.data?.2 != codeType || self.data?.3 != nextType || self.data?.4 != timeout {
|
||||
self.data = (number, email, codeType, nextType, timeout)
|
||||
if self.data?.0 != number || self.data?.1 != email || self.data?.2 != codeType || self.data?.3 != nextType || self.data?.4 != timeout || self.data?.5 != hasPreviousCode || self.data?.6 != previousIsPhrase {
|
||||
self.data = (number, email, codeType, nextType, timeout, hasPreviousCode, previousIsPhrase)
|
||||
|
||||
var appleSignInAllowed = false
|
||||
if case let .email(_, _, _, _, appleSignInAllowedValue, _) = codeType {
|
||||
@ -179,7 +201,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
}
|
||||
|
||||
if self.isNodeLoaded {
|
||||
self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed)
|
||||
self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed, hasPreviousCode: hasPreviousCode, previousIsPhrase: previousIsPhrase)
|
||||
self.requestLayout(transition: .immediate)
|
||||
}
|
||||
}
|
||||
@ -199,7 +221,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
}
|
||||
|
||||
@objc private func nextPressed() {
|
||||
guard let (_, _, type, _, _) = self.data else {
|
||||
guard let (_, _, type, _, _, _, _) = self.data else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
private let currentOptionInfoActivateAreaNode: AccessibilityAreaNode
|
||||
private let nextOptionTitleNode: ImmediateTextNode
|
||||
private let nextOptionButtonNode: HighlightableButtonNode
|
||||
private let nextOptionArrowNode: ASImageNode
|
||||
|
||||
private let resetTextNode: ImmediateTextNode
|
||||
private let resetNode: HighlightableButtonNode
|
||||
@ -41,10 +42,15 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
|
||||
private let textField: TextFieldNode
|
||||
private let textSeparatorNode: ASDisplayNode
|
||||
private let pasteButton: HighlightableButtonNode
|
||||
|
||||
private let codeInputView: CodeInputView
|
||||
private let errorTextNode: ImmediateTextNode
|
||||
|
||||
private let hintButtonNode: HighlightTrackingButtonNode
|
||||
private let hintTextNode: ImmediateTextNode
|
||||
private let hintArrowNode: ASImageNode
|
||||
|
||||
private var codeType: SentAuthorizationCodeType?
|
||||
|
||||
private let countdownDisposable = MetaDisposable()
|
||||
@ -53,6 +59,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
private var layoutArguments: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private var appleSignInAllowed = false
|
||||
private var hasPreviousCode = false
|
||||
private var previousIsPhrase = false
|
||||
|
||||
var phoneNumber: String = "" {
|
||||
didSet {
|
||||
@ -82,6 +90,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
|
||||
var requestNextOption: (() -> Void)?
|
||||
var requestAnotherOption: (() -> Void)?
|
||||
var requestPreviousOption: (() -> Void)?
|
||||
var updateNextEnabled: ((Bool) -> Void)?
|
||||
var reset: (() -> Void)?
|
||||
var retryReset: (() -> Void)?
|
||||
@ -128,6 +137,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
self.titleIconNode.displaysAsynchronously = false
|
||||
|
||||
self.currentOptionNode = ImmediateTextNodeWithEntities()
|
||||
self.currentOptionNode.balancedTextLayout = true
|
||||
self.currentOptionNode.isUserInteractionEnabled = false
|
||||
self.currentOptionNode.displaysAsynchronously = false
|
||||
self.currentOptionNode.lineSpacing = 0.1
|
||||
@ -159,6 +169,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
}
|
||||
self.nextOptionButtonNode.addSubnode(self.nextOptionTitleNode)
|
||||
|
||||
self.nextOptionArrowNode = ASImageNode()
|
||||
self.nextOptionArrowNode.displaysAsynchronously = false
|
||||
self.nextOptionArrowNode.isUserInteractionEnabled = false
|
||||
|
||||
self.codeInputView = CodeInputView()
|
||||
self.codeInputView.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
|
||||
self.codeInputView.textField.returnKeyType = .done
|
||||
@ -194,10 +208,25 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
self.textField.textField.disableAutomaticKeyboardHandling = [.forward, .backward]
|
||||
self.textField.textField.tintColor = self.theme.list.itemAccentColor
|
||||
|
||||
self.pasteButton = HighlightableButtonNode()
|
||||
|
||||
self.errorTextNode = ImmediateTextNode()
|
||||
self.errorTextNode.alpha = 0.0
|
||||
self.errorTextNode.displaysAsynchronously = false
|
||||
self.errorTextNode.textAlignment = .center
|
||||
self.errorTextNode.isUserInteractionEnabled = false
|
||||
|
||||
self.hintButtonNode = HighlightableButtonNode()
|
||||
self.hintButtonNode.alpha = 0.0
|
||||
self.hintButtonNode.isUserInteractionEnabled = false
|
||||
|
||||
self.hintTextNode = ImmediateTextNode()
|
||||
self.hintTextNode.displaysAsynchronously = false
|
||||
self.hintTextNode.textAlignment = .center
|
||||
self.hintTextNode.isUserInteractionEnabled = false
|
||||
|
||||
self.hintArrowNode = ASImageNode()
|
||||
self.hintArrowNode.displaysAsynchronously = false
|
||||
|
||||
self.resetNode = HighlightableButtonNode()
|
||||
self.resetNode.displaysAsynchronously = false
|
||||
@ -238,6 +267,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
self.addSubnode(self.codeInputView)
|
||||
self.addSubnode(self.textSeparatorNode)
|
||||
self.addSubnode(self.textField)
|
||||
self.addSubnode(self.pasteButton)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.titleActivateAreaNode)
|
||||
self.addSubnode(self.titleIconNode)
|
||||
@ -245,11 +275,15 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
self.addSubnode(self.currentOptionActivateAreaNode)
|
||||
self.addSubnode(self.currentOptionInfoNode)
|
||||
self.addSubnode(self.nextOptionButtonNode)
|
||||
self.nextOptionButtonNode.addSubnode(self.nextOptionArrowNode)
|
||||
self.addSubnode(self.animationNode)
|
||||
self.addSubnode(self.resetNode)
|
||||
self.addSubnode(self.resetTextNode)
|
||||
self.addSubnode(self.dividerNode)
|
||||
self.addSubnode(self.errorTextNode)
|
||||
self.addSubnode(self.hintButtonNode)
|
||||
self.hintButtonNode.addSubnode(self.hintTextNode)
|
||||
self.hintButtonNode.addSubnode(self.hintArrowNode)
|
||||
self.addSubnode(self.proceedNode)
|
||||
|
||||
self.codeInputView.updated = { [weak self] in
|
||||
@ -295,6 +329,18 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
}
|
||||
self.signInWithAppleButton?.addTarget(self, action: #selector(self.signInWithApplePressed), for: .touchUpInside)
|
||||
self.resetNode.addTarget(self, action: #selector(self.resetPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.pasteButton.setTitle(strings.Login_Paste, with: Font.medium(13.0), with: theme.list.itemAccentColor, for: .normal)
|
||||
self.pasteButton.setBackgroundImage(generateStretchableFilledCircleImage(radius: 12.0, color: theme.list.itemAccentColor.withAlphaComponent(0.1)), for: .normal)
|
||||
self.pasteButton.addTarget(self, action: #selector(self.pastePressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.hintButtonNode.addTarget(self, action: #selector(self.previousOptionNodePressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.hintArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: theme.list.itemAccentColor)
|
||||
self.hintArrowNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
self.nextOptionArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: theme.list.itemAccentColor)
|
||||
|
||||
self.updatePasteVisibility()
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -309,6 +355,18 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func pastePressed() {
|
||||
if let text = UIPasteboard.general.string, !text.isEmpty {
|
||||
if checkValidity(text: text) {
|
||||
self.textField.textField.text = text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updatePasteVisibility() {
|
||||
self.pasteButton.isHidden = !UIPasteboard.general.hasStrings
|
||||
}
|
||||
|
||||
func updateCode(_ code: String) {
|
||||
self.codeInputView.text = code
|
||||
self.codeChanged(text: code)
|
||||
@ -343,10 +401,12 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
self.codeInputView.text = ""
|
||||
}
|
||||
|
||||
func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, appleSignInAllowed: Bool) {
|
||||
func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, appleSignInAllowed: Bool, hasPreviousCode: Bool, previousIsPhrase: Bool) {
|
||||
self.codeType = codeType
|
||||
self.phoneNumber = number
|
||||
self.email = email
|
||||
self.hasPreviousCode = hasPreviousCode
|
||||
self.previousIsPhrase = previousIsPhrase
|
||||
|
||||
var appleSignInAllowed = appleSignInAllowed
|
||||
if #available(iOS 13.0, *) {
|
||||
@ -369,6 +429,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
self.currentOptionInfoActivateAreaNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
if let timeout = timeout {
|
||||
#if DEBUG
|
||||
let timeout = min(timeout, 5)
|
||||
@ -378,7 +439,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
if let strongSelf = self {
|
||||
if let currentTimeoutTime = strongSelf.currentTimeoutTime, currentTimeoutTime > 0 {
|
||||
strongSelf.currentTimeoutTime = currentTimeoutTime - 1
|
||||
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, timeout: strongSelf.currentTimeoutTime, strings: strongSelf.strings, primaryColor: strongSelf.theme.list.itemPrimaryTextColor, accentColor: strongSelf.theme.list.itemAccentColor)
|
||||
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, returnToCode: hasPreviousCode, timeout: strongSelf.currentTimeoutTime, strings: strongSelf.strings, primaryColor: strongSelf.theme.list.itemPrimaryTextColor, accentColor: strongSelf.theme.list.itemAccentColor)
|
||||
strongSelf.nextOptionTitleNode.attributedText = nextOptionText
|
||||
strongSelf.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive
|
||||
strongSelf.nextOptionButtonNode.accessibilityLabel = nextOptionText.string
|
||||
@ -418,7 +479,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
self.currentTimeoutTime = nil
|
||||
self.countdownDisposable.set(nil)
|
||||
}
|
||||
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, timeout: self.currentTimeoutTime, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor)
|
||||
|
||||
let (nextOptionText, nextOptionActive) = authorizationNextOptionText(currentType: codeType, nextType: nextType, returnToCode: hasPreviousCode, timeout: self.currentTimeoutTime, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor)
|
||||
self.nextOptionTitleNode.attributedText = nextOptionText
|
||||
self.nextOptionButtonNode.isUserInteractionEnabled = nextOptionActive
|
||||
self.nextOptionButtonNode.accessibilityLabel = nextOptionText.string
|
||||
@ -427,6 +489,14 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
} else {
|
||||
self.nextOptionButtonNode.accessibilityTraits = [.button, .notEnabled]
|
||||
}
|
||||
|
||||
switch codeType {
|
||||
case .word, .phrase:
|
||||
self.nextOptionArrowNode.isHidden = !hasPreviousCode
|
||||
default:
|
||||
self.nextOptionArrowNode.isHidden = true
|
||||
}
|
||||
|
||||
if let layoutArguments = self.layoutArguments {
|
||||
self.containerLayoutUpdated(layoutArguments.0, navigationBarHeight: layoutArguments.1, transition: .immediate)
|
||||
}
|
||||
@ -699,7 +769,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
|
||||
switch codeType {
|
||||
case .word, .phrase:
|
||||
additionalBottomInset = 120.0
|
||||
additionalBottomInset = 100.0
|
||||
|
||||
self.nextOptionButtonNode.isHidden = false
|
||||
items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
|
||||
@ -719,13 +789,6 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
self.proceedNode.isHidden = true
|
||||
items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
|
||||
}
|
||||
|
||||
if case .email = codeType {
|
||||
self.nextOptionButtonNode.isHidden = true
|
||||
} else {
|
||||
self.nextOptionButtonNode.isHidden = false
|
||||
items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -740,8 +803,57 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
|
||||
let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - additionalBottomInset)), items: items, transition: transition, failIfDoesNotFit: false)
|
||||
|
||||
if let codeType = self.codeType {
|
||||
let yOffset: CGFloat = layout.size.width > 320.0 ? 18.0 : 5.0
|
||||
if case .phrase = codeType {
|
||||
self.hintButtonNode.alpha = 1.0
|
||||
self.hintButtonNode.isUserInteractionEnabled = false
|
||||
|
||||
self.hintTextNode.attributedText = NSAttributedString(string: self.strings.Login_EnterPhraseHint, font: Font.regular(13.0), textColor: self.theme.list.itemSecondaryTextColor, paragraphAlignment: .center)
|
||||
|
||||
let hintTextSize = self.hintTextNode.updateLayout(CGSize(width: layout.size.width - 48.0, height: .greatestFiniteMagnitude))
|
||||
transition.updateFrame(node: self.hintButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - hintTextSize.width) / 2.0), y: self.textField.frame.maxY + yOffset), size: hintTextSize))
|
||||
self.hintTextNode.frame = CGRect(origin: .zero, size: hintTextSize)
|
||||
|
||||
let pasteSize = self.pasteButton.measure(layout.size)
|
||||
let pasteButtonSize = CGSize(width: pasteSize.width + 16.0, height: 24.0)
|
||||
transition.updateFrame(node: self.pasteButton, frame: CGRect(origin: CGPoint(x: layout.size.width - 40.0 - pasteButtonSize.width, y: self.textField.frame.midY - pasteButtonSize.height / 2.0), size: pasteButtonSize))
|
||||
self.hintArrowNode.isHidden = true
|
||||
} else if case .word = codeType {
|
||||
self.hintButtonNode.alpha = 0.0
|
||||
self.hintButtonNode.isUserInteractionEnabled = false
|
||||
self.hintArrowNode.isHidden = true
|
||||
} else if self.hasPreviousCode {
|
||||
self.hintButtonNode.alpha = 1.0
|
||||
self.hintButtonNode.isUserInteractionEnabled = true
|
||||
|
||||
self.hintTextNode.attributedText = NSAttributedString(string: self.previousIsPhrase ? self.strings.Login_BackToPhrase : self.strings.Login_BackToWord, font: Font.regular(13.0), textColor: self.theme.list.itemAccentColor, paragraphAlignment: .center)
|
||||
|
||||
let hintTextSize = self.hintTextNode.updateLayout(CGSize(width: layout.size.width - 48.0, height: .greatestFiniteMagnitude))
|
||||
transition.updateFrame(node: self.hintButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - hintTextSize.width) / 2.0), y: self.codeInputView.frame.maxY + yOffset + 6.0), size: hintTextSize))
|
||||
self.hintTextNode.frame = CGRect(origin: .zero, size: hintTextSize)
|
||||
|
||||
if let icon = self.hintArrowNode.image {
|
||||
self.hintArrowNode.frame = CGRect(origin: CGPoint(x: self.hintTextNode.frame.minX - icon.size.width - 5.0, y: self.hintTextNode.frame.midY - icon.size.height / 2.0), size: icon.size)
|
||||
}
|
||||
self.hintArrowNode.isHidden = false
|
||||
} else {
|
||||
self.hintButtonNode.alpha = 0.0
|
||||
self.hintButtonNode.isUserInteractionEnabled = false
|
||||
self.hintArrowNode.isHidden = true
|
||||
}
|
||||
} else {
|
||||
self.hintButtonNode.alpha = 0.0
|
||||
self.hintButtonNode.isUserInteractionEnabled = false
|
||||
self.hintArrowNode.isHidden = true
|
||||
}
|
||||
|
||||
self.nextOptionTitleNode.frame = self.nextOptionButtonNode.bounds
|
||||
|
||||
if let icon = self.nextOptionArrowNode.image {
|
||||
self.nextOptionArrowNode.frame = CGRect(origin: CGPoint(x: self.nextOptionTitleNode.frame.maxX + 7.0, y: self.nextOptionTitleNode.frame.midY - icon.size.height / 2.0), size: icon.size)
|
||||
}
|
||||
|
||||
self.titleActivateAreaNode.frame = self.titleNode.frame
|
||||
self.currentOptionActivateAreaNode.frame = self.currentOptionNode.frame
|
||||
self.currentOptionInfoActivateAreaNode.frame = self.currentOptionInfoNode.frame
|
||||
@ -792,12 +904,18 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
}
|
||||
self.errorTextNode.alpha = 1.0
|
||||
self.errorTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
self.errorTextNode.layer.addShakeAnimation(amplitude: -8.0, duration: 0.5, count: 6, decay: true)
|
||||
|
||||
let previousHintAlpha = self.hintButtonNode.alpha
|
||||
self.hintButtonNode.alpha = 0.0
|
||||
self.hintButtonNode.layer.animateAlpha(from: previousHintAlpha, to: 0.0, duration: 0.1)
|
||||
|
||||
Queue.mainQueue().after(1.6) {
|
||||
self.errorTextNode.alpha = 0.0
|
||||
self.errorTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
||||
|
||||
self.hintButtonNode.alpha = previousHintAlpha
|
||||
self.hintButtonNode.layer.animateAlpha(from: 0.0, to: previousHintAlpha, duration: 0.1)
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut)
|
||||
transition.updateBackgroundColor(node: self.textSeparatorNode, color: self.theme.list.itemPlainSeparatorColor)
|
||||
}
|
||||
@ -852,23 +970,25 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
var updated = textField.text ?? ""
|
||||
updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string)
|
||||
|
||||
return checkValidity(text: updated)
|
||||
}
|
||||
|
||||
func checkValidity(text: String) -> Bool {
|
||||
if let codeType = self.codeType {
|
||||
switch codeType {
|
||||
case let .word(startsWith):
|
||||
if let startsWith, startsWith.count == 1, !updated.isEmpty && !updated.hasPrefix(startsWith) {
|
||||
if let startsWith, startsWith.count == 1, !text.isEmpty && !text.hasPrefix(startsWith) {
|
||||
if self.errorTextNode.alpha.isZero {
|
||||
//TODO:localize
|
||||
self.animateError(text: "Incorrect, please try again")
|
||||
self.animateError(text: self.strings.Login_WrongPhraseError)
|
||||
}
|
||||
return false
|
||||
}
|
||||
case let .phrase(startsWith):
|
||||
if let startsWith, !updated.isEmpty {
|
||||
let firstWord = updated.components(separatedBy: " ").first ?? ""
|
||||
if let startsWith, !text.isEmpty {
|
||||
let firstWord = text.components(separatedBy: " ").first ?? ""
|
||||
if !firstWord.isEmpty && !startsWith.hasPrefix(firstWord) {
|
||||
if self.errorTextNode.alpha.isZero {
|
||||
//TODO:localize
|
||||
self.animateError(text: "Incorrect, please try again")
|
||||
self.animateError(text: self.strings.Login_WrongPhraseError)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -877,7 +997,6 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -885,6 +1004,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
self.requestAnotherOption?()
|
||||
}
|
||||
|
||||
@objc func previousOptionNodePressed() {
|
||||
self.requestPreviousOption?()
|
||||
}
|
||||
|
||||
@objc func proceedPressed() {
|
||||
switch self.codeType {
|
||||
case let .fragment(url, _):
|
||||
|
@ -45,6 +45,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
|
||||
private var stateDisposable: Disposable?
|
||||
private let actionDisposable = MetaDisposable()
|
||||
private var applicationStateDisposable: Disposable?
|
||||
|
||||
private var didPlayPresentationAnimation = false
|
||||
|
||||
@ -92,6 +93,18 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
|
||||
self?.updateState(state: state)
|
||||
}).strict()
|
||||
|
||||
self.applicationStateDisposable = (self.sharedContext.applicationBindings.applicationIsActive
|
||||
|> deliverOnMainQueue).start(next: { [weak self] isActive in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
for viewController in self.viewControllers {
|
||||
if let codeController = viewController as? AuthorizationSequenceCodeEntryController {
|
||||
codeController.updateAppIsActive(isActive)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
@ -101,6 +114,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
deinit {
|
||||
self.stateDisposable?.dispose()
|
||||
self.actionDisposable.dispose()
|
||||
self.applicationStateDisposable?.dispose()
|
||||
}
|
||||
|
||||
override public func loadView() {
|
||||
@ -296,7 +310,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
return controller
|
||||
}
|
||||
|
||||
private func codeEntryController(number: String, phoneCodeHash: String, email: String?, type: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) -> AuthorizationSequenceCodeEntryController {
|
||||
private func codeEntryController(number: String, phoneCodeHash: String, email: String?, type: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, hasPreviousCode: Bool, previousIsPhrase: Bool, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) -> AuthorizationSequenceCodeEntryController {
|
||||
var currentController: AuthorizationSequenceCodeEntryController?
|
||||
for c in self.viewControllers {
|
||||
if let c = c as? AuthorizationSequenceCodeEntryController {
|
||||
@ -568,6 +582,16 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
}
|
||||
controller.requestNextOption = { [weak self, weak controller] in
|
||||
if let strongSelf = self {
|
||||
switch type {
|
||||
case .word, .phrase:
|
||||
if hasPreviousCode {
|
||||
strongSelf.actionDisposable.set(togglePreviousCodeEntry(account: strongSelf.account).start())
|
||||
return
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if nextType == nil {
|
||||
if let controller {
|
||||
let carrier = CTCarrier()
|
||||
@ -616,11 +640,17 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
}
|
||||
}
|
||||
}
|
||||
controller.reset = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let account = strongSelf.account
|
||||
let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty)).startStandalone()
|
||||
controller.requestPreviousOption = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.actionDisposable.set(togglePreviousCodeEntry(account: self.account).start())
|
||||
}
|
||||
controller.reset = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: self.account.testingEnvironment, masterDatacenterId: self.account.masterDatacenterId, contents: .empty)).startStandalone()
|
||||
}
|
||||
controller.signInWithApple = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
@ -645,7 +675,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
strongSelf.sharedContext.applicationBindings.openUrl(url)
|
||||
}
|
||||
}
|
||||
controller.updateData(number: formatPhoneNumber(number), email: email, codeType: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService)
|
||||
controller.updateData(number: formatPhoneNumber(number), email: email, codeType: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService, hasPreviousCode: hasPreviousCode, previousIsPhrase: previousIsPhrase)
|
||||
return controller
|
||||
}
|
||||
|
||||
@ -1189,12 +1219,14 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
|
||||
controllers.append(self.phoneEntryController(countryCode: countryCode, number: number, splashController: previousSplashController))
|
||||
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty && (previousSplashController == nil || self.viewControllers.count > 2))
|
||||
case let .confirmationCodeEntry(number, type, phoneCodeHash, timeout, nextType, _):
|
||||
case let .confirmationCodeEntry(number, type, phoneCodeHash, timeout, nextType, _, previousCodeEntry, usePrevious):
|
||||
var controllers: [ViewController] = []
|
||||
if !self.otherAccountPhoneNumbers.1.isEmpty {
|
||||
controllers.append(self.splashController())
|
||||
}
|
||||
controllers.append(self.phoneEntryController(countryCode: AuthorizationSequenceController.defaultCountryCode(), number: "", splashController: nil))
|
||||
|
||||
var isGoingBack = false
|
||||
if case let .emailSetupRequired(appleSignInAllowed) = type {
|
||||
self.appleSignInAllowed = appleSignInAllowed
|
||||
controllers.append(self.emailSetupController(number: number, appleSignInAllowed: appleSignInAllowed))
|
||||
@ -1202,9 +1234,35 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
if let _ = self.currentEmail {
|
||||
controllers.append(self.emailSetupController(number: number, appleSignInAllowed: self.appleSignInAllowed))
|
||||
}
|
||||
controllers.append(self.codeEntryController(number: number, phoneCodeHash: phoneCodeHash, email: self.currentEmail, type: type, nextType: nextType, timeout: timeout, termsOfService: nil))
|
||||
|
||||
if let previousCodeEntry, case let .confirmationCodeEntry(number, type, phoneCodeHash, timeout, nextType, _, _, _) = previousCodeEntry, usePrevious {
|
||||
var previousIsPhrase = false
|
||||
if case .phrase = type {
|
||||
previousIsPhrase = true
|
||||
}
|
||||
controllers.append(self.codeEntryController(number: number, phoneCodeHash: phoneCodeHash, email: self.currentEmail, type: type, nextType: nextType, timeout: timeout, hasPreviousCode: true, previousIsPhrase: previousIsPhrase, termsOfService: nil))
|
||||
isGoingBack = true
|
||||
} else {
|
||||
var previousIsPhrase = false
|
||||
if let previousCodeEntry, case let .confirmationCodeEntry(_, type, _, _, _, _, _, _) = previousCodeEntry {
|
||||
if case .phrase = type {
|
||||
previousIsPhrase = true
|
||||
}
|
||||
}
|
||||
controllers.append(self.codeEntryController(number: number, phoneCodeHash: phoneCodeHash, email: self.currentEmail, type: type, nextType: nextType, timeout: timeout, hasPreviousCode: previousCodeEntry != nil, previousIsPhrase: previousIsPhrase, termsOfService: nil))
|
||||
}
|
||||
}
|
||||
|
||||
if isGoingBack, let currentLastController = self.viewControllers.last as? AuthorizationSequenceCodeEntryController, !currentLastController.isWordOrPhrase {
|
||||
var tempControllers = controllers
|
||||
tempControllers.append(currentLastController)
|
||||
self.setViewControllers(tempControllers, animated: false)
|
||||
Queue.mainQueue().justDispatch {
|
||||
self.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
} else {
|
||||
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
|
||||
}
|
||||
case let .passwordEntry(hint, _, _, suggestReset, syncContacts):
|
||||
var controllers: [ViewController] = []
|
||||
if !self.otherAccountPhoneNumbers.1.isEmpty {
|
||||
|
@ -56,9 +56,18 @@ public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, ph
|
||||
}
|
||||
}
|
||||
|
||||
public func authorizationNextOptionText(currentType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, strings: PresentationStrings, primaryColor: UIColor, accentColor: UIColor) -> (NSAttributedString, Bool) {
|
||||
public func authorizationNextOptionText(currentType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, returnToCode: Bool = false, timeout: Int32?, strings: PresentationStrings, primaryColor: UIColor, accentColor: UIColor) -> (NSAttributedString, Bool) {
|
||||
let font = Font.regular(16.0)
|
||||
|
||||
switch currentType {
|
||||
case .word, .phrase:
|
||||
if returnToCode {
|
||||
return (NSAttributedString(string: strings.Login_ReturnToCode, font: font, textColor: accentColor, paragraphAlignment: .center), true)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if let nextType = nextType, let timeout = timeout, timeout > 0 {
|
||||
let minutes = timeout / 60
|
||||
let seconds = timeout % 60
|
||||
|
@ -4,11 +4,18 @@ import AsyncDisplayKit
|
||||
import Markdown
|
||||
|
||||
public class ActionSheetTextItem: ActionSheetItem {
|
||||
public enum Font {
|
||||
case `default`
|
||||
case large
|
||||
}
|
||||
|
||||
public let title: String
|
||||
public let font: Font
|
||||
public let parseMarkdown: Bool
|
||||
|
||||
public init(title: String, parseMarkdown: Bool = true) {
|
||||
public init(title: String, font: Font = .default, parseMarkdown: Bool = true) {
|
||||
self.title = title
|
||||
self.font = font
|
||||
self.parseMarkdown = parseMarkdown
|
||||
}
|
||||
|
||||
@ -30,8 +37,6 @@ public class ActionSheetTextItem: ActionSheetItem {
|
||||
}
|
||||
|
||||
public class ActionSheetTextNode: ActionSheetItemNode {
|
||||
private let defaultFont: UIFont
|
||||
|
||||
private let theme: ActionSheetControllerTheme
|
||||
|
||||
private var item: ActionSheetTextItem?
|
||||
@ -42,7 +47,6 @@ public class ActionSheetTextNode: ActionSheetItemNode {
|
||||
|
||||
override public init(theme: ActionSheetControllerTheme) {
|
||||
self.theme = theme
|
||||
self.defaultFont = Font.regular(floor(theme.baseFontSize * 13.0 / 17.0))
|
||||
|
||||
self.label = ImmediateTextNode()
|
||||
self.label.isUserInteractionEnabled = false
|
||||
@ -66,8 +70,16 @@ public class ActionSheetTextNode: ActionSheetItemNode {
|
||||
func setItem(_ item: ActionSheetTextItem) {
|
||||
self.item = item
|
||||
|
||||
let defaultFont = Font.regular(floor(theme.baseFontSize * 13.0 / 17.0))
|
||||
let boldFont = Font.semibold(floor(theme.baseFontSize * 13.0 / 17.0))
|
||||
let fontSize: CGFloat
|
||||
switch item.font {
|
||||
case .default:
|
||||
fontSize = 13.0
|
||||
case .large:
|
||||
fontSize = 15.0
|
||||
}
|
||||
|
||||
let defaultFont = Font.regular(floor(self.theme.baseFontSize * fontSize / 17.0))
|
||||
let boldFont = Font.semibold(floor(self.theme.baseFontSize * fontSize / 17.0))
|
||||
|
||||
if item.parseMarkdown {
|
||||
let body = MarkdownAttributeSet(font: defaultFont, textColor: self.theme.secondaryTextColor)
|
||||
|
@ -68,14 +68,18 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
|
||||
for media in message.media {
|
||||
if let telegramMap = media as? TelegramMediaMap {
|
||||
if let liveBroadcastingTimeout = telegramMap.liveBroadcastingTimeout {
|
||||
if message.timestamp + liveBroadcastingTimeout > timestamp {
|
||||
if liveBroadcastingTimeout == liveLocationIndefinitePeriod || message.timestamp + liveBroadcastingTimeout > timestamp {
|
||||
activeLiveBroadcastingTimeout = liveBroadcastingTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let activeLiveBroadcastingTimeout = activeLiveBroadcastingTimeout {
|
||||
if activeLiveBroadcastingTimeout == liveLocationIndefinitePeriod {
|
||||
broadcastToMessageIds[message.id] = activeLiveBroadcastingTimeout
|
||||
} else {
|
||||
broadcastToMessageIds[message.id] = message.timestamp + activeLiveBroadcastingTimeout
|
||||
}
|
||||
} else {
|
||||
stopMessageIds.insert(message.id)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
|
||||
private let infinityFont = Font.with(size: 15.0, design: .round, weight: .bold)
|
||||
private let textFont = Font.with(size: 13.0, design: .round, weight: .bold)
|
||||
private let smallTextFont = Font.with(size: 11.0, design: .round, weight: .bold)
|
||||
|
||||
@ -72,8 +73,12 @@ public final class ChatMessageLiveLocationTimerNode: ASDisplayNode {
|
||||
let remaining = beginTimestamp + timeout - timestamp
|
||||
value = CGFloat(max(0.0, 1.0 - min(1.0, remaining / timeout)))
|
||||
|
||||
let intRemaining = Int32(remaining)
|
||||
let string: String
|
||||
if timeout < 0.0 {
|
||||
value = 0.0
|
||||
string = "∞"
|
||||
} else {
|
||||
let intRemaining = Int32(remaining)
|
||||
if intRemaining > 60 * 60 {
|
||||
let hours = Int32(round(remaining / (60.0 * 60.0)))
|
||||
string = strings.Map_LiveLocationShortHour("\(hours)").string
|
||||
@ -81,6 +86,7 @@ public final class ChatMessageLiveLocationTimerNode: ASDisplayNode {
|
||||
let minutes = Int32(round(remaining / (60.0)))
|
||||
string = "\(minutes)"
|
||||
}
|
||||
}
|
||||
|
||||
return ChatMessageLiveLocationTimerNodeParams(backgroundColor: backgroundColor, foregroundColor: foregroundColor, textColor: textColor, value: value, string: string)
|
||||
} else {
|
||||
@ -120,16 +126,27 @@ public final class ChatMessageLiveLocationTimerNode: ASDisplayNode {
|
||||
path.lineCapStyle = .round
|
||||
path.stroke()
|
||||
|
||||
let attributes: [NSAttributedString.Key: Any] = [.font: parameters.string.count > 2 ? smallTextFont : textFont, .foregroundColor: parameters.foregroundColor]
|
||||
let font: UIFont
|
||||
if parameters.string == "∞" {
|
||||
font = infinityFont
|
||||
} else if parameters.string.count > 2 {
|
||||
font = smallTextFont
|
||||
} else {
|
||||
font = textFont
|
||||
}
|
||||
|
||||
let attributes: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: parameters.foregroundColor]
|
||||
let nsString = parameters.string as NSString
|
||||
let size = nsString.size(withAttributes: attributes)
|
||||
|
||||
var offset: CGFloat = 0.0
|
||||
if parameters.string.count > 2 {
|
||||
offset = UIScreenPixel
|
||||
var offset = CGPoint()
|
||||
if parameters.string == "∞" {
|
||||
offset = CGPoint(x: 1.0, y: -1.0)
|
||||
} else if parameters.string.count > 2 {
|
||||
offset = CGPoint(x: 0.0, y: UIScreenPixel)
|
||||
}
|
||||
|
||||
nsString.draw(at: CGPoint(x: floor((bounds.size.width - size.width) / 2.0), y: floor((bounds.size.height - size.height) / 2.0) + offset), withAttributes: attributes)
|
||||
nsString.draw(at: CGPoint(x: floor((bounds.size.width - size.width) / 2.0) + offset.x, y: floor((bounds.size.height - size.height) / 2.0) + offset.y), withAttributes: attributes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,10 +5,12 @@ import AsyncDisplayKit
|
||||
|
||||
private final class LiveLocationWavesNodeParams: NSObject {
|
||||
let color: UIColor
|
||||
let extend: Bool
|
||||
let progress: CGFloat
|
||||
|
||||
init(color: UIColor, progress: CGFloat) {
|
||||
init(color: UIColor, extend: Bool, progress: CGFloat) {
|
||||
self.color = color
|
||||
self.extend = extend
|
||||
self.progress = progress
|
||||
|
||||
super.init()
|
||||
@ -26,6 +28,12 @@ public final class LiveLocationWavesNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public var extend: Bool {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
private var effectiveProgress: CGFloat = 0.0 {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
@ -34,8 +42,9 @@ public final class LiveLocationWavesNode: ASDisplayNode {
|
||||
|
||||
var animator: ConstantDisplayLinkAnimator?
|
||||
|
||||
public init(color: UIColor) {
|
||||
public init(color: UIColor, extend: Bool = false) {
|
||||
self.color = color
|
||||
self.extend = extend
|
||||
|
||||
super.init()
|
||||
|
||||
@ -105,7 +114,7 @@ public final class LiveLocationWavesNode: ASDisplayNode {
|
||||
public override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||
let t = CACurrentMediaTime()
|
||||
let value: CGFloat = CGFloat(t.truncatingRemainder(dividingBy: 2.0)) / 2.0
|
||||
return LiveLocationWavesNodeParams(color: self.color, progress: value)
|
||||
return LiveLocationWavesNodeParams(color: self.color, extend: self.extend, progress: value)
|
||||
}
|
||||
|
||||
@objc public override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
@ -142,7 +151,9 @@ public final class LiveLocationWavesNode: ASDisplayNode {
|
||||
}
|
||||
context.setAlpha(alpha * 0.7)
|
||||
|
||||
if !parameters.extend {
|
||||
draw(context, position, false)
|
||||
}
|
||||
draw(context, position, true)
|
||||
|
||||
var progress = parameters.progress + 0.5
|
||||
@ -157,7 +168,9 @@ public final class LiveLocationWavesNode: ASDisplayNode {
|
||||
}
|
||||
context.setAlpha(largerAlpha * 0.7)
|
||||
|
||||
if !parameters.extend {
|
||||
draw(context, largerPos, false)
|
||||
}
|
||||
draw(context, largerPos, true)
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ public enum LocationActionListItemIcon: Equatable {
|
||||
case location
|
||||
case liveLocation
|
||||
case stopLiveLocation
|
||||
case extendLiveLocation
|
||||
case venue(TelegramMediaMap)
|
||||
|
||||
public static func ==(lhs: LocationActionListItemIcon, rhs: LocationActionListItemIcon) -> Bool {
|
||||
@ -37,6 +38,12 @@ public enum LocationActionListItemIcon: Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .extendLiveLocation:
|
||||
if case .extendLiveLocation = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .venue(lhsVenue):
|
||||
if case let .venue(rhsVenue) = rhs, lhsVenue.venue?.id == rhsVenue.venue?.id {
|
||||
return true
|
||||
@ -63,18 +70,67 @@ private func generateLocationIcon(theme: PresentationTheme) -> UIImage {
|
||||
})!
|
||||
}
|
||||
|
||||
private func generateLiveLocationIcon(theme: PresentationTheme, stop: Bool) -> UIImage {
|
||||
private enum LiveLocationIconType {
|
||||
case start
|
||||
case stop
|
||||
case extend
|
||||
}
|
||||
private func generateLiveLocationIcon(theme: PresentationTheme, type: LiveLocationIconType) -> UIImage {
|
||||
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
|
||||
let imageName: String
|
||||
let color: UIColor
|
||||
switch type {
|
||||
case .start:
|
||||
imageName = "Location/SendLiveLocationIcon"
|
||||
color = UIColor(rgb: 0x6cc139)
|
||||
case .stop:
|
||||
imageName = "Location/SendLocationIcon"
|
||||
color = UIColor(rgb: 0xff6464)
|
||||
case .extend:
|
||||
imageName = "Location/SendLocationIcon"
|
||||
color = UIColor(rgb: 0x6cc139)
|
||||
}
|
||||
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor(rgb: stop ? 0xff6464 : 0x6cc139).cgColor)
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: stop ? "Location/SendLocationIcon" : "Location/SendLiveLocationIcon"), color: theme.chat.inputPanel.actionControlForegroundColor) {
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.chat.inputPanel.actionControlForegroundColor) {
|
||||
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
|
||||
|
||||
if case .extend = type {
|
||||
context.setStrokeColor(theme.chat.inputPanel.actionControlForegroundColor.cgColor)
|
||||
context.setLineWidth(2.0 - UIScreenPixel)
|
||||
context.setLineCap(.round)
|
||||
|
||||
let length: CGFloat = 6.0 + UIScreenPixel
|
||||
context.move(to: CGPoint(x: 8.0 + 0.0, y: image.size.height / 2.0))
|
||||
context.addLine(to: CGPoint(x: 8.0 + length, y: image.size.height / 2.0))
|
||||
context.strokePath()
|
||||
|
||||
context.move(to: CGPoint(x: 8.0 + length / 2.0, y: image.size.height / 2.0 - length / 2.0))
|
||||
context.addLine(to: CGPoint(x: 8.0 + length / 2.0, y: image.size.height / 2.0 + length / 2.0))
|
||||
context.strokePath()
|
||||
} else if case .stop = type {
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setLineWidth(5.0)
|
||||
|
||||
context.move(to: CGPoint(x: 10.0, y: size.height - 10.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 10.0, y: 10.0))
|
||||
context.strokePath()
|
||||
|
||||
context.setStrokeColor(theme.chat.inputPanel.actionControlForegroundColor.cgColor)
|
||||
context.setLineWidth(2.0 - UIScreenPixel)
|
||||
context.setLineCap(.round)
|
||||
|
||||
context.move(to: CGPoint(x: 12.0, y: size.height - 12.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 12.0, y: 12.0))
|
||||
context.strokePath()
|
||||
}
|
||||
}
|
||||
})!
|
||||
}
|
||||
@ -273,10 +329,18 @@ final class LocationActionListItemNode: ListViewItemNode {
|
||||
strongSelf.iconNode.isHidden = false
|
||||
strongSelf.venueIconNode.isHidden = true
|
||||
strongSelf.iconNode.image = generateLocationIcon(theme: item.presentationData.theme)
|
||||
case .liveLocation, .stopLiveLocation:
|
||||
case .liveLocation:
|
||||
strongSelf.iconNode.isHidden = false
|
||||
strongSelf.venueIconNode.isHidden = true
|
||||
strongSelf.iconNode.image = generateLiveLocationIcon(theme: item.presentationData.theme, stop: updatedIcon == .stopLiveLocation)
|
||||
strongSelf.iconNode.image = generateLiveLocationIcon(theme: item.presentationData.theme, type: .start)
|
||||
case .stopLiveLocation:
|
||||
strongSelf.iconNode.isHidden = false
|
||||
strongSelf.venueIconNode.isHidden = true
|
||||
strongSelf.iconNode.image = generateLiveLocationIcon(theme: item.presentationData.theme, type: .stop)
|
||||
case .extendLiveLocation:
|
||||
strongSelf.iconNode.isHidden = false
|
||||
strongSelf.venueIconNode.isHidden = true
|
||||
strongSelf.iconNode.image = generateLiveLocationIcon(theme: item.presentationData.theme, type: .extend)
|
||||
case let .venue(venue):
|
||||
strongSelf.iconNode.isHidden = true
|
||||
strongSelf.venueIconNode.isHidden = false
|
||||
@ -293,13 +357,14 @@ final class LocationActionListItemNode: ListViewItemNode {
|
||||
strongSelf.venueIconNode.setSignal(venueIcon(engine: item.engine, type: type ?? "", flag: flag, background: true))
|
||||
}
|
||||
|
||||
if updatedIcon == .stopLiveLocation {
|
||||
let wavesNode = LiveLocationWavesNode(color: item.presentationData.theme.chat.inputPanel.actionControlForegroundColor)
|
||||
switch updatedIcon {
|
||||
case .stopLiveLocation, .extendLiveLocation:
|
||||
let wavesNode = LiveLocationWavesNode(color: item.presentationData.theme.chat.inputPanel.actionControlForegroundColor, extend: updatedIcon == .extendLiveLocation)
|
||||
strongSelf.addSubnode(wavesNode)
|
||||
strongSelf.wavesNode = wavesNode
|
||||
} else if let wavesNode = strongSelf.wavesNode {
|
||||
default:
|
||||
strongSelf.wavesNode?.removeFromSupernode()
|
||||
strongSelf.wavesNode = nil
|
||||
wavesNode.removeFromSupernode()
|
||||
}
|
||||
strongSelf.wavesNode?.color = item.presentationData.theme.chat.inputPanel.actionControlForegroundColor
|
||||
}
|
||||
@ -349,7 +414,7 @@ final class LocationActionListItemNode: ListViewItemNode {
|
||||
strongSelf.timerNode = timerNode
|
||||
}
|
||||
let timerSize = CGSize(width: 28.0, height: 28.0)
|
||||
timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: beginTimestamp, timeout: timeout, strings: item.presentationData.strings)
|
||||
timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: beginTimestamp, timeout: Int32(timeout) == liveLocationIndefinitePeriod ? -1.0 : timeout, strings: item.presentationData.strings)
|
||||
timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 16.0 - timerSize.width, y: floorToScreenPixels((contentSize.height - timerSize.height) / 2.0) - 2.0), size: timerSize)
|
||||
} else if let timerNode = strongSelf.timerNode {
|
||||
strongSelf.timerNode = nil
|
||||
|
@ -142,33 +142,34 @@ public final class LocationPickerController: ViewController, AttachmentContainab
|
||||
return
|
||||
}
|
||||
let controller = ActionSheetController(presentationData: strongSelf.presentationData)
|
||||
var title = strongSelf.presentationData.strings.Map_LiveLocationGroupDescription
|
||||
var title = strongSelf.presentationData.strings.Map_LiveLocationGroupNewDescription
|
||||
if case let .share(peer, _, _) = strongSelf.mode, let peer = peer, case .user = peer {
|
||||
title = strongSelf.presentationData.strings.Map_LiveLocationPrivateDescription(peer.compactDisplayTitle).string
|
||||
title = strongSelf.presentationData.strings.Map_LiveLocationPrivateNewDescription(peer.compactDisplayTitle).string
|
||||
}
|
||||
|
||||
let sendLiveLocationImpl: (Int32) -> Void = { [weak self, weak controller] period in
|
||||
controller?.dismissAnimated()
|
||||
guard let self, let controller else {
|
||||
return
|
||||
}
|
||||
controller.dismissAnimated()
|
||||
self.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period), nil, nil, nil, nil)
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: title),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor15Minutes, color: .accent, action: { [weak self, weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
strongSelf.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 15 * 60), nil, nil, nil, nil)
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
ActionSheetTextItem(title: title, font: .large, parseMarkdown: true),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForMinutes(15), color: .accent, action: { sendLiveLocationImpl(15 * 60)
|
||||
}),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: { [weak self, weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
strongSelf.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 60 * 60 - 1), nil, nil, nil, nil)
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(1), color: .accent, action: {
|
||||
sendLiveLocationImpl(60 * 60 - 1)
|
||||
}),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: { [weak self, weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
strongSelf.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 8 * 60 * 60), nil, nil, nil, nil)
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(8), color: .accent, action: {
|
||||
sendLiveLocationImpl(8 * 60 * 60)
|
||||
}),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationIndefinite, color: .accent, action: {
|
||||
sendLiveLocationImpl(liveLocationIndefinitePeriod)
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [
|
||||
|
@ -47,12 +47,12 @@ class LocationViewInteraction {
|
||||
let share: () -> Void
|
||||
let setupProximityNotification: (Bool, EngineMessage.Id?) -> Void
|
||||
let updateSendActionHighlight: (Bool) -> Void
|
||||
let sendLiveLocation: (Int32?) -> Void
|
||||
let sendLiveLocation: (Int32?, Bool) -> Void
|
||||
let stopLiveLocation: () -> Void
|
||||
let updateRightBarButton: (LocationViewRightBarButton) -> Void
|
||||
let present: (ViewController) -> Void
|
||||
|
||||
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, toggleTrackingMode: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping (TelegramMediaMap, String?, OpenInLocationDirections) -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, EngineMessage.Id?) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (Int32?) -> Void, stopLiveLocation: @escaping () -> Void, updateRightBarButton: @escaping (LocationViewRightBarButton) -> Void, present: @escaping (ViewController) -> Void) {
|
||||
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, toggleTrackingMode: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping (TelegramMediaMap, String?, OpenInLocationDirections) -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, EngineMessage.Id?) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (Int32?, Bool) -> Void, stopLiveLocation: @escaping () -> Void, updateRightBarButton: @escaping (LocationViewRightBarButton) -> Void, present: @escaping (ViewController) -> Void) {
|
||||
self.toggleMapModeSelection = toggleMapModeSelection
|
||||
self.updateMapMode = updateMapMode
|
||||
self.toggleTrackingMode = toggleTrackingMode
|
||||
@ -323,7 +323,7 @@ public final class LocationViewController: ViewController {
|
||||
} else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: updatedPresentationData, title: strongSelf.presentationData.strings.Location_LiveLocationRequired_Title, text: strongSelf.presentationData.strings.Location_LiveLocationRequired_Description, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Location_LiveLocationRequired_ShareLocation, action: {
|
||||
completion()
|
||||
strongSelf.interaction?.sendLiveLocation(distance)
|
||||
strongSelf.interaction?.sendLiveLocation(distance, false)
|
||||
}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})], actionLayout: .vertical), in: .window(.root))
|
||||
}
|
||||
completion()
|
||||
@ -341,7 +341,7 @@ public final class LocationViewController: ViewController {
|
||||
return
|
||||
}
|
||||
strongSelf.controllerNode.updateSendActionHighlight(highlighted)
|
||||
}, sendLiveLocation: { [weak self] distance in
|
||||
}, sendLiveLocation: { [weak self] distance, extend in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -400,9 +400,14 @@ public final class LocationViewController: ViewController {
|
||||
let _ = (context.account.postbox.loadedPeerWithId(subject.id.peerId)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
let controller = ActionSheetController(presentationData: strongSelf.presentationData)
|
||||
var title = strongSelf.presentationData.strings.Map_LiveLocationGroupDescription
|
||||
var title: String
|
||||
if extend {
|
||||
title = strongSelf.presentationData.strings.Map_LiveLocationExtendDescription
|
||||
} else {
|
||||
title = strongSelf.presentationData.strings.Map_LiveLocationGroupNewDescription
|
||||
if let user = peer as? TelegramUser {
|
||||
title = strongSelf.presentationData.strings.Map_LiveLocationPrivateDescription(EnginePeer(user).compactDisplayTitle).string
|
||||
title = strongSelf.presentationData.strings.Map_LiveLocationPrivateNewDescription(EnginePeer(user).compactDisplayTitle).string
|
||||
}
|
||||
}
|
||||
|
||||
let sendLiveLocationImpl: (Int32) -> Void = { [weak controller] period in
|
||||
@ -418,15 +423,18 @@ public final class LocationViewController: ViewController {
|
||||
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: title),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor15Minutes, color: .accent, action: {
|
||||
ActionSheetTextItem(title: title, font: .large, parseMarkdown: true),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForMinutes(15), color: .accent, action: {
|
||||
sendLiveLocationImpl(15 * 60)
|
||||
}),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: {
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(1), color: .accent, action: {
|
||||
sendLiveLocationImpl(60 * 60 - 1)
|
||||
}),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: {
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(8), color: .accent, action: {
|
||||
sendLiveLocationImpl(8 * 60 * 60)
|
||||
}),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationIndefinite, color: .accent, action: {
|
||||
sendLiveLocationImpl(liveLocationIndefinitePeriod)
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [
|
||||
|
@ -42,21 +42,21 @@ private struct LocationViewTransaction {
|
||||
|
||||
private enum LocationViewEntryId: Hashable {
|
||||
case info
|
||||
case toggleLiveLocation
|
||||
case toggleLiveLocation(Bool)
|
||||
case liveLocation(UInt32)
|
||||
}
|
||||
|
||||
private enum LocationViewEntry: Comparable, Identifiable {
|
||||
case info(PresentationTheme, TelegramMediaMap, String?, Double?, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime, Bool)
|
||||
case toggleLiveLocation(PresentationTheme, String, String, Double?, Double?)
|
||||
case toggleLiveLocation(PresentationTheme, String, String, Double?, Double?, Bool)
|
||||
case liveLocation(PresentationTheme, PresentationDateTimeFormat, PresentationPersonNameOrder, EngineMessage, Double?, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime, Int)
|
||||
|
||||
var stableId: LocationViewEntryId {
|
||||
switch self {
|
||||
case .info:
|
||||
return .info
|
||||
case .toggleLiveLocation:
|
||||
return .toggleLiveLocation
|
||||
case let .toggleLiveLocation(_, _, _, _, _, additional):
|
||||
return .toggleLiveLocation(additional)
|
||||
case let .liveLocation(_, _, _, message, _, _, _, _, _):
|
||||
return .liveLocation(message.stableId)
|
||||
}
|
||||
@ -70,8 +70,8 @@ private enum LocationViewEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .toggleLiveLocation(lhsTheme, lhsTitle, lhsSubtitle, lhsBeginTimestamp, lhsTimeout):
|
||||
if case let .toggleLiveLocation(rhsTheme, rhsTitle, rhsSubtitle, rhsBeginTimestamp, rhsTimeout) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsBeginTimestamp == rhsBeginTimestamp, lhsTimeout == rhsTimeout {
|
||||
case let .toggleLiveLocation(lhsTheme, lhsTitle, lhsSubtitle, lhsBeginTimestamp, lhsTimeout, lhsAdditional):
|
||||
if case let .toggleLiveLocation(rhsTheme, rhsTitle, rhsSubtitle, rhsBeginTimestamp, rhsTimeout, rhsAdditional) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsBeginTimestamp == rhsBeginTimestamp, lhsTimeout == rhsTimeout, lhsAdditional == rhsAdditional {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -94,10 +94,12 @@ private enum LocationViewEntry: Comparable, Identifiable {
|
||||
case .toggleLiveLocation, .liveLocation:
|
||||
return true
|
||||
}
|
||||
case .toggleLiveLocation:
|
||||
case let .toggleLiveLocation(_, _, _, _, _, lhsAdditional):
|
||||
switch rhs {
|
||||
case .info, .toggleLiveLocation:
|
||||
case .info:
|
||||
return false
|
||||
case let .toggleLiveLocation(_, _, _, _, _, rhsAdditional):
|
||||
return !lhsAdditional && rhsAdditional
|
||||
case .liveLocation:
|
||||
return true
|
||||
}
|
||||
@ -135,18 +137,36 @@ private enum LocationViewEntry: Comparable, Identifiable {
|
||||
}, walkingAction: {
|
||||
interaction?.requestDirections(location, nil, .walking)
|
||||
})
|
||||
case let .toggleLiveLocation(_, title, subtitle, beginTimstamp, timeout):
|
||||
let beginTimeAndTimeout: (Double, Double)?
|
||||
case let .toggleLiveLocation(_, title, subtitle, beginTimstamp, timeout, additional):
|
||||
var beginTimeAndTimeout: (Double, Double)?
|
||||
if let beginTimstamp = beginTimstamp, let timeout = timeout {
|
||||
beginTimeAndTimeout = (beginTimstamp, timeout)
|
||||
} else {
|
||||
beginTimeAndTimeout = nil
|
||||
}
|
||||
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, title: title, subtitle: subtitle, icon: beginTimeAndTimeout != nil ? .stopLiveLocation : .liveLocation, beginTimeAndTimeout: beginTimeAndTimeout, action: {
|
||||
|
||||
let icon: LocationActionListItemIcon
|
||||
if let timeout, Int32(timeout) != liveLocationIndefinitePeriod, !additional {
|
||||
icon = .extendLiveLocation
|
||||
} else if beginTimeAndTimeout != nil {
|
||||
icon = .stopLiveLocation
|
||||
} else {
|
||||
icon = .liveLocation
|
||||
}
|
||||
|
||||
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: !additional ? beginTimeAndTimeout : nil, action: {
|
||||
if beginTimeAndTimeout != nil {
|
||||
if let timeout, Int32(timeout) != liveLocationIndefinitePeriod {
|
||||
if additional {
|
||||
interaction?.stopLiveLocation()
|
||||
} else {
|
||||
interaction?.sendLiveLocation(nil)
|
||||
interaction?.sendLiveLocation(nil, true)
|
||||
}
|
||||
} else {
|
||||
interaction?.stopLiveLocation()
|
||||
}
|
||||
} else {
|
||||
interaction?.sendLiveLocation(nil, false)
|
||||
}
|
||||
}, highlighted: { highlight in
|
||||
interaction?.updateSendActionHighlight(highlight)
|
||||
@ -421,7 +441,12 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
|
||||
|
||||
if case let .channel(channel) = subject.author, case .broadcast = channel.info, activeOwnLiveLocation == nil {
|
||||
} else {
|
||||
entries.append(.toggleLiveLocation(presentationData.theme, title, subtitle, beginTime, timeout))
|
||||
if let timeout, Int32(timeout) != liveLocationIndefinitePeriod {
|
||||
entries.append(.toggleLiveLocation(presentationData.theme, presentationData.strings.Map_SharingLocation, presentationData.strings.Map_TapToAddTime, beginTime, timeout, false))
|
||||
entries.append(.toggleLiveLocation(presentationData.theme, title, subtitle, beginTime, timeout, true))
|
||||
} else {
|
||||
entries.append(.toggleLiveLocation(presentationData.theme, title, subtitle, beginTime, timeout, false))
|
||||
}
|
||||
}
|
||||
|
||||
var sortedLiveLocations: [EngineMessage] = []
|
||||
@ -452,7 +477,12 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
|
||||
if let timeout = location.liveBroadcastingTimeout {
|
||||
liveBroadcastingTimeout = timeout
|
||||
}
|
||||
let remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime)
|
||||
let remainingTime: Int32
|
||||
if liveBroadcastingTimeout == liveLocationIndefinitePeriod {
|
||||
remainingTime = liveLocationIndefinitePeriod
|
||||
} else {
|
||||
remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime)
|
||||
}
|
||||
if message.flags.contains(.Incoming) && remainingTime != 0 && proximityNotification == nil {
|
||||
proximityNotification = false
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ public func ChangePhoneNumberController(context: AccountContext) -> ViewControll
|
||||
codeController.openFragment = { url in
|
||||
context.sharedContext.applicationBindings.openUrl(url)
|
||||
}
|
||||
codeController.updateData(number: formatPhoneNumber(context: context, number: phoneNumber), email: nil, codeType: next.type, nextType: nil, timeout: next.timeout, termsOfService: nil)
|
||||
codeController.updateData(number: formatPhoneNumber(context: context, number: phoneNumber), email: nil, codeType: next.type, nextType: nil, timeout: next.timeout, termsOfService: nil, hasPreviousCode: false, previousIsPhrase: false)
|
||||
dismissImpl = { [weak codeController] in
|
||||
codeController?.dismiss()
|
||||
}
|
||||
|
@ -1446,7 +1446,7 @@ public func privacyAndSecurityController(
|
||||
emailChangeCompletion(codeController)
|
||||
}))
|
||||
}
|
||||
codeController.updateData(number: "", email: email, codeType: .email(emailPattern: "", length: data.length, resetAvailablePeriod: nil, resetPendingDate: nil, appleSignInAllowed: false, setup: true), nextType: nil, timeout: nil, termsOfService: nil)
|
||||
codeController.updateData(number: "", email: email, codeType: .email(emailPattern: "", length: data.length, resetAvailablePeriod: nil, resetPendingDate: nil, appleSignInAllowed: false, setup: true), nextType: nil, timeout: nil, termsOfService: nil, hasPreviousCode: false, previousIsPhrase: false)
|
||||
pushControllerImpl?(codeController, true)
|
||||
dismissCodeControllerImpl = { [weak codeController] in
|
||||
codeController?.dismiss()
|
||||
|
@ -306,7 +306,20 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
|
||||
|> mapToSignal { success -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||
if success {
|
||||
return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
||||
var previousCodeEntry: UnauthorizedAccountStateContents?
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState, case let .confirmationCodeEntry(_, type, _, _, _, _, previousCodeEntryValue, _) = state.contents {
|
||||
if let previousCodeEntryValue {
|
||||
previousCodeEntry = previousCodeEntryValue
|
||||
} else {
|
||||
switch type {
|
||||
case .word, .phrase:
|
||||
previousCodeEntry = state.contents
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
|
||||
|
||||
return .sentCode(account)
|
||||
}
|
||||
@ -318,7 +331,20 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
|
||||
}
|
||||
}
|
||||
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
||||
var previousCodeEntry: UnauthorizedAccountStateContents?
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState, case let .confirmationCodeEntry(_, type, _, _, _, _, previousCodeEntryValue, _) = state.contents {
|
||||
if let previousCodeEntryValue {
|
||||
previousCodeEntry = previousCodeEntryValue
|
||||
} else {
|
||||
switch type {
|
||||
case .word, .phrase:
|
||||
previousCodeEntry = state.contents
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
|
||||
case let .sentCodeSuccess(authorization):
|
||||
switch authorization {
|
||||
case let .authorization(_, otherwiseReloginDays, _, futureAuthToken, user):
|
||||
@ -415,7 +441,20 @@ private func internalResendAuthorizationCode(accountManager: AccountManager<Tele
|
||||
|> mapToSignal { success -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||
if success {
|
||||
return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
||||
var previousCodeEntry: UnauthorizedAccountStateContents?
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState, case let .confirmationCodeEntry(_, type, _, _, _, _, previousCodeEntryValue, _) = state.contents {
|
||||
if let previousCodeEntryValue {
|
||||
previousCodeEntry = previousCodeEntryValue
|
||||
} else {
|
||||
switch type {
|
||||
case .word, .phrase:
|
||||
previousCodeEntry = state.contents
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
|
||||
|
||||
return .sentCode(account)
|
||||
}
|
||||
@ -427,7 +466,20 @@ private func internalResendAuthorizationCode(accountManager: AccountManager<Tele
|
||||
}
|
||||
}
|
||||
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
||||
var previousCodeEntry: UnauthorizedAccountStateContents?
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState, case let .confirmationCodeEntry(_, type, _, _, _, _, previousCodeEntryValue, _) = state.contents {
|
||||
if let previousCodeEntryValue {
|
||||
previousCodeEntry = previousCodeEntryValue
|
||||
} else {
|
||||
switch type {
|
||||
case .word, .phrase:
|
||||
previousCodeEntry = state.contents
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
|
||||
|
||||
return .single(.sentCode(account))
|
||||
case .sentCodeSuccess:
|
||||
@ -443,8 +495,19 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
|
||||
return account.postbox.transaction { transaction -> Signal<Void, AuthorizationCodeRequestError> in
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState {
|
||||
switch state.contents {
|
||||
case let .confirmationCodeEntry(number, _, hash, _, nextType, syncContacts):
|
||||
case let .confirmationCodeEntry(number, type, hash, _, nextType, syncContacts, previousCodeEntryValue, _):
|
||||
if nextType != nil {
|
||||
var previousCodeEntry: UnauthorizedAccountStateContents?
|
||||
if let previousCodeEntryValue {
|
||||
previousCodeEntry = previousCodeEntryValue
|
||||
} else {
|
||||
switch type {
|
||||
case .word, .phrase:
|
||||
previousCodeEntry = state.contents
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
|
||||
|> mapError { error -> AuthorizationCodeRequestError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
@ -503,7 +566,7 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
|
||||
|> mapToSignal { success -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
|
||||
if success {
|
||||
return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
|
||||
|
||||
return .sentCode(account)
|
||||
}
|
||||
@ -516,7 +579,7 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
|
||||
|> map { _ -> Void in return Void() }
|
||||
}
|
||||
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
|
||||
case .sentCodeSuccess:
|
||||
break
|
||||
}
|
||||
@ -675,7 +738,7 @@ public func sendLoginEmailCode(account: UnauthorizedAccount, email: String) -> S
|
||||
return account.postbox.transaction { transaction -> Signal<Never, AuthorizationSendEmailCodeError> in
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState {
|
||||
switch state.contents {
|
||||
case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts):
|
||||
case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts, _, _):
|
||||
return account.network.request(Api.functions.account.sendVerifyEmailCode(purpose: .emailVerifyPurposeLoginSetup(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), email: email), automaticFloodWait: false)
|
||||
|> `catch` { error -> Signal<Api.account.SentEmailCode, AuthorizationSendEmailCodeError> in
|
||||
let errorDescription = error.errorDescription ?? ""
|
||||
@ -695,7 +758,7 @@ public func sendLoginEmailCode(account: UnauthorizedAccount, email: String) -> S
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
switch result {
|
||||
case let .sentEmailCode(emailPattern, length):
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: .email(emailPattern: emailPattern, length: length, resetAvailablePeriod: nil, resetPendingDate: nil, appleSignInAllowed: false, setup: true), hash: phoneCodeHash, timeout: nil, nextType: nil, syncContacts: syncContacts)))
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: .email(emailPattern: emailPattern, length: length, resetAvailablePeriod: nil, resetPendingDate: nil, appleSignInAllowed: false, setup: true), hash: phoneCodeHash, timeout: nil, nextType: nil, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false)))
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
@ -754,7 +817,7 @@ public func verifyLoginEmailSetup(account: UnauthorizedAccount, code: Authorizat
|
||||
return account.postbox.transaction { transaction -> Signal<Never, AuthorizationEmailVerificationError> in
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState {
|
||||
switch state.contents {
|
||||
case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts):
|
||||
case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts, _, _):
|
||||
let verification: Api.EmailVerification
|
||||
switch code {
|
||||
case let .emailCode(code):
|
||||
@ -793,7 +856,7 @@ public func verifyLoginEmailSetup(account: UnauthorizedAccount, code: Authorizat
|
||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||
}
|
||||
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false)))
|
||||
case .sentCodeSuccess:
|
||||
break
|
||||
}
|
||||
@ -831,7 +894,7 @@ public func resetLoginEmail(account: UnauthorizedAccount, phoneNumber: String, p
|
||||
return account.postbox.transaction { transaction -> Signal<Never, AuthorizationEmailResetError> in
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState {
|
||||
switch state.contents {
|
||||
case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts):
|
||||
case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts, _, _):
|
||||
return account.network.request(Api.functions.auth.resetLoginEmail(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false)
|
||||
|> `catch` { error -> Signal<Api.auth.SentCode, AuthorizationEmailResetError> in
|
||||
let errorDescription = error.errorDescription ?? ""
|
||||
@ -854,7 +917,7 @@ public func resetLoginEmail(account: UnauthorizedAccount, phoneNumber: String, p
|
||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||
}
|
||||
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false)))
|
||||
|
||||
return .complete()
|
||||
case .sentCodeSuccess:
|
||||
@ -883,7 +946,7 @@ public func authorizeWithCode(accountManager: AccountManager<TelegramAccountMana
|
||||
return account.postbox.transaction { transaction -> Signal<AuthorizeWithCodeResult, AuthorizationCodeVerificationError> in
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState {
|
||||
switch state.contents {
|
||||
case let .confirmationCodeEntry(number, _, hash, _, _, syncContacts):
|
||||
case let .confirmationCodeEntry(number, _, hash, _, _, syncContacts, _, _):
|
||||
var flags: Int32 = 0
|
||||
var phoneCode: String?
|
||||
var emailVerification: Api.EmailVerification?
|
||||
@ -1326,6 +1389,17 @@ public func resetAuthorizationState(account: UnauthorizedAccount, to value: Auth
|
||||
}
|
||||
}
|
||||
|
||||
public func togglePreviousCodeEntry(account: UnauthorizedAccount) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState {
|
||||
if case let .confirmationCodeEntry(number, type, hash, timeout, nextType, syncContacts, previousCodeEntry, usePrevious) = state.contents, let previousCodeEntry {
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: state.isTestingEnvironment, masterDatacenterId: state.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: type, hash: hash, timeout: timeout, nextType: nextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: !usePrevious)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public enum ReportMissingCodeError {
|
||||
case generic
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import Postbox
|
||||
|
||||
public let liveLocationIndefinitePeriod: Int32 = 0x7fffffff
|
||||
|
||||
public final class NamedGeoPlace: PostboxCoding, Equatable {
|
||||
public let country: String?
|
||||
public let state: String?
|
||||
|
@ -173,10 +173,10 @@ public struct UnauthorizedAccountTermsOfService: PostboxCoding, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable {
|
||||
public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable {
|
||||
case empty
|
||||
case phoneEntry(countryCode: Int32, number: String)
|
||||
case confirmationCodeEntry(number: String, type: SentAuthorizationCodeType, hash: String, timeout: Int32?, nextType: AuthorizationCodeNextType?, syncContacts: Bool)
|
||||
case confirmationCodeEntry(number: String, type: SentAuthorizationCodeType, hash: String, timeout: Int32?, nextType: AuthorizationCodeNextType?, syncContacts: Bool, previousCodeEntry: UnauthorizedAccountStateContents?, usePrevious: Bool)
|
||||
case passwordEntry(hint: String, number: String?, code: AuthorizationCode?, suggestReset: Bool, syncContacts: Bool)
|
||||
case passwordRecovery(hint: String, number: String?, code: AuthorizationCode?, emailPattern: String, syncContacts: Bool)
|
||||
case awaitingAccountReset(protectedUntil: Int32, number: String?, syncContacts: Bool)
|
||||
@ -193,7 +193,7 @@ public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable {
|
||||
if let value = decoder.decodeOptionalInt32ForKey("nt") {
|
||||
nextType = AuthorizationCodeNextType(rawValue: value)
|
||||
}
|
||||
self = .confirmationCodeEntry(number: decoder.decodeStringForKey("num", orElse: ""), type: decoder.decodeObjectForKey("t", decoder: { SentAuthorizationCodeType(decoder: $0) }) as! SentAuthorizationCodeType, hash: decoder.decodeStringForKey("h", orElse: ""), timeout: decoder.decodeOptionalInt32ForKey("tm"), nextType: nextType, syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0)
|
||||
self = .confirmationCodeEntry(number: decoder.decodeStringForKey("num", orElse: ""), type: decoder.decodeObjectForKey("t", decoder: { SentAuthorizationCodeType(decoder: $0) }) as! SentAuthorizationCodeType, hash: decoder.decodeStringForKey("h", orElse: ""), timeout: decoder.decodeOptionalInt32ForKey("tm"), nextType: nextType, syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0, previousCodeEntry: decoder.decodeObjectForKey("previousCodeEntry", decoder: { UnauthorizedAccountStateContents(decoder: $0) }) as? UnauthorizedAccountStateContents, usePrevious: decoder.decodeInt32ForKey("usePrevious", orElse: 1) != 0)
|
||||
case UnauthorizedAccountStateContentsValue.passwordEntry.rawValue:
|
||||
var code: AuthorizationCode?
|
||||
if let modernCode = decoder.decodeObjectForKey("modernCode", decoder: { AuthorizationCode(decoder: $0) }) as? AuthorizationCode {
|
||||
@ -228,7 +228,7 @@ public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable {
|
||||
encoder.encodeInt32(UnauthorizedAccountStateContentsValue.phoneEntry.rawValue, forKey: "v")
|
||||
encoder.encodeInt32(countryCode, forKey: "cc")
|
||||
encoder.encodeString(number, forKey: "n")
|
||||
case let .confirmationCodeEntry(number, type, hash, timeout, nextType, syncContacts):
|
||||
case let .confirmationCodeEntry(number, type, hash, timeout, nextType, syncContacts, previousCodeEntry, usePrevious):
|
||||
encoder.encodeInt32(UnauthorizedAccountStateContentsValue.confirmationCodeEntry.rawValue, forKey: "v")
|
||||
encoder.encodeString(number, forKey: "num")
|
||||
encoder.encodeObject(type, forKey: "t")
|
||||
@ -244,6 +244,14 @@ public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable {
|
||||
encoder.encodeNil(forKey: "nt")
|
||||
}
|
||||
encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts")
|
||||
|
||||
if let previousCodeEntry = previousCodeEntry {
|
||||
encoder.encodeObject(previousCodeEntry, forKey: "previousCodeEntry")
|
||||
encoder.encodeInt32(usePrevious ? 1 : 0, forKey: "usePrevious")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "previousCodeEntry")
|
||||
encoder.encodeInt32(0, forKey: "usePrevious")
|
||||
}
|
||||
case let .passwordEntry(hint, number, code, suggestReset, syncContacts):
|
||||
encoder.encodeInt32(UnauthorizedAccountStateContentsValue.passwordEntry.rawValue, forKey: "v")
|
||||
encoder.encodeString(hint, forKey: "h")
|
||||
@ -312,8 +320,8 @@ public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .confirmationCodeEntry(lhsNumber, lhsType, lhsHash, lhsTimeout, lhsNextType, lhsSyncContacts):
|
||||
if case let .confirmationCodeEntry(rhsNumber, rhsType, rhsHash, rhsTimeout, rhsNextType, rhsSyncContacts) = rhs {
|
||||
case let .confirmationCodeEntry(lhsNumber, lhsType, lhsHash, lhsTimeout, lhsNextType, lhsSyncContacts, lhsPreviousCodeEntry, lhsUsePrevious):
|
||||
if case let .confirmationCodeEntry(rhsNumber, rhsType, rhsHash, rhsTimeout, rhsNextType, rhsSyncContacts, rhsPreviousCodeEntry, rhsUsePrevious) = rhs {
|
||||
if lhsNumber != rhsNumber {
|
||||
return false
|
||||
}
|
||||
@ -332,6 +340,12 @@ public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable {
|
||||
if lhsSyncContacts != rhsSyncContacts {
|
||||
return false
|
||||
}
|
||||
if lhsPreviousCodeEntry != rhsPreviousCodeEntry {
|
||||
return false
|
||||
}
|
||||
if lhsUsePrevious != rhsUsePrevious {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
@ -19,7 +19,7 @@ func _internal_topPeerActiveLiveLocationMessages(viewTracker: AccountViewTracker
|
||||
for entry in view.entries {
|
||||
for media in entry.message.media {
|
||||
if let location = media as? TelegramMediaMap, let liveBroadcastingTimeout = location.liveBroadcastingTimeout {
|
||||
if entry.message.timestamp + liveBroadcastingTimeout > timestamp {
|
||||
if liveBroadcastingTimeout == liveLocationIndefinitePeriod || entry.message.timestamp + liveBroadcastingTimeout > timestamp {
|
||||
result.append(entry.message)
|
||||
}
|
||||
} else {
|
||||
|
@ -982,8 +982,9 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
self.inlineStickerLayers = [stickerLayer]
|
||||
}
|
||||
stickerLayer.isVisibleForAnimations = self.visibility != .none
|
||||
stickerLayer.dynamicColor = file.isCustomTemplateEmoji ? mainColor : nil
|
||||
stickerLayer.frame = inlineMediaFrame
|
||||
} else if contentAnimatedFilesValue.count == 4 {
|
||||
} else if contentAnimatedFilesValue.count == 4, let file = contentAnimatedFilesValue.first {
|
||||
var stickerLayers: [InlineStickerItemLayer] = []
|
||||
if self.inlineStickerLayers.count == contentAnimatedFilesValue.count {
|
||||
stickerLayers = self.inlineStickerLayers
|
||||
@ -995,14 +996,16 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
}
|
||||
self.inlineStickerLayers = stickerLayers
|
||||
}
|
||||
|
||||
var frames: [CGRect] = []
|
||||
let smallSize = CGSize(width: inlineMediaFrame.width / 2.0, height: inlineMediaFrame.width / 2.0)
|
||||
frames.append(CGRect(origin: inlineMediaFrame.origin, size: smallSize))
|
||||
frames.append(CGRect(origin: inlineMediaFrame.origin.offsetBy(dx: smallSize.width, dy: 0.0), size: smallSize))
|
||||
frames.append(CGRect(origin: inlineMediaFrame.origin.offsetBy(dx: 0.0, dy: smallSize.height), size: smallSize))
|
||||
frames.append(CGRect(origin: inlineMediaFrame.origin.offsetBy(dx: smallSize.width, dy: smallSize.height), size: smallSize))
|
||||
frames.append(CGRect(origin: inlineMediaFrame.origin, size: smallSize).insetBy(dx: 2.0, dy: 2.0))
|
||||
frames.append(CGRect(origin: inlineMediaFrame.origin.offsetBy(dx: smallSize.width, dy: 0.0), size: smallSize).insetBy(dx: 2.0, dy: 2.0))
|
||||
frames.append(CGRect(origin: inlineMediaFrame.origin.offsetBy(dx: 0.0, dy: smallSize.height), size: smallSize).insetBy(dx: 2.0, dy: 2.0))
|
||||
frames.append(CGRect(origin: inlineMediaFrame.origin.offsetBy(dx: smallSize.width, dy: smallSize.height), size: smallSize).insetBy(dx: 2.0, dy: 2.0))
|
||||
for i in 0 ..< stickerLayers.count {
|
||||
stickerLayers[i].frame = frames[i]
|
||||
stickerLayers[i].dynamicColor = file.isCustomTemplateEmoji ? mainColor : nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
selectedMedia = telegramMap
|
||||
if let liveBroadcastingTimeout = telegramMap.liveBroadcastingTimeout {
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
if item.message.timestamp != scheduleWhenOnlineTimestamp && item.message.timestamp + liveBroadcastingTimeout > timestamp {
|
||||
if item.message.timestamp != scheduleWhenOnlineTimestamp && (liveBroadcastingTimeout == liveLocationIndefinitePeriod || item.message.timestamp + liveBroadcastingTimeout > timestamp) {
|
||||
activeLiveBroadcastingTimeout = liveBroadcastingTimeout
|
||||
}
|
||||
}
|
||||
@ -402,7 +402,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
let timerForegroundColor: UIColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.accentControlColor : item.presentationData.theme.theme.chat.message.outgoing.accentControlColor
|
||||
let timerTextColor: UIColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
|
||||
strongSelf.liveTimerNode?.update(backgroundColor: timerForegroundColor.withAlphaComponent(0.4), foregroundColor: timerForegroundColor, textColor: timerTextColor, beginTimestamp: Double(item.message.timestamp), timeout: Double(activeLiveBroadcastingTimeout), strings: item.presentationData.strings)
|
||||
strongSelf.liveTimerNode?.update(backgroundColor: timerForegroundColor.withAlphaComponent(0.4), foregroundColor: timerForegroundColor, textColor: timerTextColor, beginTimestamp: Double(item.message.timestamp), timeout: activeLiveBroadcastingTimeout == liveLocationIndefinitePeriod ? -1.0 : Double(activeLiveBroadcastingTimeout), strings: item.presentationData.strings)
|
||||
|
||||
if strongSelf.liveTextNode == nil {
|
||||
let liveTextNode = ChatMessageLiveLocationTextNode()
|
||||
@ -421,6 +421,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
strongSelf.liveTextNode?.update(color: timerTextColor, timestamp: Double(updateTimestamp), strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat)
|
||||
|
||||
if activeLiveBroadcastingTimeout != liveLocationIndefinitePeriod {
|
||||
let timeoutDeadline = item.message.timestamp + activeLiveBroadcastingTimeout
|
||||
if strongSelf.timeoutTimer?.1 != timeoutDeadline {
|
||||
strongSelf.timeoutTimer?.0.invalidate()
|
||||
@ -436,6 +437,10 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
strongSelf.timeoutTimer = (timer, timeoutDeadline)
|
||||
timer.start()
|
||||
}
|
||||
} else {
|
||||
strongSelf.timeoutTimer?.0.invalidate()
|
||||
strongSelf.timeoutTimer = nil
|
||||
}
|
||||
} else {
|
||||
if let liveTimerNode = strongSelf.liveTimerNode {
|
||||
strongSelf.liveTimerNode = nil
|
||||
|
@ -207,10 +207,14 @@ public final class MediaEditor {
|
||||
}
|
||||
|
||||
public var resultIsVideo: Bool {
|
||||
if self.values.entities.contains(where: { $0.entity.isAnimated }) {
|
||||
return true
|
||||
}
|
||||
if case let .sticker(file) = self.subject {
|
||||
return file.isAnimatedSticker || file.isVideoSticker
|
||||
} else {
|
||||
return self.player != nil || self.audioPlayer != nil || self.additionalPlayer != nil
|
||||
}
|
||||
return self.player != nil || self.audioPlayer != nil || self.additionalPlayer != nil || self.values.entities.contains(where: { $0.entity.isAnimated })
|
||||
}
|
||||
|
||||
public var resultImage: UIImage? {
|
||||
|
@ -6744,54 +6744,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
dismissImpl?()
|
||||
completion()
|
||||
|
||||
self.updateEditProgress(0.0, cancel: { [weak self] in
|
||||
self?.stickerUploadDisposable.set(nil)
|
||||
})
|
||||
self.stickerUploadDisposable.set((self.context.engine.stickers.createStickerSet(
|
||||
title: title ?? "",
|
||||
shortName: "",
|
||||
stickers: [
|
||||
ImportSticker(
|
||||
resource: .standalone(resource: file.resource),
|
||||
thumbnailResource: file.previewRepresentations.first.flatMap { .standalone(resource: $0.resource) },
|
||||
emojis: self.effectiveStickerEmoji(),
|
||||
dimensions: file.dimensions ?? PixelDimensions(width: 512, height: 512),
|
||||
duration: file.duration,
|
||||
mimeType: file.mimeType,
|
||||
keywords: ""
|
||||
)
|
||||
],
|
||||
thumbnail: nil,
|
||||
type: .stickers(content: .image),
|
||||
software: nil
|
||||
) |> deliverOnMainQueue).startStandalone(next: { [weak self] status in
|
||||
guard let self else {
|
||||
return
|
||||
if let title {
|
||||
self.uploadSticker(file, action: .createStickerPack(title: title))
|
||||
}
|
||||
switch status {
|
||||
case let .progress(progress, _, _):
|
||||
self.updateEditProgress(progress, cancel: { [weak self] in
|
||||
self?.stickerUploadDisposable.set(nil)
|
||||
})
|
||||
case let .complete(info, items):
|
||||
self.completion(MediaEditorScreen.Result(), { [weak self] finished in
|
||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let navigationController = self.navigationController as? NavigationController
|
||||
self.dismiss()
|
||||
if let navigationController {
|
||||
Queue.mainQueue().after(0.2) {
|
||||
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
|
||||
let controller = self.context.sharedContext.makeStickerPackScreen(context: self.context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [.result(info: info, items: items, installed: true)], isEditing: false, expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: self.sendSticker)
|
||||
(navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}))
|
||||
}, cancel: {})
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
@ -6855,38 +6810,38 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
|
||||
let signal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> castError(UploadStickerError.self)
|
||||
|> mapToSignal { peer -> Signal<UploadStickerStatus, UploadStickerError> in
|
||||
|> mapToSignal { peer -> Signal<(UploadStickerStatus, (StickerPackReference, String)?), UploadStickerError> in
|
||||
guard let peer else {
|
||||
return .complete()
|
||||
}
|
||||
return resourceSignal
|
||||
|> mapToSignal { result -> Signal<UploadStickerStatus, UploadStickerError> in
|
||||
|> mapToSignal { result -> Signal<(UploadStickerStatus, (StickerPackReference, String)?), UploadStickerError> in
|
||||
switch result {
|
||||
case .failed:
|
||||
return .fail(.generic)
|
||||
case let .progress(progress):
|
||||
return .single(.progress(progress * 0.5))
|
||||
return .single((.progress(progress * 0.5), nil))
|
||||
case let .complete(resource):
|
||||
if let resource = resource as? CloudDocumentMediaResource {
|
||||
return .single(.progress(1.0)) |> then(.single(.complete(resource, mimeType)))
|
||||
return .single((.progress(1.0), nil)) |> then(.single((.complete(resource, mimeType), nil)))
|
||||
} else {
|
||||
return context.engine.stickers.uploadSticker(peer: peer._asPeer(), resource: resource, thumbnail: file.previewRepresentations.first?.resource, alt: "", dimensions: dimensions, duration: duration, mimeType: mimeType)
|
||||
|> mapToSignal { status -> Signal<UploadStickerStatus, UploadStickerError> in
|
||||
|> mapToSignal { status -> Signal<(UploadStickerStatus, (StickerPackReference, String)?), UploadStickerError> in
|
||||
switch status {
|
||||
case let .progress(progress):
|
||||
return .single(.progress(isVideo ? 0.5 + progress * 0.5 : progress))
|
||||
return .single((.progress(isVideo ? 0.5 + progress * 0.5 : progress), nil))
|
||||
case let .complete(resource, _):
|
||||
let file = stickerFile(resource: resource, thumbnailResource: file.previewRepresentations.first?.resource, size: file.size ?? 0, dimensions: dimensions, duration: file.duration, isVideo: isVideo)
|
||||
switch action {
|
||||
case .send:
|
||||
return .single(status)
|
||||
return .single((status, nil))
|
||||
case .addToFavorites:
|
||||
return context.engine.stickers.toggleStickerSaved(file: file, saved: true)
|
||||
|> `catch` { _ -> Signal<SavedStickerResult, UploadStickerError> in
|
||||
return .fail(.generic)
|
||||
}
|
||||
|> map { _ in
|
||||
return status
|
||||
return (status, nil)
|
||||
}
|
||||
case let .createStickerPack(title):
|
||||
let sticker = ImportSticker(
|
||||
@ -6902,13 +6857,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
return .fail(.generic)
|
||||
}
|
||||
|> mapToSignal { innerStatus in
|
||||
if case .complete = innerStatus {
|
||||
return .single(status)
|
||||
if case let .complete(info, _) = innerStatus {
|
||||
return .single((status, (.id(id: info.id.id, accessHash: info.accessHash), title)))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
case let .addToStickerPack(pack, _):
|
||||
case let .addToStickerPack(pack, title):
|
||||
let sticker = ImportSticker(
|
||||
resource: .standalone(resource: resource),
|
||||
emojis: emojis,
|
||||
@ -6922,10 +6877,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
return .fail(.generic)
|
||||
}
|
||||
|> map { _ in
|
||||
return status
|
||||
return (status, (pack, title))
|
||||
}
|
||||
case .upload, .update:
|
||||
return .single(status)
|
||||
return .single((status, nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6934,7 +6889,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
}
|
||||
self.stickerUploadDisposable.set((signal
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] status in
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] (status, packReferenceAndTitle) in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -6976,15 +6931,14 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
if let parentController = navigationController?.viewControllers.last as? ViewController {
|
||||
parentController.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: self.context, file: file, loop: true, title: nil, text: presentationData.strings.Conversation_StickerAddedToFavorites, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
case let .addToStickerPack(packReference, title):
|
||||
let navigationController = self.navigationController as? NavigationController
|
||||
if let navigationController {
|
||||
case .addToStickerPack, .createStickerPack:
|
||||
if let (packReference, packTitle) = packReferenceAndTitle, let navigationController = self.navigationController as? NavigationController {
|
||||
Queue.mainQueue().after(0.2) {
|
||||
let controller = self.context.sharedContext.makeStickerPackScreen(context: self.context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], isEditing: false, expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: self.sendSticker)
|
||||
(navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
|
||||
|
||||
Queue.mainQueue().after(0.1) {
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: self.context, file: file, loop: true, title: nil, text: presentationData.strings.StickerPack_StickerAdded(title).string, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: self.context, file: file, loop: true, title: nil, text: presentationData.strings.StickerPack_StickerAdded(packTitle).string, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2547,7 +2547,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
contentRequiredValidation = true
|
||||
} else if message.flags.contains(.Incoming), let media = media as? TelegramMediaMap, let liveBroadcastingTimeout = media.liveBroadcastingTimeout {
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
if message.timestamp + liveBroadcastingTimeout > timestamp {
|
||||
if liveBroadcastingTimeout == liveLocationIndefinitePeriod || message.timestamp + liveBroadcastingTimeout > timestamp {
|
||||
messageIdsWithLiveLocation.append(message.id)
|
||||
}
|
||||
} else if let telegramFile = media as? TelegramMediaFile {
|
||||
|
Loading…
x
Reference in New Issue
Block a user