Merge commit '7357c597070cb3c5fd200a20b90c1ad881f29da9'

This commit is contained in:
Isaac 2024-04-16 23:42:42 +04:00
commit b4e1173279
24 changed files with 662 additions and 232 deletions

View File

@ -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";

View File

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

View File

@ -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, _):

View File

@ -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)
}
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
case let .passwordEntry(hint, _, _, suggestReset, syncContacts):
var controllers: [ViewController] = []
if !self.otherAccountPhoneNumbers.1.isEmpty {

View File

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

View File

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

View File

@ -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 {
broadcastToMessageIds[message.id] = message.timestamp + activeLiveBroadcastingTimeout
if activeLiveBroadcastingTimeout == liveLocationIndefinitePeriod {
broadcastToMessageIds[message.id] = activeLiveBroadcastingTimeout
} else {
broadcastToMessageIds[message.id] = message.timestamp + activeLiveBroadcastingTimeout
}
} else {
stopMessageIds.insert(message.id)
}

View File

@ -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,14 +73,19 @@ 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 intRemaining > 60 * 60 {
let hours = Int32(round(remaining / (60.0 * 60.0)))
string = strings.Map_LiveLocationShortHour("\(hours)").string
if timeout < 0.0 {
value = 0.0
string = ""
} else {
let minutes = Int32(round(remaining / (60.0)))
string = "\(minutes)"
let intRemaining = Int32(remaining)
if intRemaining > 60 * 60 {
let hours = Int32(round(remaining / (60.0 * 60.0)))
string = strings.Map_LiveLocationShortHour("\(hours)").string
} else {
let minutes = Int32(round(remaining / (60.0)))
string = "\(minutes)"
}
}
return ChatMessageLiveLocationTimerNodeParams(backgroundColor: backgroundColor, foregroundColor: foregroundColor, textColor: textColor, value: value, string: string)
@ -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)
}
}
}

View File

@ -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)
draw(context, position, false)
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)
draw(context, largerPos, false)
if !parameters.extend {
draw(context, largerPos, false)
}
draw(context, largerPos, true)
}
}

View File

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

View File

@ -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: [

View File

@ -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
if let user = peer as? TelegramUser {
title = strongSelf.presentationData.strings.Map_LiveLocationPrivateDescription(EnginePeer(user).compactDisplayTitle).string
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_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: [

View File

@ -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 {
interaction?.stopLiveLocation()
if let timeout, Int32(timeout) != liveLocationIndefinitePeriod {
if additional {
interaction?.stopLiveLocation()
} else {
interaction?.sendLiveLocation(nil, true)
}
} else {
interaction?.stopLiveLocation()
}
} else {
interaction?.sendLiveLocation(nil)
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
}

View File

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

View File

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

View File

@ -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 ?? ""
@ -694,8 +757,8 @@ public func sendLoginEmailCode(account: UnauthorizedAccount, email: String) -> S
|> mapToSignal { result -> Signal<Never, AuthorizationSendEmailCodeError> in
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)))
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, 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
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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,20 +421,25 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.liveTextNode?.update(color: timerTextColor, timestamp: Double(updateTimestamp), strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat)
let timeoutDeadline = item.message.timestamp + activeLiveBroadcastingTimeout
if strongSelf.timeoutTimer?.1 != timeoutDeadline {
strongSelf.timeoutTimer?.0.invalidate()
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if activeLiveBroadcastingTimeout != liveLocationIndefinitePeriod {
let timeoutDeadline = item.message.timestamp + activeLiveBroadcastingTimeout
if strongSelf.timeoutTimer?.1 != timeoutDeadline {
strongSelf.timeoutTimer?.0.invalidate()
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let timer = SwiftSignalKit.Timer(timeout: Double(max(0, timeoutDeadline - currentTimestamp)), repeat: false, completion: {
if let strongSelf = self {
strongSelf.timeoutTimer?.0.invalidate()
strongSelf.timeoutTimer = nil
item.controllerInteraction.requestMessageUpdate(item.message.id, false)
}
}, queue: Queue.mainQueue())
strongSelf.timeoutTimer = (timer, timeoutDeadline)
timer.start()
let timer = SwiftSignalKit.Timer(timeout: Double(max(0, timeoutDeadline - currentTimestamp)), repeat: false, completion: {
if let strongSelf = self {
strongSelf.timeoutTimer?.0.invalidate()
strongSelf.timeoutTimer = nil
item.controllerInteraction.requestMessageUpdate(item.message.id, false)
}
}, queue: Queue.mainQueue())
strongSelf.timeoutTimer = (timer, timeoutDeadline)
timer.start()
}
} else {
strongSelf.timeoutTimer?.0.invalidate()
strongSelf.timeoutTimer = nil
}
} else {
if let liveTimerNode = strongSelf.liveTimerNode {

View File

@ -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? {

View File

@ -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
}
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))
}
}
})
})
}
}))
if let title {
self.uploadSticker(file, action: .createStickerPack(title: title))
}
}, 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)
}
}
}

View File

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