Various improvements

This commit is contained in:
Ilya Laktyushin 2022-08-28 15:07:00 +02:00
parent 7f2ab1788f
commit bb06841df3
70 changed files with 1990 additions and 769 deletions

View File

@ -7809,8 +7809,6 @@ Sorry for the inconvenience.";
"Premium.Gift.Info" = "You can review the list of features and terms of use for Telegram Premium [here]()."; "Premium.Gift.Info" = "You can review the list of features and terms of use for Telegram Premium [here]().";
"Premium.Gift.GiftSubscription" = "Gift Subscription for %@"; "Premium.Gift.GiftSubscription" = "Gift Subscription for %@";
"Premium.Gift.PricePerMonth" = "%@ / month";
"Premium.Gift.Months_1" = "%@ Month"; "Premium.Gift.Months_1" = "%@ Month";
"Premium.Gift.Months_any" = "%@ Months"; "Premium.Gift.Months_any" = "%@ Months";
@ -7986,15 +7984,23 @@ Sorry for the inconvenience.";
"Login.EnterCodeSMSTitle" = "Enter Code"; "Login.EnterCodeSMSTitle" = "Enter Code";
"Login.EnterCodeSMSText" = "We've sent and SMS with an activation code to your phone **%@**."; "Login.EnterCodeSMSText" = "We've sent and SMS with an activation code to your phone **%@**.";
"Login.SendCodeAsSMS" = "Send the code as an SMS"; "Login.SendCodeAsSMS" = "Send the code as an SMS";
"Login.EnterCodeTelegramTitle" = "Enter Code"; "Login.EnterCodeTelegramTitle" = "Enter Code";
"Login.EnterCodeTelegramText" = "We've sent the code to the **Telegram app** for %@ on your other device."; "Login.EnterCodeTelegramText" = "We've sent the code to the **Telegram app** for %@ on your other device.";
"Login.AddEmailTitle" = "Add Email"; "Login.AddEmailTitle" = "Add Email";
"Login.AddEmailText" = "Please enter your valid email address to protect your account."; "Login.AddEmailText" = "Please enter your valid email address to protect your account.";
"Login.AddEmailPlaceholder" = "Enter your email"; "Login.AddEmailPlaceholder" = "Enter your email";
"Login.EnterCodeEmailTitle" = "Check Your Email"; "Login.EnterCodeEmailTitle" = "Check Your Email";
"Login.EnterCodeEmailText" = "Please enter the code we have sent to your email %@."; "Login.EnterCodeEmailText" = "Please enter the code we have sent to your email %@.";
"Login.EnterNewEmailTitle" = "Enter New Email";
"Login.EnterCodeNewEmailTitle" = "Check Your New Email";
"Login.EnterCodeNewEmailText" = "Please enter the code we have sent to your new email %@.";
"Login.WrongCodeError" = "Wrong code, please try again."; "Login.WrongCodeError" = "Wrong code, please try again.";
"PrivacySettings.LoginEmail" = "Login Email";
"Conversation.EmojiCopied" = "Emoji copied to clipboard";
"Conversation.EmojiTooltip" = "Tap here to choose more emoji";
"Premium.PricePerMonth" = "%@/month";
"Premium.PricePerYear" = "%@/year";

View File

@ -23,6 +23,7 @@ swift_library(
"//submodules/MeshAnimationCache:MeshAnimationCache", "//submodules/MeshAnimationCache:MeshAnimationCache",
"//submodules/Utils/RangeSet:RangeSet", "//submodules/Utils/RangeSet:RangeSet",
"//submodules/InAppPurchaseManager:InAppPurchaseManager", "//submodules/InAppPurchaseManager:InAppPurchaseManager",
"//submodules/TextFormat:TextFormat",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -752,6 +752,8 @@ public protocol SharedAccountContext: AnyObject {
func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController
func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController
func navigateToCurrentCall() func navigateToCurrentCall()
var hasOngoingCall: ValuePromise<Bool> { get } var hasOngoingCall: ValuePromise<Bool> { get }
var immediateHasOngoingCall: Bool { get } var immediateHasOngoingCall: Bool { get }

View File

@ -7,6 +7,7 @@ import Display
import AsyncDisplayKit import AsyncDisplayKit
import UniversalMediaPlayer import UniversalMediaPlayer
import TelegramPresentationData import TelegramPresentationData
import TextFormat
public enum ChatControllerInteractionOpenMessageMode { public enum ChatControllerInteractionOpenMessageMode {
case `default` case `default`
@ -37,6 +38,7 @@ public final class OpenChatMessageParams {
public let callPeer: (PeerId, Bool) -> Void public let callPeer: (PeerId, Bool) -> Void
public let enqueueMessage: (EnqueueMessage) -> Void public let enqueueMessage: (EnqueueMessage) -> Void
public let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? public let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
public let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?
public let setupTemporaryHiddenMedia: (Signal<Any?, NoError>, Int, Media) -> Void public let setupTemporaryHiddenMedia: (Signal<Any?, NoError>, Int, Media) -> Void
public let chatAvatarHiddenMedia: (Signal<MessageId?, NoError>, Media) -> Void public let chatAvatarHiddenMedia: (Signal<MessageId?, NoError>, Media) -> Void
public let actionInteraction: GalleryControllerActionInteraction? public let actionInteraction: GalleryControllerActionInteraction?
@ -64,6 +66,7 @@ public final class OpenChatMessageParams {
callPeer: @escaping (PeerId, Bool) -> Void, callPeer: @escaping (PeerId, Bool) -> Void,
enqueueMessage: @escaping (EnqueueMessage) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void,
sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?,
sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?,
setupTemporaryHiddenMedia: @escaping (Signal<Any?, NoError>, Int, Media) -> Void, setupTemporaryHiddenMedia: @escaping (Signal<Any?, NoError>, Int, Media) -> Void,
chatAvatarHiddenMedia: @escaping (Signal<MessageId?, NoError>, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal<MessageId?, NoError>, Media) -> Void,
actionInteraction: GalleryControllerActionInteraction? = nil, actionInteraction: GalleryControllerActionInteraction? = nil,
@ -90,6 +93,7 @@ public final class OpenChatMessageParams {
self.callPeer = callPeer self.callPeer = callPeer
self.enqueueMessage = enqueueMessage self.enqueueMessage = enqueueMessage
self.sendSticker = sendSticker self.sendSticker = sendSticker
self.sendEmoji = sendEmoji
self.setupTemporaryHiddenMedia = setupTemporaryHiddenMedia self.setupTemporaryHiddenMedia = setupTemporaryHiddenMedia
self.chatAvatarHiddenMedia = chatAvatarHiddenMedia self.chatAvatarHiddenMedia = chatAvatarHiddenMedia
self.actionInteraction = actionInteraction self.actionInteraction = actionInteraction

View File

@ -12,10 +12,32 @@ swift_library(
deps = [ deps = [
"//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/TelegramCore:TelegramCore", "//submodules/TelegramCore:TelegramCore",
"//submodules/Postbox:Postbox",
"//submodules/Display:Display", "//submodules/Display:Display",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/TextFormat:TextFormat", "//submodules/TextFormat:TextFormat",
"//submodules/Markdown:Markdown", "//submodules/Markdown:Markdown",
"//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AccountContext:AccountContext",
"//submodules/CountrySelectionUI:CountrySelectionUI",
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
"//submodules/LegacyComponents:LegacyComponents",
"//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI",
"//submodules/PasswordSetupUI:PasswordSetupUI",
"//submodules/TelegramNotices:TelegramNotices",
"//submodules/ProgressNavigationButtonNode:ProgressNavigationButtonNode",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/ImageCompression:ImageCompression",
"//submodules/RMIntro:RMIntro",
"//submodules/QrCode:QrCode",
"//submodules/PhoneInputNode:PhoneInputNode",
"//submodules/CodeInputView:CodeInputView",
"//submodules/DebugSettingsUI:DebugSettingsUI",
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
"//submodules/AuthorizationUtils:AuthorizationUtils",
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -4,7 +4,7 @@ import AsyncDisplayKit
import Display import Display
import SwiftSignalKit import SwiftSignalKit
import TelegramPresentationData import TelegramPresentationData
import AuthorizationUI import AuthorizationUtils
private func timerValueString(days: Int32, hours: Int32, minutes: Int32, color: UIColor, strings: PresentationStrings) -> NSAttributedString { private func timerValueString(days: Int32, hours: Int32, minutes: Int32, color: UIColor, strings: PresentationStrings) -> NSAttributedString {
var daysString = "" var daysString = ""

View File

@ -6,7 +6,7 @@ import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import ProgressNavigationButtonNode import ProgressNavigationButtonNode
final class AuthorizationSequenceCodeEntryController: ViewController { public final class AuthorizationSequenceCodeEntryController: ViewController {
private var controllerNode: AuthorizationSequenceCodeEntryControllerNode { private var controllerNode: AuthorizationSequenceCodeEntryControllerNode {
return self.displayNode as! AuthorizationSequenceCodeEntryControllerNode return self.displayNode as! AuthorizationSequenceCodeEntryControllerNode
} }
@ -15,20 +15,20 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
private let theme: PresentationTheme private let theme: PresentationTheme
private let openUrl: (String) -> Void private let openUrl: (String) -> Void
var loginWithCode: ((String) -> Void)? public var loginWithCode: ((String) -> Void)?
var signInWithApple: (() -> Void)? public var signInWithApple: (() -> Void)?
var reset: (() -> Void)? var reset: (() -> Void)?
var requestNextOption: (() -> Void)? var requestNextOption: (() -> Void)?
var data: (String, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?)? var data: (String, String?, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?)?
var termsOfService: (UnauthorizedAccountTermsOfService, Bool)? var termsOfService: (UnauthorizedAccountTermsOfService, Bool)?
private let hapticFeedback = HapticFeedback() private let hapticFeedback = HapticFeedback()
private var appleSignInAllowed = false private var appleSignInAllowed = false
var inProgress: Bool = false { public var inProgress: Bool = false {
didSet { didSet {
// if self.inProgress { // if self.inProgress {
// let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.rootController.navigationBar.accentTextColor)) // let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.rootController.navigationBar.accentTextColor))
@ -40,7 +40,7 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
} }
} }
init(presentationData: PresentationData, openUrl: @escaping (String) -> Void, back: @escaping () -> Void) { public init(presentationData: PresentationData, openUrl: @escaping (String) -> Void, back: @escaping () -> Void) {
self.strings = presentationData.strings self.strings = presentationData.strings
self.theme = presentationData.theme self.theme = presentationData.theme
self.openUrl = openUrl self.openUrl = openUrl
@ -96,16 +96,16 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
self?.navigationItem.rightBarButtonItem?.isEnabled = value self?.navigationItem.rightBarButtonItem?.isEnabled = value
} }
if let (number, codeType, nextType, timeout) = self.data { if let (number, email, codeType, nextType, timeout) = self.data {
var appleSignInAllowed = false var appleSignInAllowed = false
if case let .email(_, _, _, appleSignInAllowedValue, _) = codeType { if case let .email(_, _, _, appleSignInAllowedValue, _) = codeType {
appleSignInAllowed = appleSignInAllowedValue appleSignInAllowed = appleSignInAllowedValue
} }
self.controllerNode.updateData(number: number, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed) self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed)
} }
} }
override func viewDidAppear(_ animated: Bool) { override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
self.controllerNode.activateInput() self.controllerNode.activateInput()
@ -115,19 +115,19 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
self.controllerNode.resetCode() self.controllerNode.resetCode()
} }
func animateSuccess() { public func animateSuccess() {
self.controllerNode.animateSuccess() self.controllerNode.animateSuccess()
} }
func animateError(text: String) { public func animateError(text: String) {
self.hapticFeedback.error() self.hapticFeedback.error()
self.controllerNode.animateError(text: text) self.controllerNode.animateError(text: text)
} }
func updateData(number: 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)?) {
self.termsOfService = termsOfService self.termsOfService = termsOfService
if self.data?.0 != number || self.data?.1 != codeType || self.data?.2 != nextType || self.data?.3 != timeout { if self.data?.0 != number || self.data?.1 != email || self.data?.2 != codeType || self.data?.3 != nextType || self.data?.4 != timeout {
self.data = (number, codeType, nextType, timeout) self.data = (number, email, codeType, nextType, timeout)
var appleSignInAllowed = false var appleSignInAllowed = false
if case let .email(_, _, _, appleSignInAllowedValue, _) = codeType { if case let .email(_, _, _, appleSignInAllowedValue, _) = codeType {
@ -135,20 +135,20 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
} }
if self.isNodeLoaded { if self.isNodeLoaded {
self.controllerNode.updateData(number: number, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed) self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed)
self.requestLayout(transition: .immediate) self.requestLayout(transition: .immediate)
} }
} }
} }
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition) super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
} }
@objc func nextPressed() { @objc private func nextPressed() {
guard let (_, type, _, _) = self.data else { guard let (_, _, type, _, _) = self.data else {
return return
} }

View File

@ -6,7 +6,6 @@ import TelegramCore
import SwiftSignalKit import SwiftSignalKit
import TelegramPresentationData import TelegramPresentationData
import TextFormat import TextFormat
import AuthorizationUI
import AuthenticationServices import AuthenticationServices
import CodeInputView import CodeInputView
import PhoneNumberFormat import PhoneNumberFormat
@ -14,6 +13,7 @@ import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
import SolidRoundedButtonNode import SolidRoundedButtonNode
import InvisibleInkDustNode import InvisibleInkDustNode
import AuthorizationUtils
final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextFieldDelegate { final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextFieldDelegate {
private let strings: PresentationStrings private let strings: PresentationStrings
@ -54,6 +54,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
} }
} }
var email: String?
var currentCode: String { var currentCode: String {
return self.codeInputView.text return self.codeInputView.text
} }
@ -202,9 +204,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.codeInputView.text = "" self.codeInputView.text = ""
} }
func updateData(number: String, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, appleSignInAllowed: Bool) { func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, appleSignInAllowed: Bool) {
self.codeType = codeType self.codeType = codeType
self.phoneNumber = number self.phoneNumber = number
self.email = email
var appleSignInAllowed = appleSignInAllowed var appleSignInAllowed = appleSignInAllowed
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
@ -213,7 +216,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
} }
self.appleSignInAllowed = appleSignInAllowed self.appleSignInAllowed = appleSignInAllowed
self.currentOptionNode.attributedText = authorizationCurrentOptionText(codeType, phoneNumber: self.phoneNumber, email: nil, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor) self.currentOptionNode.attributedText = authorizationCurrentOptionText(codeType, phoneNumber: self.phoneNumber, email: self.email, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor)
if case .missedCall = codeType { if case .missedCall = codeType {
self.currentOptionInfoNode.attributedText = NSAttributedString(string: self.strings.Login_CodePhonePatternInfoText, font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center) self.currentOptionInfoNode.attributedText = NSAttributedString(string: self.strings.Login_CodePhonePatternInfoText, font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
} else { } else {

View File

@ -12,7 +12,6 @@ import TelegramPresentationData
import TextFormat import TextFormat
import AccountContext import AccountContext
import CountrySelectionUI import CountrySelectionUI
import SettingsUI
import PhoneNumberFormat import PhoneNumberFormat
import LegacyComponents import LegacyComponents
import LegacyMediaPickerUI import LegacyMediaPickerUI
@ -247,7 +246,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
guard let strongSelf = self, let controller = controller else { guard let strongSelf = self, let controller = controller else {
return return
} }
controller.present(proxySettingsController(accountManager: strongSelf.sharedContext.accountManager, postbox: strongSelf.account.postbox, network: strongSelf.account.network, mode: .modal, presentationData: strongSelf.sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: strongSelf.sharedContext.presentationData), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) controller.present(strongSelf.sharedContext.makeProxySettingsController(sharedContext: strongSelf.sharedContext, account: strongSelf.account), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
})) }))
} }
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: actions), in: .window(.root)) controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: actions), in: .window(.root))
@ -260,11 +259,11 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
return controller return controller
} }
private func codeEntryController(number: String, type: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) -> AuthorizationSequenceCodeEntryController { private func codeEntryController(number: String, email: String?, type: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) -> AuthorizationSequenceCodeEntryController {
var currentController: AuthorizationSequenceCodeEntryController? var currentController: AuthorizationSequenceCodeEntryController?
for c in self.viewControllers { for c in self.viewControllers {
if let c = c as? AuthorizationSequenceCodeEntryController { if let c = c as? AuthorizationSequenceCodeEntryController {
if c.data?.1 == type { if c.data?.2 == type {
currentController = c currentController = c
} }
break break
@ -502,11 +501,14 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
authorizationController.performRequests() authorizationController.performRequests()
} }
} }
controller.updateData(number: formatPhoneNumber(number), codeType: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService) controller.updateData(number: formatPhoneNumber(number), email: email, codeType: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService)
return controller return controller
} }
private var signInWithAppleSetup = false private var signInWithAppleSetup = false
private var appleSignInAllowed = false
private var currentEmail: String?
private func emailSetupController(number: String, appleSignInAllowed: Bool) -> AuthorizationSequenceEmailEntryController { private func emailSetupController(number: String, appleSignInAllowed: Bool) -> AuthorizationSequenceEmailEntryController {
var currentController: AuthorizationSequenceEmailEntryController? var currentController: AuthorizationSequenceEmailEntryController?
for c in self.viewControllers { for c in self.viewControllers {
@ -519,7 +521,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
if let currentController = currentController { if let currentController = currentController {
controller = currentController controller = currentController
} else { } else {
controller = AuthorizationSequenceEmailEntryController(presentationData: self.presentationData, back: { [weak self] in controller = AuthorizationSequenceEmailEntryController(presentationData: self.presentationData, mode: .setup, back: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -536,9 +538,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
controller?.inProgress = true controller?.inProgress = true
strongSelf.actionDisposable.set((sendLoginEmailCode(account: strongSelf.account, email: email) strongSelf.actionDisposable.set((sendLoginEmailCode(account: strongSelf.account, email: email)
|> deliverOnMainQueue).start(next: { result in |> deliverOnMainQueue).start(error: { error in
controller?.inProgress = false
}, error: { error in
if let strongSelf = self, let controller = controller { if let strongSelf = self, let controller = controller {
controller.inProgress = false controller.inProgress = false
@ -554,6 +554,12 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
} }
}, completed: { [weak self] in
controller?.inProgress = false
if let strongSelf = self {
strongSelf.currentEmail = email
}
})) }))
} }
controller.signInWithApple = { [weak self] in controller.signInWithApple = { [weak self] in
@ -1017,9 +1023,13 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
} }
controllers.append(self.phoneEntryController(countryCode: defaultCountryCode(), number: "", splashController: nil)) controllers.append(self.phoneEntryController(countryCode: defaultCountryCode(), number: "", splashController: nil))
if case let .emailSetupRequired(appleSignInAllowed) = type { if case let .emailSetupRequired(appleSignInAllowed) = type {
self.appleSignInAllowed = appleSignInAllowed
controllers.append(self.emailSetupController(number: number, appleSignInAllowed: appleSignInAllowed)) controllers.append(self.emailSetupController(number: number, appleSignInAllowed: appleSignInAllowed))
} else { } else {
controllers.append(self.codeEntryController(number: number, type: type, nextType: nextType, timeout: timeout, termsOfService: nil)) if let _ = self.currentEmail {
controllers.append(self.emailSetupController(number: number, appleSignInAllowed: self.appleSignInAllowed))
}
controllers.append(self.codeEntryController(number: number, email: self.currentEmail, type: type, nextType: nextType, timeout: timeout, termsOfService: nil))
} }
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty) self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
case let .passwordEntry(hint, _, _, suggestReset, syncContacts): case let .passwordEntry(hint, _, _, suggestReset, syncContacts):

View File

@ -5,34 +5,42 @@ import AsyncDisplayKit
import TelegramPresentationData import TelegramPresentationData
import ProgressNavigationButtonNode import ProgressNavigationButtonNode
final class AuthorizationSequenceEmailEntryController: ViewController { public final class AuthorizationSequenceEmailEntryController: ViewController {
public enum Mode {
case setup
case change
}
private let mode: Mode
private var controllerNode: AuthorizationSequenceEmailEntryControllerNode { private var controllerNode: AuthorizationSequenceEmailEntryControllerNode {
return self.displayNode as! AuthorizationSequenceEmailEntryControllerNode return self.displayNode as! AuthorizationSequenceEmailEntryControllerNode
} }
private let presentationData: PresentationData private let presentationData: PresentationData
var proceedWithEmail: ((String) -> Void)? public var proceedWithEmail: ((String) -> Void)?
var signInWithApple: (() -> Void)? public var signInWithApple: (() -> Void)?
private let hapticFeedback = HapticFeedback() private let hapticFeedback = HapticFeedback()
private var appleSignInAllowed = false private var appleSignInAllowed = false
var inProgress: Bool = false { public var inProgress: Bool = false {
didSet { didSet {
if self.inProgress { // if self.inProgress {
let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.accentTextColor)) // let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.accentTextColor))
self.navigationItem.rightBarButtonItem = item // self.navigationItem.rightBarButtonItem = item
} else { // } else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) // self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
} // }
self.controllerNode.inProgress = self.inProgress self.controllerNode.inProgress = self.inProgress
} }
} }
init(presentationData: PresentationData, back: @escaping () -> Void) { public init(presentationData: PresentationData, mode: Mode, back: @escaping () -> Void) {
self.presentationData = presentationData self.presentationData = presentationData
self.mode = mode
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings))) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings)))
@ -57,7 +65,7 @@ final class AuthorizationSequenceEmailEntryController: ViewController {
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = AuthorizationSequenceEmailEntryControllerNode(strings: self.presentationData.strings, theme: self.presentationData.theme) self.displayNode = AuthorizationSequenceEmailEntryControllerNode(strings: self.presentationData.strings, theme: self.presentationData.theme, mode: self.mode)
self.displayNodeDidLoad() self.displayNodeDidLoad()
self.controllerNode.view.disableAutomaticKeyboardHandling = [.forward, .backward] self.controllerNode.view.disableAutomaticKeyboardHandling = [.forward, .backward]
@ -73,13 +81,13 @@ final class AuthorizationSequenceEmailEntryController: ViewController {
self.controllerNode.updateData(appleSignInAllowed: self.appleSignInAllowed) self.controllerNode.updateData(appleSignInAllowed: self.appleSignInAllowed)
} }
override func viewDidAppear(_ animated: Bool) { override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
self.controllerNode.activateInput() self.controllerNode.activateInput()
} }
func updateData(appleSignInAllowed: Bool) { public func updateData(appleSignInAllowed: Bool) {
var appleSignInAllowed = appleSignInAllowed var appleSignInAllowed = appleSignInAllowed
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
} else { } else {
@ -93,13 +101,13 @@ final class AuthorizationSequenceEmailEntryController: ViewController {
} }
} }
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition) super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
} }
@objc func nextPressed() { @objc private func nextPressed() {
if self.controllerNode.currentEmail.isEmpty { if self.controllerNode.currentEmail.isEmpty {
if self.appleSignInAllowed { if self.appleSignInAllowed {
self.signInWithApple?() self.signInWithApple?()

View File

@ -3,7 +3,7 @@ import UIKit
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import TelegramPresentationData import TelegramPresentationData
import AuthorizationUI import AuthorizationUtils
import AuthenticationServices import AuthenticationServices
import AnimatedStickerNode import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
@ -50,6 +50,7 @@ final class AuthorizationDividerNode: ASDisplayNode {
final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UITextFieldDelegate { final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UITextFieldDelegate {
private let strings: PresentationStrings private let strings: PresentationStrings
private let theme: PresentationTheme private let theme: PresentationTheme
private let mode: AuthorizationSequenceEmailEntryController.Mode
private let animationNode: AnimatedStickerNode private let animationNode: AnimatedStickerNode
private let titleNode: ASTextNode private let titleNode: ASTextNode
@ -79,9 +80,10 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
} }
} }
init(strings: PresentationStrings, theme: PresentationTheme) { init(strings: PresentationStrings, theme: PresentationTheme, mode: AuthorizationSequenceEmailEntryController.Mode) {
self.strings = strings self.strings = strings
self.theme = theme self.theme = theme
self.mode = mode
self.animationNode = DefaultAnimatedStickerNodeImpl() self.animationNode = DefaultAnimatedStickerNodeImpl()
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "IntroMail"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "IntroMail"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
@ -90,7 +92,6 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
self.titleNode = ASTextNode() self.titleNode = ASTextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = false self.titleNode.displaysAsynchronously = false
self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_AddEmailTitle, font: Font.light(30.0), textColor: self.theme.list.itemPrimaryTextColor)
self.noticeNode = ASTextNode() self.noticeNode = ASTextNode()
self.noticeNode.isUserInteractionEnabled = false self.noticeNode.isUserInteractionEnabled = false
@ -190,10 +191,10 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
insets.top = layout.statusBarHeight ?? 20.0 insets.top = layout.statusBarHeight ?? 20.0
if let inputHeight = layout.inputHeight { if let inputHeight = layout.inputHeight {
insets.bottom += max(inputHeight, insets.bottom) insets.bottom = max(inputHeight, insets.bottom)
} }
self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_AddEmailTitle, font: Font.bold(28.0), textColor: self.theme.list.itemPrimaryTextColor) self.titleNode.attributedText = NSAttributedString(string: self.mode == .setup ? self.strings.Login_AddEmailTitle : self.strings.Login_EnterNewEmailTitle, font: Font.bold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
let animationSize = CGSize(width: 100.0, height: 100.0) let animationSize = CGSize(width: 100.0, height: 100.0)
let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude)) let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
@ -209,19 +210,22 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.noticeNode, size: noticeSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.noticeNode, size: noticeSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 32.0, maxValue: 60.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 22.0, maxValue: 40.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 48.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 48.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
let inset: CGFloat = 24.0
let buttonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - proceedSize.width) / 2.0), y: layout.size.height - insets.bottom - proceedSize.height - inset), size: proceedSize)
transition.updateFrame(node: self.proceedNode, frame: buttonFrame)
let dividerSize = self.dividerNode.updateLayout(width: layout.size.width)
transition.updateFrame(node: self.dividerNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - dividerSize.width) / 2.0), y: buttonFrame.minY - dividerSize.height), size: dividerSize))
if let _ = self.signInWithAppleButton, self.appleSignInAllowed { if let _ = self.signInWithAppleButton, self.appleSignInAllowed {
self.dividerNode.isHidden = false self.dividerNode.isHidden = false
let dividerSize = self.dividerNode.updateLayout(width: layout.size.width)
items.append(AuthorizationLayoutItem(node: self.dividerNode, size: dividerSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 48.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
} else { } else {
self.dividerNode.isHidden = true self.dividerNode.isHidden = true
} }
items.append(AuthorizationLayoutItem(node: self.proceedNode, size: proceedSize, spacingBefore: self.dividerNode.isHidden ? AuthorizationLayoutItemSpacing(weight: 48.0, maxValue: 100.0) : AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
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 - 80.0)), items: items, transition: transition, failIfDoesNotFit: false) 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 - 80.0)), items: items, transition: transition, failIfDoesNotFit: false)
if let signInWithAppleButton = self.signInWithAppleButton, self.appleSignInAllowed { if let signInWithAppleButton = self.signInWithAppleButton, self.appleSignInAllowed {

View File

@ -3,7 +3,7 @@ import UIKit
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import TelegramPresentationData import TelegramPresentationData
import AuthorizationUI import AuthorizationUtils
final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UITextFieldDelegate { final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UITextFieldDelegate {
private let strings: PresentationStrings private let strings: PresentationStrings

View File

@ -3,7 +3,7 @@ import UIKit
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import TelegramPresentationData import TelegramPresentationData
import AuthorizationUI import AuthorizationUtils
final class AuthorizationSequencePasswordRecoveryControllerNode: ASDisplayNode, UITextFieldDelegate { final class AuthorizationSequencePasswordRecoveryControllerNode: ASDisplayNode, UITextFieldDelegate {
private let strings: PresentationStrings private let strings: PresentationStrings

View File

@ -9,7 +9,6 @@ import TelegramPresentationData
import ProgressNavigationButtonNode import ProgressNavigationButtonNode
import AccountContext import AccountContext
import CountrySelectionUI import CountrySelectionUI
import SettingsUI
import PhoneNumberFormat import PhoneNumberFormat
import DebugSettingsUI import DebugSettingsUI
@ -30,6 +29,18 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
private var currentData: (Int32, String?, String)? private var currentData: (Int32, String?, String)?
var codeNode: ASDisplayNode {
return self.controllerNode.codeNode
}
var numberNode: ASDisplayNode {
return self.controllerNode.numberNode
}
var buttonNode: ASDisplayNode {
return self.controllerNode.buttonNode
}
var inProgress: Bool = false { var inProgress: Bool = false {
didSet { didSet {
// if self.inProgress { // if self.inProgress {
@ -39,11 +50,14 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) // self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
// } // }
self.controllerNode.inProgress = self.inProgress self.controllerNode.inProgress = self.inProgress
self.confirmationController?.inProgress = self.inProgress
} }
} }
var loginWithNumber: ((String, Bool) -> Void)? var loginWithNumber: ((String, Bool) -> Void)?
var accountUpdated: ((UnauthorizedAccount) -> Void)? var accountUpdated: ((UnauthorizedAccount) -> Void)?
weak var confirmationController: PhoneConfirmationController?
private let termsDisposable = MetaDisposable() private let termsDisposable = MetaDisposable()
private let hapticFeedback = HapticFeedback() private let hapticFeedback = HapticFeedback()
@ -98,13 +112,13 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
} }
private var shouldAnimateIn = false private var shouldAnimateIn = false
private var transitionInArguments: (buttonFrame: CGRect, animationSnapshot: UIView, textSnapshot: UIView)? private var transitionInArguments: (buttonFrame: CGRect, buttonTitle: String, animationSnapshot: UIView, textSnapshot: UIView)?
func animateWithSplashController(_ controller: AuthorizationSequenceSplashController) { func animateWithSplashController(_ controller: AuthorizationSequenceSplashController) {
self.shouldAnimateIn = true self.shouldAnimateIn = true
if let animationSnapshot = controller.animationSnapshot, let textSnapshot = controller.textSnaphot { if let animationSnapshot = controller.animationSnapshot, let textSnapshot = controller.textSnaphot {
self.transitionInArguments = (controller.buttonFrame, animationSnapshot, textSnapshot) self.transitionInArguments = (controller.buttonFrame, controller.buttonTitle, animationSnapshot, textSnapshot)
} }
} }
@ -163,8 +177,8 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
if self.shouldAnimateIn { if self.shouldAnimateIn {
self.animatingIn = true self.animatingIn = true
if let (buttonFrame, animationSnapshot, textSnapshot) = self.transitionInArguments { if let (buttonFrame, buttonTitle, animationSnapshot, textSnapshot) = self.transitionInArguments {
self.controllerNode.willAnimateIn(buttonFrame: buttonFrame, animationSnapshot: animationSnapshot, textSnapshot: textSnapshot) self.controllerNode.willAnimateIn(buttonFrame: buttonFrame, buttonTitle: buttonTitle, animationSnapshot: animationSnapshot, textSnapshot: textSnapshot)
} }
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
self.controllerNode.activateInput() self.controllerNode.activateInput()
@ -182,15 +196,23 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
} }
} }
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let confirmationController = self.confirmationController {
confirmationController.transitionOut()
}
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition) super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
if self.shouldAnimateIn, let inputHeight = layout.inputHeight, inputHeight > 0.0 { if self.shouldAnimateIn, let inputHeight = layout.inputHeight, inputHeight > 0.0 {
if let (buttonFrame, animationSnapshot, textSnapshot) = self.transitionInArguments { if let (buttonFrame, buttonTitle, animationSnapshot, textSnapshot) = self.transitionInArguments {
self.shouldAnimateIn = false self.shouldAnimateIn = false
self.controllerNode.animateIn(buttonFrame: buttonFrame, animationSnapshot: animationSnapshot, textSnapshot: textSnapshot) self.controllerNode.animateIn(buttonFrame: buttonFrame, buttonTitle: buttonTitle, animationSnapshot: animationSnapshot, textSnapshot: textSnapshot)
} }
} }
} }
@ -217,14 +239,16 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
actions.append(TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})) actions.append(TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {}))
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_PhoneNumberAlreadyAuthorized, actions: actions), in: .window(.root)) self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_PhoneNumberAlreadyAuthorized, actions: actions), in: .window(.root))
} else { } else {
var actions: [TextAlertAction] = [] let (code, formattedNumber) = self.controllerNode.formattedCodeAndNumber
actions.append(TextAlertAction(type: .genericAction, title: self.presentationData.strings.Login_Edit, action: {}))
actions.append(TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Login_Yes, action: { [weak self] in let confirmationController = PhoneConfirmationController(theme: self.presentationData.theme, strings: self.presentationData.strings, code: code, number: formattedNumber, sourceController: self)
confirmationController.proceed = { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.loginWithNumber?(strongSelf.controllerNode.currentNumber, strongSelf.controllerNode.syncContacts) strongSelf.loginWithNumber?(strongSelf.controllerNode.currentNumber, strongSelf.controllerNode.syncContacts)
} }
})) }
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: logInNumber, text: self.presentationData.strings.Login_PhoneNumberConfirmation, actions: actions), in: .window(.root)) (self.navigationController as? NavigationController)?.presentOverlay(controller: confirmationController, inGlobal: true, blockInteraction: true)
self.confirmationController = confirmationController
} }
} else { } else {
self.hapticFeedback.error() self.hapticFeedback.error()

View File

@ -6,7 +6,6 @@ import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import PhoneInputNode import PhoneInputNode
import CountrySelectionUI import CountrySelectionUI
import AuthorizationUI
import QrCode import QrCode
import SwiftSignalKit import SwiftSignalKit
import Postbox import Postbox
@ -14,6 +13,8 @@ import AccountContext
import AnimatedStickerNode import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
import SolidRoundedButtonNode import SolidRoundedButtonNode
import AuthorizationUtils
import ManagedAnimationNode
private final class PhoneAndCountryNode: ASDisplayNode { private final class PhoneAndCountryNode: ASDisplayNode {
let strings: PresentationStrings let strings: PresentationStrings
@ -23,9 +24,13 @@ private final class PhoneAndCountryNode: ASDisplayNode {
var selectCountryCode: (() -> Void)? var selectCountryCode: (() -> Void)?
var checkPhone: (() -> Void)? var checkPhone: (() -> Void)?
var hasNumberUpdated: ((Bool) -> Void)?
var numberUpdated: (() -> Void)?
var preferredCountryIdForCode: [String: String] = [:] var preferredCountryIdForCode: [String: String] = [:]
var hasCountry = false
init(strings: PresentationStrings, theme: PresentationTheme) { init(strings: PresentationStrings, theme: PresentationTheme) {
self.strings = strings self.strings = strings
@ -122,6 +127,7 @@ private final class PhoneAndCountryNode: ASDisplayNode {
let flagString = emojiFlagForISOCountryCode(country.id) let flagString = emojiFlagForISOCountryCode(country.id)
let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(country.id, strings: strongSelf.strings) ?? country.name let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(country.id, strings: strongSelf.strings) ?? country.name
strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemAccentColor, for: []) strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemAccentColor, for: [])
strongSelf.hasCountry = true
let maskFont = Font.with(size: 20.0, design: .regular, traits: [.monospacedNumbers]) let maskFont = Font.with(size: 20.0, design: .regular, traits: [.monospacedNumbers])
if let mask = AuthorizationSequenceCountrySelectionController.lookupPatternByNumber(number, preferredCountries: strongSelf.preferredCountryIdForCode).flatMap({ NSAttributedString(string: $0, font: maskFont, textColor: theme.list.itemPlaceholderTextColor) }) { if let mask = AuthorizationSequenceCountrySelectionController.lookupPatternByNumber(number, preferredCountries: strongSelf.preferredCountryIdForCode).flatMap({ NSAttributedString(string: $0, font: maskFont, textColor: theme.list.itemPlaceholderTextColor) }) {
@ -140,6 +146,14 @@ private final class PhoneAndCountryNode: ASDisplayNode {
self.phoneInputNode.numberTextUpdated = { [weak self] number in self.phoneInputNode.numberTextUpdated = { [weak self] number in
if let strongSelf = self { if let strongSelf = self {
let _ = processNumberChange(strongSelf.phoneInputNode.number) let _ = processNumberChange(strongSelf.phoneInputNode.number)
strongSelf.numberUpdated?()
if strongSelf.hasCountry {
strongSelf.hasNumberUpdated?(!strongSelf.phoneInputNode.codeAndNumber.2.isEmpty)
} else {
strongSelf.hasNumberUpdated?(false)
}
} }
} }
@ -149,11 +163,14 @@ private final class PhoneAndCountryNode: ASDisplayNode {
strongSelf.preferredCountryIdForCode[code] = name strongSelf.preferredCountryIdForCode[code] = name
} }
strongSelf.numberUpdated?()
if processNumberChange(strongSelf.phoneInputNode.number) { if processNumberChange(strongSelf.phoneInputNode.number) {
} else if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] { } else if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] {
let flagString = emojiFlagForISOCountryCode(name) let flagString = emojiFlagForISOCountryCode(name)
let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(name, strings: strongSelf.strings) ?? countryName let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(name, strings: strongSelf.strings) ?? countryName
strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemAccentColor, for: []) strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemAccentColor, for: [])
strongSelf.hasCountry = true
if strongSelf.phoneInputNode.mask == nil { if strongSelf.phoneInputNode.mask == nil {
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor) strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor)
@ -162,15 +179,23 @@ private final class PhoneAndCountryNode: ASDisplayNode {
let flagString = emojiFlagForISOCountryCode(countryId) let flagString = emojiFlagForISOCountryCode(countryId)
let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(countryId, strings: strongSelf.strings) ?? countryName let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(countryId, strings: strongSelf.strings) ?? countryName
strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemAccentColor, for: []) strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemAccentColor, for: [])
strongSelf.hasCountry = true
if strongSelf.phoneInputNode.mask == nil { if strongSelf.phoneInputNode.mask == nil {
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor) strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor)
} }
} else { } else {
strongSelf.hasCountry = false
strongSelf.countryButton.setTitle(strings.Login_SelectCountry, with: Font.regular(20.0), with: theme.list.itemAccentColor, for: []) strongSelf.countryButton.setTitle(strings.Login_SelectCountry, with: Font.regular(20.0), with: theme.list.itemAccentColor, for: [])
strongSelf.phoneInputNode.mask = nil strongSelf.phoneInputNode.mask = nil
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor) strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor)
} }
if strongSelf.hasCountry {
strongSelf.hasNumberUpdated?(!strongSelf.phoneInputNode.codeAndNumber.2.isEmpty)
} else {
strongSelf.hasNumberUpdated?(false)
}
} }
} }
@ -253,6 +278,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
private let hasOtherAccounts: Bool private let hasOtherAccounts: Bool
private let animationNode: AnimatedStickerNode private let animationNode: AnimatedStickerNode
private let managedAnimationNode: ManagedPhoneAnimationNode
private let titleNode: ASTextNode private let titleNode: ASTextNode
private let noticeNode: ASTextNode private let noticeNode: ASTextNode
private let phoneAndCountryNode: PhoneAndCountryNode private let phoneAndCountryNode: PhoneAndCountryNode
@ -278,6 +304,10 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
} }
} }
var formattedCodeAndNumber: (String, String) {
return self.phoneAndCountryNode.phoneInputNode.formattedCodeAndNumber
}
var syncContacts: Bool { var syncContacts: Bool {
get { get {
if self.hasOtherAccounts { if self.hasOtherAccounts {
@ -307,6 +337,18 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
} }
} }
var codeNode: ASDisplayNode {
return self.phoneAndCountryNode.phoneInputNode.countryCodeField
}
var numberNode: ASDisplayNode {
return self.phoneAndCountryNode.phoneInputNode.numberField
}
var buttonNode: ASDisplayNode {
return self.proceedNode
}
init(sharedContext: SharedAccountContext, account: UnauthorizedAccount, strings: PresentationStrings, theme: PresentationTheme, debugAction: @escaping () -> Void, hasOtherAccounts: Bool) { init(sharedContext: SharedAccountContext, account: UnauthorizedAccount, strings: PresentationStrings, theme: PresentationTheme, debugAction: @escaping () -> Void, hasOtherAccounts: Bool) {
self.sharedContext = sharedContext self.sharedContext = sharedContext
self.account = account self.account = account
@ -320,6 +362,9 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "IntroPhone"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "IntroPhone"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animationNode.visibility = true self.animationNode.visibility = true
self.managedAnimationNode = ManagedPhoneAnimationNode()
self.managedAnimationNode.isHidden = true
self.titleNode = ASTextNode() self.titleNode = ASTextNode()
self.titleNode.isUserInteractionEnabled = true self.titleNode.isUserInteractionEnabled = true
self.titleNode.displaysAsynchronously = false self.titleNode.displaysAsynchronously = false
@ -338,6 +383,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0, gloss: false) self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0, gloss: false)
self.proceedNode.progressType = .embedded self.proceedNode.progressType = .embedded
self.proceedNode.isEnabled = false
super.init() super.init()
@ -353,6 +399,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.addSubnode(self.contactSyncNode) self.addSubnode(self.contactSyncNode)
self.addSubnode(self.proceedNode) self.addSubnode(self.proceedNode)
self.addSubnode(self.animationNode) self.addSubnode(self.animationNode)
self.addSubnode(self.managedAnimationNode)
self.contactSyncNode.isHidden = true self.contactSyncNode.isHidden = true
self.phoneAndCountryNode.selectCountryCode = { [weak self] in self.phoneAndCountryNode.selectCountryCode = { [weak self] in
@ -361,6 +408,16 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.phoneAndCountryNode.checkPhone = { [weak self] in self.phoneAndCountryNode.checkPhone = { [weak self] in
self?.checkPhone?() self?.checkPhone?()
} }
self.phoneAndCountryNode.hasNumberUpdated = { [weak self] hasNumber in
self?.proceedNode.isEnabled = hasNumber
}
self.phoneAndCountryNode.numberUpdated = { [weak self] in
if let strongSelf = self, !strongSelf.managedAnimationNode.isHidden {
if let state = ManagedPhoneAnimationState.allCases.randomElement() {
strongSelf.managedAnimationNode.enqueue(state)
}
}
}
self.tokenEventsDisposable.set((account.updateLoginTokenEvents self.tokenEventsDisposable.set((account.updateLoginTokenEvents
|> deliverOnMainQueue).start(next: { [weak self] _ in |> deliverOnMainQueue).start(next: { [weak self] _ in
@ -370,6 +427,11 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.proceedNode.pressed = { [weak self] in self.proceedNode.pressed = { [weak self] in
self?.checkPhone?() self?.checkPhone?()
} }
self.animationNode.completed = { [weak self] _ in
self?.animationNode.isHidden = true
self?.managedAnimationNode.isHidden = false
}
} }
deinit { deinit {
@ -390,9 +452,11 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
private var textSnapshotView: UIView? private var textSnapshotView: UIView?
private var forcedButtonFrame: CGRect? private var forcedButtonFrame: CGRect?
func willAnimateIn(buttonFrame: CGRect, animationSnapshot: UIView, textSnapshot: UIView) { func willAnimateIn(buttonFrame: CGRect, buttonTitle: String, animationSnapshot: UIView, textSnapshot: UIView) {
self.proceedNode.frame = buttonFrame self.proceedNode.frame = buttonFrame
self.proceedNode.title = "Start Messaging"
self.proceedNode.isEnabled = true
self.proceedNode.title = buttonTitle
self.animationSnapshotView = animationSnapshot self.animationSnapshotView = animationSnapshot
self.view.insertSubview(animationSnapshot, at: 0) self.view.insertSubview(animationSnapshot, at: 0)
@ -413,7 +477,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
} }
} }
func animateIn(buttonFrame: CGRect, animationSnapshot: UIView, textSnapshot: UIView) { func animateIn(buttonFrame: CGRect, buttonTitle: String, animationSnapshot: UIView, textSnapshot: UIView) {
self.proceedNode.animateTitle(to: self.strings.Login_Continue) self.proceedNode.animateTitle(to: self.strings.Login_Continue)
let duration: Double = 0.3 let duration: Double = 0.3
@ -423,6 +487,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self?.animationSnapshotView = nil self?.animationSnapshotView = nil
}) })
self.animationSnapshotView?.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -100.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) self.animationSnapshotView?.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -100.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
self.animationSnapshotView?.layer.animateScale(from: 1.0, to: 0.3, duration: 0.4)
self.textSnapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in self.textSnapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in
self?.textSnapshotView?.removeFromSuperview() self?.textSnapshotView?.removeFromSuperview()
@ -438,6 +503,8 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.contactSyncNode self.contactSyncNode
] ]
self.animationNode.layer.animateScale(from: 0.3, to: 1.0, duration: 0.3)
for node in nodes { for node in nodes {
node.alpha = 1.0 node.alpha = 1.0
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) node.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
@ -492,6 +559,8 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.animationNode.updateLayout(size: animationSize) self.animationNode.updateLayout(size: animationSize)
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 - 80.0)), items: items, transition: transition, failIfDoesNotFit: false) 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 - 80.0)), items: items, transition: transition, failIfDoesNotFit: false)
transition.updateFrame(node: self.managedAnimationNode, frame: self.animationNode.frame)
} }
func activateInput() { func activateInput() {
@ -601,3 +670,366 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
})) }))
} }
} }
final class PhoneConfirmationController: ViewController {
private var controllerNode: Node {
return self.displayNode as! Node
}
private let theme: PresentationTheme
private let strings: PresentationStrings
private let code: String
private let number: String
private weak var sourceController: AuthorizationSequencePhoneEntryController?
var inProgress: Bool = false {
didSet {
if self.inProgress != oldValue {
if self.inProgress {
self.controllerNode.proceedNode.transitionToProgress()
} else {
self.controllerNode.proceedNode.transitionFromProgress()
}
}
}
}
var proceed: () -> Void = {}
class Node: ASDisplayNode {
private let dimNode: ASDisplayNode
private let backgroundNode: ASDisplayNode
private let codeSourceNode: ImmediateTextNode
private let phoneSourceNode: ImmediateTextNode
private let codeTargetNode: ImmediateTextNode
private let phoneTargetNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let cancelButton: HighlightableButtonNode
fileprivate let proceedNode: SolidRoundedButtonNode
var proceed: () -> Void = {}
var cancel: () -> Void = {}
init(theme: PresentationTheme, strings: PresentationStrings, code: String, number: String) {
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = theme.list.plainBackgroundColor
self.backgroundNode.cornerRadius = 11.0
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.attributedText = NSAttributedString(string: strings.Login_PhoneNumberConfirmation, font: Font.regular(17.0), textColor: theme.list.itemPrimaryTextColor)
self.textNode.textAlignment = .center
self.cancelButton = HighlightableButtonNode()
self.cancelButton.setTitle(strings.Login_Edit, with: Font.regular(19.0), with: theme.list.itemAccentColor, for: .normal)
self.proceedNode = SolidRoundedButtonNode(title: strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 11.0, gloss: false)
self.proceedNode.progressType = .embedded
let font = Font.with(size: 20.0, design: .regular, traits: [.monospacedNumbers])
let largeFont = Font.with(size: 34.0, design: .regular, weight: .bold, traits: [.monospacedNumbers])
self.codeSourceNode = ImmediateTextNode()
self.codeSourceNode.alpha = 0.0
self.codeSourceNode.displaysAsynchronously = false
self.codeSourceNode.attributedText = NSAttributedString(string: code, font: font, textColor: theme.list.itemPrimaryTextColor)
self.phoneSourceNode = ImmediateTextNode()
self.phoneSourceNode.alpha = 0.0
self.phoneSourceNode.displaysAsynchronously = false
let sourceString = NSMutableAttributedString(string: number, font: font, textColor: theme.list.itemPrimaryTextColor)
sourceString.addAttribute(NSAttributedString.Key.kern, value: 1.6, range: NSRange(location: 0, length: sourceString.length))
self.phoneSourceNode.attributedText = sourceString
self.codeTargetNode = ImmediateTextNode()
self.codeTargetNode.displaysAsynchronously = false
self.codeTargetNode.attributedText = NSAttributedString(string: code, font: largeFont, textColor: theme.list.itemPrimaryTextColor)
self.phoneTargetNode = ImmediateTextNode()
self.phoneTargetNode.displaysAsynchronously = false
let targetString = NSMutableAttributedString(string: number, font: largeFont, textColor: theme.list.itemPrimaryTextColor)
targetString.addAttribute(NSAttributedString.Key.kern, value: 1.6, range: NSRange(location: 0, length: sourceString.length))
self.phoneTargetNode.attributedText = targetString
super.init()
self.clipsToBounds = false
self.addSubnode(self.dimNode)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.codeSourceNode)
self.addSubnode(self.phoneSourceNode)
self.addSubnode(self.codeTargetNode)
self.addSubnode(self.phoneTargetNode)
self.addSubnode(self.textNode)
self.addSubnode(self.cancelButton)
self.addSubnode(self.proceedNode)
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
self.proceedNode.pressed = { [weak self] in
self?.proceed()
}
}
override func didLoad() {
super.didLoad()
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapped)))
}
@objc private func dimTapped() {
self.cancelPressed()
}
@objc private func cancelPressed() {
self.dimNode.isUserInteractionEnabled = false
self.cancel()
}
func animateIn(codeNode: ASDisplayNode, numberNode: ASDisplayNode, buttonNode: ASDisplayNode) {
let codeFrame = codeNode.convert(codeNode.bounds, to: nil)
let numberFrame = numberNode.convert(numberNode.bounds, to: nil)
let buttonFrame = buttonNode.convert(buttonNode.bounds, to: nil)
codeNode.isHidden = true
numberNode.isHidden = true
buttonNode.isHidden = true
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
let codeSize = self.codeSourceNode.updateLayout(self.frame.size)
self.codeSourceNode.frame = CGRect(origin: CGPoint(x: codeFrame.midX - codeSize.width / 2.0, y: codeFrame.midY - codeSize.height / 2.0), size: codeSize)
let numberSize = self.phoneSourceNode.updateLayout(self.frame.size)
self.phoneSourceNode.frame = CGRect(origin: CGPoint(x: numberFrame.minX, y: numberFrame.midY - numberSize.height / 2.0), size: numberSize)
let targetScale = codeSize.height / self.codeTargetNode.frame.height
let sourceScale = self.codeTargetNode.frame.height / codeSize.height
self.codeSourceNode.layer.animateScale(from: 1.0, to: sourceScale, duration: 0.3)
self.codeSourceNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
self.codeSourceNode.layer.animatePosition(from: self.codeSourceNode.position, to: self.codeTargetNode.position, duration: 0.3)
self.phoneSourceNode.layer.animateScale(from: 1.0, to: sourceScale, duration: 0.3)
self.phoneSourceNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
self.phoneSourceNode.layer.animatePosition(from: self.phoneSourceNode.position, to: self.phoneTargetNode.position, duration: 0.3)
self.codeTargetNode.layer.animateScale(from: targetScale, to: 1.0, duration: 0.3)
self.codeTargetNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.codeTargetNode.layer.animatePosition(from: self.codeSourceNode.position, to: self.codeTargetNode.position, duration: 0.3)
self.phoneTargetNode.layer.animateScale(from: targetScale, to: 1.0, duration: 0.3)
self.phoneTargetNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.phoneTargetNode.layer.animatePosition(from: self.phoneSourceNode.position, to: self.phoneTargetNode.position, duration: 0.3)
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
self.backgroundNode.layer.animateFrame(from: CGRect(origin: CGPoint(x: 14.0, y: codeFrame.minY), size: CGSize(width: self.backgroundNode.frame.width - 12.0, height: buttonFrame.maxY + 18.0 - codeFrame.minY)), to: self.backgroundNode.frame, duration: 0.3)
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.textNode.layer.animateScale(from: 0.5, to: 1.0, duration: 0.3)
self.textNode.layer.animatePosition(from: CGPoint(x: -100.0, y: -45.0), to: CGPoint(), duration: 0.3, additive: true)
self.cancelButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.cancelButton.layer.animateScale(from: 0.5, to: 1.0, duration: 0.3)
self.cancelButton.layer.animatePosition(from: CGPoint(x: -100.0, y: -70.0), to: CGPoint(), duration: 0.3, additive: true)
self.proceedNode.layer.animatePosition(from: buttonFrame.center, to: self.proceedNode.position, duration: 0.3)
}
func animateOut(codeNode: ASDisplayNode, numberNode: ASDisplayNode, buttonNode: ASDisplayNode, completion: @escaping () -> Void) {
let codeFrame = codeNode.convert(codeNode.bounds, to: nil)
let numberFrame = numberNode.convert(numberNode.bounds, to: nil)
let buttonFrame = buttonNode.convert(buttonNode.bounds, to: nil)
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
let codeSize = self.codeSourceNode.updateLayout(self.frame.size)
self.codeSourceNode.frame = CGRect(origin: CGPoint(x: codeFrame.midX - codeSize.width / 2.0, y: codeFrame.midY - codeSize.height / 2.0), size: codeSize)
let numberSize = self.phoneSourceNode.updateLayout(self.frame.size)
self.phoneSourceNode.frame = CGRect(origin: CGPoint(x: numberFrame.minX, y: numberFrame.midY - numberSize.height / 2.0), size: numberSize)
let targetScale = codeSize.height / self.codeTargetNode.frame.height
let sourceScale = self.codeTargetNode.frame.height / codeSize.height
self.codeSourceNode.layer.animateScale(from: sourceScale, to: 1.0, duration: 0.3)
self.codeSourceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.codeSourceNode.layer.animatePosition(from: self.codeTargetNode.position, to: self.codeSourceNode.position, duration: 0.3)
self.phoneSourceNode.layer.animateScale(from: sourceScale, to: 1.0, duration: 0.3)
self.phoneSourceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.phoneSourceNode.layer.animatePosition(from: self.phoneTargetNode.position, to: self.phoneSourceNode.position, duration: 0.3)
self.codeTargetNode.layer.animateScale(from: 1.0, to: targetScale, duration: 0.3)
self.codeTargetNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
self.codeTargetNode.layer.animatePosition(from: self.codeTargetNode.position, to: self.codeSourceNode.position, duration: 0.3)
Queue.mainQueue().after(0.25) {
codeNode.isHidden = false
numberNode.isHidden = false
buttonNode.isHidden = false
}
self.phoneTargetNode.layer.animateScale(from: 1.0, to: targetScale, duration: 0.3)
self.phoneTargetNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
completion()
})
self.phoneTargetNode.layer.animatePosition(from: self.phoneTargetNode.position, to: self.phoneSourceNode.position, duration: 0.3)
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.1, removeOnCompletion: false)
self.backgroundNode.layer.animateFrame(from: self.backgroundNode.frame, to: CGRect(origin: CGPoint(x: 14.0, y: codeFrame.minY), size: CGSize(width: self.backgroundNode.frame.width - 12.0, height: buttonFrame.maxY + 18.0 - codeFrame.minY)), duration: 0.3)
self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.textNode.layer.animateScale(from: 1.0, to: 0.5, duration: 0.3, removeOnCompletion: false)
self.textNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -100.0, y: -45.0), duration: 0.3, removeOnCompletion: false, additive: true)
self.cancelButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.cancelButton.layer.animateScale(from: 1.0, to: 0.5, duration: 0.3, removeOnCompletion: false)
self.cancelButton.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -100.0, y: -70.0), duration: 0.3, removeOnCompletion: false, additive: true)
self.proceedNode.layer.animatePosition(from: self.proceedNode.position, to: buttonFrame.center, duration: 0.3, removeOnCompletion: false)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
let sideInset: CGFloat = 8.0
let innerInset: CGFloat = 18.0
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: -layout.size.width, y: 0.0), size: CGSize(width: layout.size.width * 3.0, height: layout.size.height)))
let backgroundSize = CGSize(width: layout.size.width - sideInset * 2.0, height: 243.0)
let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - backgroundSize.width) / 2.0), y: layout.size.height - backgroundSize.height - 260.0), size: backgroundSize)
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
let codeSize = self.codeTargetNode.updateLayout(backgroundSize)
let numberSize = self.phoneTargetNode.updateLayout(backgroundSize)
let totalWidth = codeSize.width + numberSize.width + 10.0
let codeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - totalWidth) / 2.0), y: 30.0), size: codeSize)
transition.updateFrame(node: self.codeTargetNode, frame: codeFrame.offsetBy(dx: backgroundFrame.minX, dy: backgroundFrame.minY))
let numberFrame = CGRect(origin: CGPoint(x: codeFrame.maxX + 10.0, y: 30.0), size: numberSize)
transition.updateFrame(node: self.phoneTargetNode, frame: numberFrame.offsetBy(dx: backgroundFrame.minX, dy: backgroundFrame.minY))
let textSize = self.textNode.updateLayout(backgroundSize)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - textSize.width) / 2.0), y: 88.0), size: textSize).offsetBy(dx: backgroundFrame.minX, dy: backgroundFrame.minY))
let proceedWidth = backgroundSize.width - 16.0 * 2.0
let proceedHeight = self.proceedNode.updateLayout(width: proceedWidth, transition: transition)
transition.updateFrame(node: self.proceedNode, frame: CGRect(origin: CGPoint(x: innerInset, y: backgroundSize.height - proceedHeight - innerInset), size: CGSize(width: proceedWidth, height: proceedHeight)).offsetBy(dx: backgroundFrame.minX, dy: backgroundFrame.minY))
let cancelSize = self.cancelButton.measure(layout.size)
transition.updateFrame(node: self.cancelButton, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - cancelSize.width) / 2.0), y: backgroundSize.height - proceedHeight - innerInset - cancelSize.height - 25.0), size: cancelSize).offsetBy(dx: backgroundFrame.minX, dy: backgroundFrame.minY))
}
}
public init(theme: PresentationTheme, strings: PresentationStrings, code: String, number: String, sourceController: AuthorizationSequencePhoneEntryController) {
self.theme = theme
self.strings = strings
self.code = code
self.number = number
self.sourceController = sourceController
super.init(navigationBarPresentationData: nil)
self.blocksBackgroundWhenInOverlay = true
self.statusBar.statusBarStyle = .Ignore
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var isDismissed = false
override public func loadDisplayNode() {
self.displayNode = Node(theme: self.theme, strings: self.strings, code: self.code, number: self.number)
self.displayNodeDidLoad()
self.controllerNode.proceed = { [weak self] in
self?.proceed()
}
self.controllerNode.cancel = { [weak self] in
if let strongSelf = self, let sourceController = strongSelf.sourceController {
strongSelf.controllerNode.animateOut(codeNode: sourceController.codeNode, numberNode: sourceController.numberNode, buttonNode: sourceController.buttonNode, completion: { [weak self] in
self?.dismiss()
})
}
}
}
func transitionOut() {
self.controllerNode.cancel()
let transition = ContainedViewLayoutTransition.animated(duration: 0.5, curve: .spring)
transition.updatePosition(layer: self.view.layer, position: CGPoint(x: self.view.center.x - self.view.frame.width, y: self.view.center.y))
}
private var didPlayAppearanceAnimation = false
override public func viewDidAppear(_ animated: Bool) {
if !self.didPlayAppearanceAnimation {
self.didPlayAppearanceAnimation = true
if let sourceController = self.sourceController {
self.controllerNode.animateIn(codeNode: sourceController.codeNode, numberNode: sourceController.numberNode, buttonNode: sourceController.buttonNode)
}
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, transition: transition)
}
}
private enum ManagedPhoneAnimationState: CaseIterable, Equatable {
case keypad1
case keypad2
case keypad3
case keypad4
}
private final class ManagedPhoneAnimationNode: ManagedAnimationNode {
private var phoneState: ManagedPhoneAnimationState = .keypad1
private var timer: SwiftSignalKit.Timer?
init() {
super.init(size: CGSize(width: 100.0, height: 100.0))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.3))
}
func enqueue(_ state: ManagedPhoneAnimationState) {
switch state {
case .keypad1:
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 10), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 10, endFrame: 0), duration: 0.15))
case .keypad2:
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 24, endFrame: 34), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 34, endFrame: 24), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.1))
case .keypad3:
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 36, endFrame: 46), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 46, endFrame: 36), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.1))
case .keypad4:
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 10), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 10, endFrame: 0), duration: 0.15))
}
}
}

View File

@ -5,8 +5,8 @@ import Display
import TelegramPresentationData import TelegramPresentationData
import TextFormat import TextFormat
import Markdown import Markdown
import AuthorizationUI
import SolidRoundedButtonNode import SolidRoundedButtonNode
import AuthorizationUtils
private func roundCorners(diameter: CGFloat) -> UIImage { private func roundCorners(diameter: CGFloat) -> UIImage {
UIGraphicsBeginImageContextWithOptions(CGSize(width: diameter, height: diameter), false, 0.0) UIGraphicsBeginImageContextWithOptions(CGSize(width: diameter, height: diameter), false, 0.0)

View File

@ -116,6 +116,10 @@ final class AuthorizationSequenceSplashController: ViewController {
return self.startButton.frame return self.startButton.frame
} }
var buttonTitle: String {
return self.startButton.title ?? ""
}
var animationSnapshot: UIView? { var animationSnapshot: UIView? {
return self.controller.createAnimationSnapshot() return self.controller.createAnimationSnapshot()
} }

View File

@ -0,0 +1,23 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AuthorizationUtils",
module_name = "AuthorizationUtils",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/TelegramCore:TelegramCore",
"//submodules/Display:Display",
"//submodules/TextFormat:TextFormat",
"//submodules/Markdown:Markdown",
"//submodules/TelegramPresentationData:TelegramPresentationData",
],
visibility = [
"//visibility:public",
],
)

View File

@ -29,10 +29,17 @@ public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, ph
return NSAttributedString(string: "", font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center) return NSAttributedString(string: "", font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center)
case let .email(emailPattern, _, _, _, _): case let .email(emailPattern, _, _, _, _):
let mutableString = NSAttributedString(string: strings.Login_EnterCodeEmailText(email ?? emailPattern).string, font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center).mutableCopy() as! NSMutableAttributedString let mutableString = NSAttributedString(string: strings.Login_EnterCodeEmailText(email ?? emailPattern).string, font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center).mutableCopy() as! NSMutableAttributedString
let range = (mutableString.string as NSString).range(of: "*******")
if range.location != NSNotFound { let string = mutableString.string
mutableString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true, range: range) let nsString = string as NSString
if let regex = try? NSRegularExpression(pattern: "\\*", options: []) {
let matches = regex.matches(in: string, options: [], range: NSMakeRange(0, nsString.length))
if let first = matches.first {
mutableString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true, range: NSRange(location: first.range.location, length: matches.count))
} }
}
return mutableString return mutableString
} }
} }

View File

@ -1696,7 +1696,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, openPeer: { _, _ in }, openPeer: { _, _ in
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, gallerySource: .custom(messages: foundMessages |> map { message, a, b in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, gallerySource: .custom(messages: foundMessages |> map { message, a, b in
return (message.map { $0._asMessage() }, a, b) return (message.map { $0._asMessage() }, a, b)
}, messageId: message.id, loadMore: { }, messageId: message.id, loadMore: {
loadMore() loadMore()
@ -1815,7 +1815,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, openPeer: { peer, navigation in }, openPeer: { peer, navigation in
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation, gallerySource: gallerySource)) }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation, gallerySource: gallerySource))
}, openMessageContextMenu: { [weak self] message, _, node, rect, gesture in }, openMessageContextMenu: { [weak self] message, _, node, rect, gesture in
guard let strongSelf = self, let currentEntries = strongSelf.currentEntries else { guard let strongSelf = self, let currentEntries = strongSelf.currentEntries else {
return return

View File

@ -215,7 +215,7 @@ open class NavigationController: UINavigationController, ContainableController,
return self._displayNode! return self._displayNode!
} }
var statusBarHost: StatusBarHost? { public var statusBarHost: StatusBarHost? {
didSet { didSet {
} }
} }

View File

@ -70,6 +70,11 @@ public final class InAppPurchaseManager: NSObject {
return self.numberFormatter.string(from: price) ?? "" return self.numberFormatter.string(from: price) ?? ""
} }
public func defaultPrice(_ value: NSDecimalNumber, monthsCount: Int) -> String {
let price = value.multiplying(by: NSDecimalNumber(value: monthsCount)).round(2)
return self.numberFormatter.string(from: price) ?? ""
}
public var priceValue: NSDecimalNumber { public var priceValue: NSDecimalNumber {
return self.skProduct.price return self.skProduct.price
} }

View File

@ -124,7 +124,7 @@ public func parseMarkdownIntoAttributedString(_ string: String, attributes: Mark
if let bold = parseBold(string: nsString, remainingRange: &remainingRange) { if let bold = parseBold(string: nsString, remainingRange: &remainingRange) {
var boldAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: attributes.bold.font, NSAttributedString.Key.foregroundColor: attributes.bold.textColor, NSAttributedString.Key.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] var boldAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: attributes.bold.font, NSAttributedString.Key.foregroundColor: attributes.bold.textColor, NSAttributedString.Key.paragraphStyle: paragraphStyleWithAlignment(textAlignment)]
if !attributes.body.additionalAttributes.isEmpty { if !attributes.bold.additionalAttributes.isEmpty {
for (key, value) in attributes.bold.additionalAttributes { for (key, value) in attributes.bold.additionalAttributes {
boldAttributes[NSAttributedString.Key(rawValue: key)] = value boldAttributes[NSAttributedString.Key(rawValue: key)] = value
} }

View File

@ -132,6 +132,10 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
private var countryNameForCode: (Int32, String)? private var countryNameForCode: (Int32, String)?
public var formattedCodeAndNumber: (String, String) {
return (self.countryCodeField.textField.text ?? "", self.numberField.textField.text ?? "")
}
public var codeAndNumber: (Int32?, String?, String) { public var codeAndNumber: (Int32?, String?, String) {
get { get {
var code: Int32? var code: Int32?

View File

@ -58,41 +58,25 @@ class EmojiHeaderComponent: Component {
return false return false
} }
private var _ready = Promise<Bool>() private var _ready = Promise<Bool>(true)
var ready: Signal<Bool, NoError> { var ready: Signal<Bool, NoError> {
return self._ready.get() return self._ready.get()
} }
weak var animateFrom: UIView? weak var animateFrom: UIView?
weak var containerView: UIView? weak var containerView: UIView?
var animationColor: UIColor?
private let sceneView: SCNView
let statusView: ComponentHostView<Empty> let statusView: ComponentHostView<Empty>
private var previousInteractionTimestamp: Double = 0.0
private var timer: SwiftSignalKit.Timer?
private var hasIdleAnimations = false private var hasIdleAnimations = false
override init(frame: CGRect) { override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: CGSize(width: 64.0, height: 64.0)))
self.sceneView.backgroundColor = .clear
self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60
self.statusView = ComponentHostView<Empty>() self.statusView = ComponentHostView<Empty>()
super.init(frame: frame) super.init(frame: frame)
self.addSubview(self.sceneView)
self.addSubview(self.statusView) self.addSubview(self.statusView)
self.setup()
let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
self.addGestureRecognizer(tapGestureRecoginzer)
self.disablesInteractiveModalDismiss = true self.disablesInteractiveModalDismiss = true
self.disablesInteractiveTransitionGestureRecognizer = true self.disablesInteractiveTransitionGestureRecognizer = true
} }
@ -101,41 +85,7 @@ class EmojiHeaderComponent: Component {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
deinit { func animateIn() {
self.timer?.invalidate()
}
private let hapticFeedback = HapticFeedback()
private var delayTapsTill: Double?
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
self.playAppearanceAnimation(velocity: nil, mirror: false, explode: true)
}
private func setup() {
guard let url = getAppBundle().url(forResource: "gift", withExtension: "scn"), let scene = try? SCNScene(url: url, options: nil) else {
return
}
self.sceneView.scene = scene
self.sceneView.delegate = self
let _ = self.sceneView.snapshot()
}
private var didSetReady = false
func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) {
if !self.didSetReady {
self.didSetReady = true
Queue.mainQueue().justDispatch {
self._ready.set(.single(true))
self.onReady()
}
}
}
private func maybeAnimateIn() {
guard let animateFrom = self.animateFrom, var containerView = self.containerView else { guard let animateFrom = self.animateFrom, var containerView = self.containerView else {
return return
} }
@ -150,8 +100,8 @@ class EmojiHeaderComponent: Component {
self.statusView.center = targetPosition self.statusView.center = targetPosition
animateFrom.alpha = 0.0 animateFrom.alpha = 0.0
self.statusView.layer.animateScale(from: 0.05, to: 1.0, duration: 0.8, timingFunction: kCAMediaTimingFunctionSpring) self.statusView.layer.animateScale(from: 0.05, to: 1.0, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring)
self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.8, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
self.addSubview(self.statusView) self.addSubview(self.statusView)
self.statusView.center = initialPosition self.statusView.center = initialPosition
}) })
@ -164,129 +114,7 @@ class EmojiHeaderComponent: Component {
self.containerView = nil self.containerView = nil
} }
private func onReady() {
self.setupScaleAnimation()
self.maybeAnimateIn()
self.playAppearanceAnimation(explode: true)
self.previousInteractionTimestamp = CACurrentMediaTime()
self.timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
if let strongSelf = self, strongSelf.hasIdleAnimations {
let currentTimestamp = CACurrentMediaTime()
if currentTimestamp > strongSelf.previousInteractionTimestamp + 5.0 {
strongSelf.playAppearanceAnimation()
}
}
}, queue: Queue.mainQueue())
self.timer?.start()
}
private func setupScaleAnimation() {
// let animation = CABasicAnimation(keyPath: "transform.scale")
// animation.duration = 2.0
// animation.fromValue = 1.0
// animation.toValue = 1.15
// animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
// animation.autoreverses = true
// animation.repeatCount = .infinity
//
// self.avatarNode.view.layer.add(animation, forKey: "scale")
}
private func playAppearanceAnimation(velocity: CGFloat? = nil, smallAngle: Bool = false, mirror: Bool = false, explode: Bool = false) {
guard let scene = self.sceneView.scene else {
return
}
let currentTime = CACurrentMediaTime()
self.previousInteractionTimestamp = currentTime
self.delayTapsTill = currentTime + 0.85
if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particlesLeft = scene.rootNode.childNode(withName: "particles_left", recursively: false), let particlesRight = scene.rootNode.childNode(withName: "particles_right", recursively: false), let particlesBottomLeft = scene.rootNode.childNode(withName: "particles_left_bottom", recursively: false), let particlesBottomRight = scene.rootNode.childNode(withName: "particles_right_bottom", recursively: false) {
if let leftParticleSystem = particlesLeft.particleSystems?.first, let rightParticleSystem = particlesRight.particleSystems?.first, let leftBottomParticleSystem = particlesBottomLeft.particleSystems?.first, let rightBottomParticleSystem = particlesBottomRight.particleSystems?.first {
leftParticleSystem.speedFactor = 2.0
leftParticleSystem.particleVelocity = 1.6
leftParticleSystem.birthRate = 60.0
leftParticleSystem.particleLifeSpan = 4.0
rightParticleSystem.speedFactor = 2.0
rightParticleSystem.particleVelocity = 1.6
rightParticleSystem.birthRate = 60.0
rightParticleSystem.particleLifeSpan = 4.0
// leftBottomParticleSystem.speedFactor = 2.0
leftBottomParticleSystem.particleVelocity = 1.6
leftBottomParticleSystem.birthRate = 24.0
leftBottomParticleSystem.particleLifeSpan = 7.0
// rightBottomParticleSystem.speedFactor = 2.0
rightBottomParticleSystem.particleVelocity = 1.6
rightBottomParticleSystem.birthRate = 24.0
rightBottomParticleSystem.particleLifeSpan = 7.0
node.physicsField?.isActive = true
Queue.mainQueue().after(1.0) {
node.physicsField?.isActive = false
leftParticleSystem.birthRate = 12.0
leftParticleSystem.particleVelocity = 1.2
leftParticleSystem.particleLifeSpan = 3.0
rightParticleSystem.birthRate = 12.0
rightParticleSystem.particleVelocity = 1.2
rightParticleSystem.particleLifeSpan = 3.0
leftBottomParticleSystem.particleVelocity = 1.2
leftBottomParticleSystem.birthRate = 7.0
leftBottomParticleSystem.particleLifeSpan = 5.0
rightBottomParticleSystem.particleVelocity = 1.2
rightBottomParticleSystem.birthRate = 7.0
rightBottomParticleSystem.particleLifeSpan = 5.0
let leftAnimation = POPBasicAnimation()
leftAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in
property?.readBlock = { particleSystem, values in
values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor
}
property?.writeBlock = { particleSystem, values in
(particleSystem as! SCNParticleSystem).speedFactor = values!.pointee
}
property?.threshold = 0.01
}) as! POPAnimatableProperty)
leftAnimation.fromValue = 1.2 as NSNumber
leftAnimation.toValue = 0.85 as NSNumber
leftAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
leftAnimation.duration = 0.5
leftParticleSystem.pop_add(leftAnimation, forKey: "speedFactor")
let rightAnimation = POPBasicAnimation()
rightAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in
property?.readBlock = { particleSystem, values in
values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor
}
property?.writeBlock = { particleSystem, values in
(particleSystem as! SCNParticleSystem).speedFactor = values!.pointee
}
property?.threshold = 0.01
}) as! POPAnimatableProperty)
rightAnimation.fromValue = 1.2 as NSNumber
rightAnimation.toValue = 0.85 as NSNumber
rightAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
rightAnimation.duration = 0.5
rightParticleSystem.pop_add(rightAnimation, forKey: "speedFactor")
}
}
}
}
func update(component: EmojiHeaderComponent, availableSize: CGSize, transition: Transition) -> CGSize { func update(component: EmojiHeaderComponent, availableSize: CGSize, transition: Transition) -> CGSize {
self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0))
if self.sceneView.superview == self {
self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)
}
self.hasIdleAnimations = component.hasIdleAnimations self.hasIdleAnimations = component.hasIdleAnimations
let size = self.statusView.update( let size = self.statusView.update(

View File

@ -257,13 +257,16 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
discount = "" discount = ""
} }
let pricePerMonth = product.storeProduct.pricePerMonth(Int(product.months))
items.append(SectionGroupComponent.Item( items.append(SectionGroupComponent.Item(
AnyComponentWithIdentity( AnyComponentWithIdentity(
id: product.id, id: product.id,
component: AnyComponent( component: AnyComponent(
PremiumOptionComponent( PremiumOptionComponent(
title: giftTitle, title: giftTitle,
totalPrice: product.price, subtitle: product.price,
labelPrice: pricePerMonth,
discount: discount, discount: discount,
selected: product.id == component.selectedProductId, selected: product.id == component.selectedProductId,
primaryTextColor: textColor, primaryTextColor: textColor,

View File

@ -307,7 +307,8 @@ struct PremiumIntroConfiguration {
final class PremiumOptionComponent: CombinedComponent { final class PremiumOptionComponent: CombinedComponent {
let title: String let title: String
let totalPrice: String let subtitle: String
let labelPrice: String
let discount: String let discount: String
let selected: Bool let selected: Bool
let primaryTextColor: UIColor let primaryTextColor: UIColor
@ -318,7 +319,8 @@ final class PremiumOptionComponent: CombinedComponent {
init( init(
title: String, title: String,
totalPrice: String, subtitle: String,
labelPrice: String,
discount: String, discount: String,
selected: Bool, selected: Bool,
primaryTextColor: UIColor, primaryTextColor: UIColor,
@ -328,7 +330,8 @@ final class PremiumOptionComponent: CombinedComponent {
checkBorderColor: UIColor checkBorderColor: UIColor
) { ) {
self.title = title self.title = title
self.totalPrice = totalPrice self.subtitle = subtitle
self.labelPrice = labelPrice
self.discount = discount self.discount = discount
self.selected = selected self.selected = selected
self.primaryTextColor = primaryTextColor self.primaryTextColor = primaryTextColor
@ -342,7 +345,10 @@ final class PremiumOptionComponent: CombinedComponent {
if lhs.title != rhs.title { if lhs.title != rhs.title {
return false return false
} }
if lhs.totalPrice != rhs.totalPrice { if lhs.subtitle != rhs.subtitle {
return false
}
if lhs.labelPrice != rhs.labelPrice {
return false return false
} }
if lhs.discount != rhs.discount { if lhs.discount != rhs.discount {
@ -372,6 +378,7 @@ final class PremiumOptionComponent: CombinedComponent {
static var body: Body { static var body: Body {
let check = Child(CheckComponent.self) let check = Child(CheckComponent.self)
let title = Child(MultilineTextComponent.self) let title = Child(MultilineTextComponent.self)
let subtitle = Child(MultilineTextComponent.self)
let discountBackground = Child(RoundedRectangle.self) let discountBackground = Child(RoundedRectangle.self)
let discount = Child(MultilineTextComponent.self) let discount = Child(MultilineTextComponent.self)
let label = Child(MultilineTextComponent.self) let label = Child(MultilineTextComponent.self)
@ -379,13 +386,13 @@ final class PremiumOptionComponent: CombinedComponent {
return { context in return { context in
let component = context.component let component = context.component
let insets = UIEdgeInsets(top: 11.0, left: 46.0, bottom: 13.0, right: 16.0) var insets = UIEdgeInsets(top: 11.0, left: 46.0, bottom: 13.0, right: 16.0)
let label = label.update( let label = label.update(
component: MultilineTextComponent( component: MultilineTextComponent(
text: .plain( text: .plain(
NSAttributedString( NSAttributedString(
string: component.totalPrice, string: component.labelPrice,
font: Font.regular(17), font: Font.regular(17),
textColor: component.secondaryTextColor textColor: component.secondaryTextColor
) )
@ -411,6 +418,41 @@ final class PremiumOptionComponent: CombinedComponent {
transition: context.transition transition: context.transition
) )
var spacing: CGFloat = 0.0
var subtitleHeight: CGFloat = 0.0
if !component.subtitle.isEmpty {
spacing = 2.0
let subtitleFont = Font.regular(13)
let subtitleColor = component.secondaryTextColor
let subtitleString = parseMarkdownIntoAttributedString(
component.subtitle,
attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: subtitleFont, textColor: subtitleColor),
bold: MarkdownAttributeSet(font: subtitleFont, textColor: subtitleColor, additionalAttributes: [NSAttributedString.Key.strikethroughStyle.rawValue: NSUnderlineStyle.single.rawValue as NSNumber]),
link: MarkdownAttributeSet(font: subtitleFont, textColor: subtitleColor),
linkAttribute: { _ in return nil }
)
)
let subtitle = subtitle.update(
component: MultilineTextComponent(
text: .plain(subtitleString),
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - insets.left - insets.right - label.size.width, height: context.availableSize.height),
transition: context.transition
)
context.add(subtitle
.position(CGPoint(x: insets.left + subtitle.size.width / 2.0, y: insets.top + title.size.height + spacing + subtitle.size.height / 2.0))
)
subtitleHeight = subtitle.size.height
insets.top -= 2.0
insets.bottom -= 2.0
}
let discountSize: CGSize let discountSize: CGSize
if !component.discount.isEmpty { if !component.discount.isEmpty {
let discount = discount.update( let discount = discount.update(
@ -470,7 +512,7 @@ final class PremiumOptionComponent: CombinedComponent {
.position(CGPoint(x: insets.left + title.size.width / 2.0, y: insets.top + title.size.height / 2.0)) .position(CGPoint(x: insets.left + title.size.width / 2.0, y: insets.top + title.size.height / 2.0))
) )
let size = CGSize(width: context.availableSize.width, height: insets.top + title.size.height + insets.bottom) let size = CGSize(width: context.availableSize.width, height: insets.top + title.size.height + spacing + subtitleHeight + insets.bottom)
context.add(label context.add(label
.position(CGPoint(x: context.availableSize.width - insets.right - label.size.width / 2.0, y: size.height / 2.0)) .position(CGPoint(x: context.availableSize.width - insets.right - label.size.width / 2.0, y: size.height / 2.0))
@ -1198,6 +1240,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let buy = context.component.buy let buy = context.component.buy
let updateIsFocused = context.component.updateIsFocused let updateIsFocused = context.component.updateIsFocused
let layoutOptions = {
if state.isPremium == true { if state.isPremium == true {
} else if let products = state.products { } else if let products = state.products {
@ -1208,11 +1251,11 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
UIColor(rgb: 0xb36eee) UIColor(rgb: 0xb36eee)
] ]
let shortestOptionPrice: Int64 let shortestOptionPrice: (Int64, NSDecimalNumber)
if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) { if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) {
shortestOptionPrice = Int64(Float(product.priceCurrencyAndAmount.amount)) shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
} else { } else {
shortestOptionPrice = 1 shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
} }
var i = 0 var i = 0
@ -1227,7 +1270,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
months = 12 months = 12
} }
let discountValue = Int((1.0 - Float(product.priceCurrencyAndAmount.amount) / months / Float(shortestOptionPrice)) * 100.0) let discountValue = Int((1.0 - Float(product.priceCurrencyAndAmount.amount) / months / Float(shortestOptionPrice.0)) * 100.0)
let discount: String let discount: String
if discountValue > 0 { if discountValue > 0 {
discount = "-\(discountValue)%" discount = "-\(discountValue)%"
@ -1235,6 +1278,24 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
discount = "" discount = ""
} }
let defaultPrice = product.defaultPrice(shortestOptionPrice.1, monthsCount: Int(months))
var subtitle = ""
var pricePerMonth = product.price
if months > 1 {
pricePerMonth = product.pricePerMonth(Int(months))
if defaultPrice != product.price {
subtitle = "**\(defaultPrice)** \(product.price)"
if months == 12 {
subtitle = environment.strings.Premium_PricePerYear(subtitle).string
}
} else {
subtitle = product.price
}
}
pricePerMonth = environment.strings.Premium_PricePerMonth(pricePerMonth).string
optionsItems.append( optionsItems.append(
SectionGroupComponent.Item( SectionGroupComponent.Item(
AnyComponentWithIdentity( AnyComponentWithIdentity(
@ -1242,7 +1303,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
component: AnyComponent( component: AnyComponent(
PremiumOptionComponent( PremiumOptionComponent(
title: giftTitle, title: giftTitle,
totalPrice: product.price, subtitle: subtitle,
labelPrice: pricePerMonth,
discount: discount, discount: discount,
selected: product.id == state.selectedProductId, selected: product.id == state.selectedProductId,
primaryTextColor: textColor, primaryTextColor: textColor,
@ -1278,9 +1340,16 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
.cornerRadius(10.0) .cornerRadius(10.0)
) )
size.height += optionsSection.size.height size.height += optionsSection.size.height
if case .emojiStatus = context.component.source {
size.height -= 18.0
} else {
size.height += 26.0 size.height += 26.0
} }
}
}
let layoutPerks = {
var i = 0 var i = 0
var perksItems: [SectionGroupComponent.Item] = [] var perksItems: [SectionGroupComponent.Item] = []
for perk in state.configuration.perks { for perk in state.configuration.perks {
@ -1389,7 +1458,24 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
.cornerRadius(10.0) .cornerRadius(10.0)
) )
size.height += perksSection.size.height size.height += perksSection.size.height
if case .emojiStatus = context.component.source {
if state.isPremium == true {
size.height -= 23.0
} else {
size.height += 23.0 size.height += 23.0
}
} else {
size.height += 23.0
}
}
if case .emojiStatus = context.component.source {
layoutPerks()
layoutOptions()
} else {
layoutOptions()
layoutPerks()
let textSideInset: CGFloat = 16.0 let textSideInset: CGFloat = 16.0
let textPadding: CGFloat = 13.0 let textPadding: CGFloat = 13.0
@ -1530,6 +1616,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
) )
size.height += termsText.size.height size.height += termsText.size.height
size.height += 10.0 size.height += 10.0
}
size.height += scrollEnvironment.insets.bottom size.height += scrollEnvironment.insets.bottom
if context.component.source != .settings { if context.component.source != .settings {
@ -1713,10 +1801,11 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
otherPeerName otherPeerName
).start(next: { [weak self] products, isPremium, otherPeerName in ).start(next: { [weak self] products, isPremium, otherPeerName in
if let strongSelf = self { if let strongSelf = self {
if strongSelf.products == nil { let hadProducts = strongSelf.products != nil
strongSelf.selectedProductId = products.first?.id
}
strongSelf.products = products.filter { $0.isSubscription } strongSelf.products = products.filter { $0.isSubscription }
if !hadProducts {
strongSelf.selectedProductId = strongSelf.products?.last?.id
}
strongSelf.isPremium = isPremium strongSelf.isPremium = isPremium
strongSelf.otherPeerName = otherPeerName strongSelf.otherPeerName = otherPeerName
strongSelf.updated(transition: .immediate) strongSelf.updated(transition: .immediate)
@ -1786,7 +1875,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
strongSelf.updateInProgress(false) strongSelf.updateInProgress(false)
strongSelf.updated(transition: .immediate) strongSelf.updated(transition: .immediate)
strongSelf.completion()
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail") addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail")
@ -2366,7 +2454,10 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
if let sourceView = self.sourceView { if let sourceView = self.sourceView {
view.animateFrom = sourceView view.animateFrom = sourceView
view.containerView = self.containerView view.containerView = self.containerView
view.animationColor = self.animationColor
Queue.mainQueue().after(0.1) {
view.animateIn()
}
self.sourceView = nil self.sourceView = nil
self.containerView = nil self.containerView = nil

View File

@ -5,6 +5,6 @@ import TelegramPresentationData
public extension SolidRoundedButtonTheme { public extension SolidRoundedButtonTheme {
convenience init(theme: PresentationTheme) { convenience init(theme: PresentationTheme) {
self.init(backgroundColor: theme.list.itemCheckColors.fillColor, backgroundColors: [], foregroundColor: theme.list.itemCheckColors.foregroundColor) self.init(backgroundColor: theme.list.itemCheckColors.fillColor, backgroundColors: [], foregroundColor: theme.list.itemCheckColors.foregroundColor, disabledBackgroundColor: theme.list.plainBackgroundColor.mixedWith(theme.list.itemDisabledTextColor, alpha: 0.15), disabledForegroundColor: theme.list.itemDisabledTextColor)
} }
} }

View File

@ -73,6 +73,7 @@ swift_library(
"//submodules/HexColor:HexColor", "//submodules/HexColor:HexColor",
"//submodules/QrCode:QrCode", "//submodules/QrCode:QrCode",
"//submodules/WallpaperResources:WallpaperResources", "//submodules/WallpaperResources:WallpaperResources",
"//submodules/AuthorizationUtils:AuthorizationUtils",
"//submodules/AuthorizationUI:AuthorizationUI", "//submodules/AuthorizationUI:AuthorizationUI",
"//submodules/InstantPageUI:InstantPageUI", "//submodules/InstantPageUI:InstantPageUI",
"//submodules/CheckNode:CheckNode", "//submodules/CheckNode:CheckNode",

View File

@ -11,7 +11,7 @@ import OverlayStatusController
import AccountContext import AccountContext
import AlertUI import AlertUI
import PresentationDataUtils import PresentationDataUtils
import AuthorizationUI import AuthorizationUtils
import PhoneNumberFormat import PhoneNumberFormat
private final class ChangePhoneNumberCodeControllerArguments { private final class ChangePhoneNumberCodeControllerArguments {

View File

@ -10,7 +10,7 @@ import PresentationDataUtils
import AccountContext import AccountContext
import AlertUI import AlertUI
import PresentationDataUtils import PresentationDataUtils
import AuthorizationUI import AuthorizationUtils
import PhoneNumberFormat import PhoneNumberFormat
private final class ConfirmPhoneNumberCodeControllerArguments { private final class ConfirmPhoneNumberCodeControllerArguments {

View File

@ -16,6 +16,7 @@ import AppBundle
import PasswordSetupUI import PasswordSetupUI
import UndoUI import UndoUI
import PremiumUI import PremiumUI
import AuthorizationUI
private final class PrivacyAndSecurityControllerArguments { private final class PrivacyAndSecurityControllerArguments {
let account: Account let account: Account
@ -33,8 +34,9 @@ private final class PrivacyAndSecurityControllerArguments {
let toggleArchiveAndMuteNonContacts: (Bool) -> Void let toggleArchiveAndMuteNonContacts: (Bool) -> Void
let setupAccountAutoremove: () -> Void let setupAccountAutoremove: () -> Void
let openDataSettings: () -> Void let openDataSettings: () -> Void
let openEmailSettings: (String?) -> Void
init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPhoneNumberPrivacy: @escaping () -> Void, openVoiceMessagePrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping (TwoStepVerificationAccessConfiguration?) -> Void, openActiveSessions: @escaping () -> Void, toggleArchiveAndMuteNonContacts: @escaping (Bool) -> Void, setupAccountAutoremove: @escaping () -> Void, openDataSettings: @escaping () -> Void) { init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPhoneNumberPrivacy: @escaping () -> Void, openVoiceMessagePrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping (TwoStepVerificationAccessConfiguration?) -> Void, openActiveSessions: @escaping () -> Void, toggleArchiveAndMuteNonContacts: @escaping (Bool) -> Void, setupAccountAutoremove: @escaping () -> Void, openDataSettings: @escaping () -> Void, openEmailSettings: @escaping (String?) -> Void) {
self.account = account self.account = account
self.openBlockedUsers = openBlockedUsers self.openBlockedUsers = openBlockedUsers
self.openLastSeenPrivacy = openLastSeenPrivacy self.openLastSeenPrivacy = openLastSeenPrivacy
@ -50,6 +52,7 @@ private final class PrivacyAndSecurityControllerArguments {
self.toggleArchiveAndMuteNonContacts = toggleArchiveAndMuteNonContacts self.toggleArchiveAndMuteNonContacts = toggleArchiveAndMuteNonContacts
self.setupAccountAutoremove = setupAccountAutoremove self.setupAccountAutoremove = setupAccountAutoremove
self.openDataSettings = openDataSettings self.openDataSettings = openDataSettings
self.openEmailSettings = openEmailSettings
} }
} }
@ -87,6 +90,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
case selectivePrivacyInfo(PresentationTheme, String) case selectivePrivacyInfo(PresentationTheme, String)
case passcode(PresentationTheme, String, Bool, String) case passcode(PresentationTheme, String, Bool, String)
case twoStepVerification(PresentationTheme, String, String, TwoStepVerificationAccessConfiguration?) case twoStepVerification(PresentationTheme, String, String, TwoStepVerificationAccessConfiguration?)
case loginEmail(PresentationTheme, String, String?)
case activeSessions(PresentationTheme, String, String) case activeSessions(PresentationTheme, String, String)
case autoArchiveHeader(String) case autoArchiveHeader(String)
case autoArchive(String, Bool) case autoArchive(String, Bool)
@ -99,7 +103,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .blockedPeers, .activeSessions, .passcode, .twoStepVerification: case .blockedPeers, .activeSessions, .passcode, .twoStepVerification, .loginEmail:
return PrivacyAndSecuritySection.general.rawValue return PrivacyAndSecuritySection.general.rawValue
case .privacyHeader, .phoneNumberPrivacy, .lastSeenPrivacy, .profilePhotoPrivacy, .forwardPrivacy, .groupPrivacy, .voiceCallPrivacy, .voiceMessagePrivacy, .selectivePrivacyInfo: case .privacyHeader, .phoneNumberPrivacy, .lastSeenPrivacy, .profilePhotoPrivacy, .forwardPrivacy, .groupPrivacy, .voiceCallPrivacy, .voiceMessagePrivacy, .selectivePrivacyInfo:
return PrivacyAndSecuritySection.privacy.rawValue return PrivacyAndSecuritySection.privacy.rawValue
@ -122,40 +126,42 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
return 3 return 3
case .twoStepVerification: case .twoStepVerification:
return 4 return 4
case .privacyHeader: case .loginEmail:
return 5 return 5
case .phoneNumberPrivacy: case .privacyHeader:
return 6 return 6
case .lastSeenPrivacy: case .phoneNumberPrivacy:
return 7 return 7
case .profilePhotoPrivacy: case .lastSeenPrivacy:
return 8 return 8
case .voiceCallPrivacy: case .profilePhotoPrivacy:
return 9 return 9
case .voiceMessagePrivacy: case .voiceCallPrivacy:
return 10 return 10
case .forwardPrivacy: case .voiceMessagePrivacy:
return 11 return 11
case .groupPrivacy: case .forwardPrivacy:
return 12 return 12
case .selectivePrivacyInfo: case .groupPrivacy:
return 13 return 13
case .autoArchiveHeader: case .selectivePrivacyInfo:
return 14 return 14
case .autoArchive: case .autoArchiveHeader:
return 15 return 15
case .autoArchiveInfo: case .autoArchive:
return 16 return 16
case .accountHeader: case .autoArchiveInfo:
return 17 return 17
case .accountTimeout: case .accountHeader:
return 18 return 18
case .accountInfo: case .accountTimeout:
return 19 return 19
case .dataSettings: case .accountInfo:
return 20 return 20
case .dataSettingsInfo: case .dataSettings:
return 21 return 21
case .dataSettingsInfo:
return 22
} }
} }
@ -233,6 +239,12 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .loginEmail(lhsTheme, lhsText, lhsEmailPattern):
if case let .loginEmail(rhsTheme, rhsText, rhsEmailPattern) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEmailPattern == rhsEmailPattern {
return true
} else {
return false
}
case let .activeSessions(lhsTheme, lhsText, lhsValue): case let .activeSessions(lhsTheme, lhsText, lhsValue):
if case let .activeSessions(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .activeSessions(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true return true
@ -341,6 +353,10 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/TwoStepAuth")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/TwoStepAuth")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openTwoStepVerification(data) arguments.openTwoStepVerification(data)
}) })
case let .loginEmail(_, text, emailPattern):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/LoginEmail")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openEmailSettings(emailPattern)
})
case let .activeSessions(_, text, value): case let .activeSessions(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Websites")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Websites")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openActiveSessions() arguments.openActiveSessions()
@ -411,7 +427,20 @@ private func stringForSelectiveSettings(strings: PresentationStrings, settings:
} }
} }
private func privacyAndSecurityControllerEntries(presentationData: PresentationData, state: PrivacyAndSecurityControllerState, privacySettings: AccountPrivacySettings?, accessChallengeData: PostboxAccessChallengeData, blockedPeerCount: Int?, activeWebsitesCount: Int, hasTwoStepAuth: Bool?, twoStepAuthData: TwoStepVerificationAccessConfiguration?, canAutoarchive: Bool, isPremiumDisabled: Bool, isPremium: Bool) -> [PrivacyAndSecurityEntry] { private func privacyAndSecurityControllerEntries(
presentationData: PresentationData,
state: PrivacyAndSecurityControllerState,
privacySettings: AccountPrivacySettings?,
accessChallengeData: PostboxAccessChallengeData,
blockedPeerCount: Int?,
activeWebsitesCount: Int,
hasTwoStepAuth: Bool?,
twoStepAuthData: TwoStepVerificationAccessConfiguration?,
canAutoarchive: Bool,
isPremiumDisabled: Bool,
isPremium: Bool,
loginEmail: String?
) -> [PrivacyAndSecurityEntry] {
var entries: [PrivacyAndSecurityEntry] = [] var entries: [PrivacyAndSecurityEntry] = []
entries.append(.blockedPeers(presentationData.theme, presentationData.strings.Settings_BlockedUsers, blockedPeerCount == nil ? "" : (blockedPeerCount == 0 ? presentationData.strings.PrivacySettings_BlockedPeersEmpty : "\(blockedPeerCount!)"))) entries.append(.blockedPeers(presentationData.theme, presentationData.strings.Settings_BlockedUsers, blockedPeerCount == nil ? "" : (blockedPeerCount == 0 ? presentationData.strings.PrivacySettings_BlockedPeersEmpty : "\(blockedPeerCount!)")))
@ -443,6 +472,8 @@ private func privacyAndSecurityControllerEntries(presentationData: PresentationD
} }
entries.append(.twoStepVerification(presentationData.theme, presentationData.strings.PrivacySettings_TwoStepAuth, twoStepAuthString, twoStepAuthData)) entries.append(.twoStepVerification(presentationData.theme, presentationData.strings.PrivacySettings_TwoStepAuth, twoStepAuthString, twoStepAuthData))
entries.append(.loginEmail(presentationData.theme, presentationData.strings.PrivacySettings_LoginEmail, loginEmail))
entries.append(.privacyHeader(presentationData.theme, presentationData.strings.PrivacySettings_PrivacyTitle)) entries.append(.privacyHeader(presentationData.theme, presentationData.strings.PrivacySettings_PrivacyTitle))
if let privacySettings = privacySettings { if let privacySettings = privacySettings {
entries.append(.phoneNumberPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_PhoneNumber, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.phoneNumber))) entries.append(.phoneNumberPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_PhoneNumber, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.phoneNumber)))
@ -509,7 +540,7 @@ class PrivacyAndSecurityControllerImpl: ItemListController {
} }
public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil, updatedBlockedPeers: ((BlockedPeersContext?) -> Void)? = nil, updatedHasTwoStepAuth: ((Bool) -> Void)? = nil, focusOnItemTag: PrivacyAndSecurityEntryTag? = nil, activeSessionsContext: ActiveSessionsContext? = nil, webSessionsContext: WebSessionsContext? = nil, blockedPeersContext: BlockedPeersContext? = nil, hasTwoStepAuth: Bool? = nil) -> ViewController { public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil, updatedBlockedPeers: ((BlockedPeersContext?) -> Void)? = nil, updatedHasTwoStepAuth: ((Bool) -> Void)? = nil, focusOnItemTag: PrivacyAndSecurityEntryTag? = nil, activeSessionsContext: ActiveSessionsContext? = nil, webSessionsContext: WebSessionsContext? = nil, blockedPeersContext: BlockedPeersContext? = nil, hasTwoStepAuth: Bool? = nil, loginEmailPattern: Signal<String?, NoError>? = nil) -> ViewController {
let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true) let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: PrivacyAndSecurityControllerState()) let stateValue = Atomic(value: PrivacyAndSecurityControllerState())
let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in
@ -568,6 +599,23 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
twoStepAuth.set(hasTwoStepAuthDataValue) twoStepAuth.set(hasTwoStepAuthDataValue)
} }
let loginEmail: Signal<String?, NoError>
if let loginEmailPattern = loginEmailPattern {
loginEmail = loginEmailPattern
} else {
loginEmail = .single(nil)
|> then(
context.engine.auth.twoStepAuthData()
|> map(Optional.init)
|> `catch` { _ -> Signal<TwoStepAuthData?, NoError> in
return .single(nil)
}
|> map { data -> String? in
return data?.loginEmailPattern
}
)
}
let updateHasTwoStepAuth: () -> Void = { let updateHasTwoStepAuth: () -> Void = {
let signal = context.engine.auth.twoStepVerificationConfiguration() let signal = context.engine.auth.twoStepVerificationConfiguration()
|> map { value -> TwoStepVerificationAccessConfiguration? in |> map { value -> TwoStepVerificationAccessConfiguration? in
@ -589,6 +637,8 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
} }
updateHasTwoStepAuth() updateHasTwoStepAuth()
var setupEmailImpl: ((String?) -> Void)?
let arguments = PrivacyAndSecurityControllerArguments(account: context.account, openBlockedUsers: { let arguments = PrivacyAndSecurityControllerArguments(account: context.account, openBlockedUsers: {
pushControllerImpl?(blockedPeersController(context: context, blockedPeersContext: blockedPeersContext), true) pushControllerImpl?(blockedPeersController(context: context, blockedPeersContext: blockedPeersContext), true)
}, openLastSeenPrivacy: { }, openLastSeenPrivacy: {
@ -926,6 +976,23 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
})) }))
}, openDataSettings: { }, openDataSettings: {
pushControllerImpl?(dataPrivacyController(context: context), true) pushControllerImpl?(dataPrivacyController(context: context), true)
}, openEmailSettings: { emailPattern in
if let emailPattern = emailPattern {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = textAlertController(
context: context, title: emailPattern, text: "This email address will be used every time you login to your Telegram account from a new device.", actions: [
TextAlertAction(type: .genericAction, title: "Change Email", action: {
setupEmailImpl?(emailPattern)
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
})
], actionLayout: .vertical
)
presentControllerImpl?(controller)
} else {
setupEmailImpl?(nil)
}
}) })
actionsDisposable.add(context.engine.peers.managedUpdatedRecentPeers().start()) actionsDisposable.add(context.engine.peers.managedUpdatedRecentPeers().start())
@ -953,9 +1020,10 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
context.sharedContext.accountManager.accessChallengeData(), context.sharedContext.accountManager.accessChallengeData(),
combineLatest(twoStepAuth.get(), twoStepAuthDataValue.get()), combineLatest(twoStepAuth.get(), twoStepAuthDataValue.get()),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App()), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App()),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)),
loginEmail
) )
|> map { presentationData, state, privacySettings, noticeView, sharedData, recentPeers, blockedPeersState, activeWebsitesState, accessChallengeData, twoStepAuth, appConfiguration, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, privacySettings, noticeView, sharedData, recentPeers, blockedPeersState, activeWebsitesState, accessChallengeData, twoStepAuth, appConfiguration, accountPeer, loginEmail -> (ItemListControllerState, (ItemListNodeState, Any)) in
var canAutoarchive = false var canAutoarchive = false
if let data = appConfiguration.data, let hasAutoarchive = data["autoarchive_setting_available"] as? Bool { if let data = appConfiguration.data, let hasAutoarchive = data["autoarchive_setting_available"] as? Bool {
canAutoarchive = hasAutoarchive canAutoarchive = hasAutoarchive
@ -971,7 +1039,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
let isPremium = accountPeer?.isPremium ?? false let isPremium = accountPeer?.isPremium ?? false
let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }).isPremiumDisabled let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }).isPremiumDisabled
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: privacyAndSecurityControllerEntries(presentationData: presentationData, state: state, privacySettings: privacySettings, accessChallengeData: accessChallengeData.data, blockedPeerCount: blockedPeersState.totalCount, activeWebsitesCount: activeWebsitesState.sessions.count, hasTwoStepAuth: twoStepAuth.0, twoStepAuthData: twoStepAuth.1, canAutoarchive: canAutoarchive, isPremiumDisabled: isPremiumDisabled, isPremium: isPremium), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: privacyAndSecurityControllerEntries(presentationData: presentationData, state: state, privacySettings: privacySettings, accessChallengeData: accessChallengeData.data, blockedPeerCount: blockedPeersState.totalCount, activeWebsitesCount: activeWebsitesState.sessions.count, hasTwoStepAuth: twoStepAuth.0, twoStepAuthData: twoStepAuth.1, canAutoarchive: canAutoarchive, isPremiumDisabled: isPremiumDisabled, isPremium: isPremium, loginEmail: loginEmail), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }
@ -997,5 +1065,60 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
updateHasTwoStepAuth() updateHasTwoStepAuth()
} }
setupEmailImpl = { emailPattern in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var dismissEmailControllerImpl: (() -> Void)?
let emailController = AuthorizationSequenceEmailEntryController(presentationData: presentationData, mode: emailPattern != nil ? .change : .setup, back: {
dismissEmailControllerImpl?()
})
emailController.proceedWithEmail = { [weak emailController] email in
emailController?.inProgress = true
actionsDisposable.add((sendLoginEmailChangeCode(account: context.account, email: email)
|> deliverOnMainQueue).start(next: { data in
var dismissCodeControllerImpl: (() -> Void)?
let codeController = AuthorizationSequenceCodeEntryController(presentationData: presentationData, openUrl: { _ in }, back: {
dismissCodeControllerImpl?()
})
codeController.loginWithCode = { code in
}
codeController.signInWithApple = {
}
codeController.updateData(number: "", email: email, codeType: .email(emailPattern: "", length: data.length, nextPhoneLoginDate: nil, appleSignInAllowed: false, setup: true), nextType: nil, timeout: nil, termsOfService: nil)
pushControllerImpl?(codeController, true)
dismissCodeControllerImpl = { [weak codeController] in
codeController?.dismiss()
}
}, error: { [weak emailController] error in
emailController?.inProgress = false
let text: String
switch error {
case .limitExceeded:
text = presentationData.strings.Login_CodeFloodError
case .generic, .codeExpired:
text = presentationData.strings.Login_UnknownError
case .timeout:
text = presentationData.strings.Login_NetworkError
}
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
}, completed: { [weak emailController] in
emailController?.inProgress = false
}))
}
emailController.signInWithApple = {
}
emailController.updateData(appleSignInAllowed: true)
pushControllerImpl?(emailController, true)
dismissEmailControllerImpl = { [weak emailController] in
emailController?.dismiss()
}
}
return controller return controller
} }

View File

@ -7,7 +7,7 @@ import TelegramCore
import SwiftSignalKit import SwiftSignalKit
import TelegramPresentationData import TelegramPresentationData
import AccountContext import AccountContext
import AuthorizationUI import AuthorizationUtils
import AnimatedStickerNode import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode

View File

@ -23,11 +23,15 @@ public final class SolidRoundedButtonTheme: Equatable {
public let backgroundColor: UIColor public let backgroundColor: UIColor
public let backgroundColors: [UIColor] public let backgroundColors: [UIColor]
public let foregroundColor: UIColor public let foregroundColor: UIColor
public let disabledBackgroundColor: UIColor?
public let disabledForegroundColor: UIColor?
public init(backgroundColor: UIColor, backgroundColors: [UIColor] = [], foregroundColor: UIColor) { public init(backgroundColor: UIColor, backgroundColors: [UIColor] = [], foregroundColor: UIColor, disabledBackgroundColor: UIColor? = nil, disabledForegroundColor: UIColor? = nil) {
self.backgroundColor = backgroundColor self.backgroundColor = backgroundColor
self.backgroundColors = backgroundColors self.backgroundColors = backgroundColors
self.foregroundColor = foregroundColor self.foregroundColor = foregroundColor
self.disabledBackgroundColor = disabledBackgroundColor
self.disabledForegroundColor = disabledForegroundColor
} }
public static func ==(lhs: SolidRoundedButtonTheme, rhs: SolidRoundedButtonTheme) -> Bool { public static func ==(lhs: SolidRoundedButtonTheme, rhs: SolidRoundedButtonTheme) -> Bool {
@ -40,6 +44,12 @@ public final class SolidRoundedButtonTheme: Equatable {
if lhs.foregroundColor != rhs.foregroundColor { if lhs.foregroundColor != rhs.foregroundColor {
return false return false
} }
if lhs.disabledBackgroundColor != rhs.disabledBackgroundColor {
return false
}
if lhs.disabledForegroundColor != rhs.disabledForegroundColor {
return false
}
return true return true
} }
} }
@ -86,6 +96,14 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
public var pressed: (() -> Void)? public var pressed: (() -> Void)?
public var validLayout: CGFloat? public var validLayout: CGFloat?
public var isEnabled: Bool = true {
didSet {
if self.isEnabled != oldValue {
self.updateColors(animated: true)
}
}
}
public var title: String? { public var title: String? {
didSet { didSet {
if let width = self.validLayout { if let width = self.validLayout {
@ -220,7 +238,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.buttonNode.highligthedChanged = { [weak self] highlighted in self.buttonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self { if let strongSelf = self, strongSelf.isEnabled {
if highlighted { if highlighted {
strongSelf.buttonBackgroundNode.layer.removeAnimation(forKey: "opacity") strongSelf.buttonBackgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.buttonBackgroundNode.alpha = 0.55 strongSelf.buttonBackgroundNode.alpha = 0.55
@ -312,36 +330,20 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
} }
} }
public func animateTitle(from title: String) {
let originalTitle = self.title ?? ""
self.title = title
Queue.mainQueue().justDispatch {
if let snapshotView = self.titleNode.view.snapshotView(afterScreenUpdates: false) {
snapshotView.frame = self.titleNode.frame
self.view.insertSubview(snapshotView, aboveSubview: self.titleNode.view)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.title = originalTitle
}
}
}
public func animateTitle(to title: String) { public func animateTitle(to title: String) {
if let snapshotView = self.titleNode.view.snapshotView(afterScreenUpdates: false) { Queue.mainQueue().justDispatch {
snapshotView.frame = self.titleNode.frame if let snapshotView = self.view.snapshotView(afterScreenUpdates: false) {
snapshotView.frame = self.bounds
self.view.insertSubview(snapshotView, aboveSubview: self.titleNode.view) self.view.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview() snapshotView?.removeFromSuperview()
}) })
self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) }
self.title = title self.title = title
self.buttonBackgroundNode.backgroundColor = self.theme.disabledBackgroundColor
self.isEnabled = false
} }
} }
@ -459,6 +461,33 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
} }
} }
self.iconNode.image = generateTintedImage(image: self.iconNode.image, color: theme.foregroundColor)
self.animationNode?.customColor = theme.foregroundColor
self.updateColors(animated: false)
self.updateShimmerParameters()
}
func updateColors(animated: Bool) {
if animated {
if let snapshotView = self.view.snapshotView(afterScreenUpdates: false) {
self.view.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
}
}
if !self.isEnabled, let disabledBackgroundColor = self.theme.disabledBackgroundColor, let disabledForegroundColor = self.theme.disabledForegroundColor {
self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: disabledForegroundColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: disabledForegroundColor)
self.buttonBackgroundNode.backgroundColor = disabledBackgroundColor
} else {
self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: self.theme.foregroundColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: self.theme.foregroundColor)
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
if theme.backgroundColors.count > 1 { if theme.backgroundColors.count > 1 {
self.buttonBackgroundNode.backgroundColor = nil self.buttonBackgroundNode.backgroundColor = nil
@ -481,18 +510,11 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
self.buttonBackgroundNode.image = nil self.buttonBackgroundNode.image = nil
self.buttonBackgroundAnimationView?.image = nil self.buttonBackgroundAnimationView?.image = nil
} }
}
self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: theme.foregroundColor)
self.iconNode.image = generateTintedImage(image: self.iconNode.image, color: theme.foregroundColor)
self.animationNode?.customColor = theme.foregroundColor
if let width = self.validLayout { if let width = self.validLayout {
_ = self.updateLayout(width: width, transition: .immediate) _ = self.updateLayout(width: width, transition: .immediate)
} }
self.updateShimmerParameters()
} }
public func sizeThatFits(_ constrainedSize: CGSize) -> CGSize { public func sizeThatFits(_ constrainedSize: CGSize) -> CGSize {
@ -532,7 +554,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
transition.updateFrame(node: self.buttonNode, frame: buttonFrame) transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
if self.title != self.titleNode.attributedText?.string { if self.title != self.titleNode.attributedText?.string {
self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: self.theme.foregroundColor) self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: self.isEnabled ? self.theme.foregroundColor : (self.theme.disabledForegroundColor ?? self.theme.foregroundColor))
} }
let iconSize: CGSize let iconSize: CGSize
@ -595,8 +617,10 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
} }
@objc private func buttonPressed() { @objc private func buttonPressed() {
if self.isEnabled {
self.pressed?() self.pressed?()
} }
}
public func transitionToProgress() { public func transitionToProgress() {
guard self.progressNode == nil else { guard self.progressNode == nil else {

View File

@ -39,6 +39,7 @@ swift_library(
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache", "//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
"//submodules/StickerPeekUI:StickerPeekUI", "//submodules/StickerPeekUI:StickerPeekUI",
"//submodules/Pasteboard:Pasteboard",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -14,6 +14,7 @@ import ShimmerEffect
import EntityKeyboard import EntityKeyboard
import AnimationCache import AnimationCache
import MultiAnimationRenderer import MultiAnimationRenderer
import TextFormat
private let nativeItemSize = 36.0 private let nativeItemSize = 36.0
private let minItemsPerRow = 8 private let minItemsPerRow = 8
@ -24,17 +25,14 @@ private let containerInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, ri
class ItemLayout { class ItemLayout {
let width: CGFloat let width: CGFloat
let itemsCount: Int let itemsCount: Int
let hasTitle: Bool
let itemsPerRow: Int let itemsPerRow: Int
let visibleItemSize: CGFloat let visibleItemSize: CGFloat
let horizontalSpacing: CGFloat let horizontalSpacing: CGFloat
let itemTopOffset: CGFloat
let height: CGFloat let height: CGFloat
init(width: CGFloat, itemsCount: Int, hasTitle: Bool) { init(width: CGFloat, itemsCount: Int) {
self.width = width self.width = width
self.itemsCount = itemsCount self.itemsCount = itemsCount
self.hasTitle = hasTitle
let itemHorizontalSpace = width - containerInsets.left - containerInsets.right let itemHorizontalSpace = width - containerInsets.left - containerInsets.right
self.itemsPerRow = max(minItemsPerRow, Int((itemHorizontalSpace + minSpacing) / (nativeItemSize + minSpacing))) self.itemsPerRow = max(minItemsPerRow, Int((itemHorizontalSpace + minSpacing) / (nativeItemSize + minSpacing)))
@ -45,8 +43,7 @@ class ItemLayout {
let numRowsInGroup = (itemsCount + (self.itemsPerRow - 1)) / self.itemsPerRow let numRowsInGroup = (itemsCount + (self.itemsPerRow - 1)) / self.itemsPerRow
self.itemTopOffset = hasTitle ? 61.0 : 0.0 self.height = CGFloat(numRowsInGroup) * visibleItemSize + CGFloat(max(0, numRowsInGroup - 1)) * verticalSpacing
self.height = itemTopOffset + CGFloat(numRowsInGroup) * visibleItemSize + CGFloat(max(0, numRowsInGroup - 1)) * verticalSpacing
} }
func frame(itemIndex: Int) -> CGRect { func frame(itemIndex: Int) -> CGRect {
@ -56,7 +53,7 @@ class ItemLayout {
return CGRect( return CGRect(
origin: CGPoint( origin: CGPoint(
x: containerInsets.left + CGFloat(column) * (self.visibleItemSize + self.horizontalSpacing), x: containerInsets.left + CGFloat(column) * (self.visibleItemSize + self.horizontalSpacing),
y: self.itemTopOffset + CGFloat(row) * (self.visibleItemSize + verticalSpacing) y: CGFloat(row) * (self.visibleItemSize + verticalSpacing)
), ),
size: CGSize( size: CGSize(
width: self.visibleItemSize, width: self.visibleItemSize,
@ -96,8 +93,8 @@ final class StickerPackEmojisItem: GridItem {
self.isEmpty = isEmpty self.isEmpty = isEmpty
self.fillsRowWithDynamicHeight = { width in self.fillsRowWithDynamicHeight = { width in
let layout = ItemLayout(width: width, itemsCount: items.count, hasTitle: title != nil) let layout = ItemLayout(width: width, itemsCount: items.count)
return layout.height return layout.height + (title != nil ? 61.0 : 0.0)
} }
} }
@ -128,11 +125,13 @@ final class StickerPackEmojisItemNode: GridItemNode {
private var visibleItemLayers: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemLayer] = [:] private var visibleItemLayers: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemLayer] = [:]
private var visibleItemPlaceholderViews: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemPlaceholderView] = [:] private var visibleItemPlaceholderViews: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemPlaceholderView] = [:]
private let containerNode: ASDisplayNode
private let titleNode: ImmediateTextNode private let titleNode: ImmediateTextNode
private let subtitleNode: ImmediateTextNode private let subtitleNode: ImmediateTextNode
private let buttonNode: HighlightableButtonNode private let buttonNode: HighlightableButtonNode
override init() { override init() {
self.containerNode = ASDisplayNode()
self.titleNode = ImmediateTextNode() self.titleNode = ImmediateTextNode()
self.subtitleNode = ImmediateTextNode() self.subtitleNode = ImmediateTextNode()
self.buttonNode = HighlightableButtonNode(pointerStyle: nil) self.buttonNode = HighlightableButtonNode(pointerStyle: nil)
@ -141,6 +140,7 @@ final class StickerPackEmojisItemNode: GridItemNode {
super.init() super.init()
self.addSubnode(self.containerNode)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.subtitleNode) self.addSubnode(self.subtitleNode)
self.addSubnode(self.buttonNode) self.addSubnode(self.buttonNode)
@ -191,6 +191,98 @@ final class StickerPackEmojisItemNode: GridItemNode {
self?.standaloneShimmerEffect?.updateLayer() self?.standaloneShimmerEffect?.updateLayer()
} }
self.boundsChangeTrackerLayer = boundsChangeTrackerLayer self.boundsChangeTrackerLayer = boundsChangeTrackerLayer
let gestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
gestureRecognizer.longTap = { [weak self] point, _ in
guard let strongSelf = self else {
return
}
if let (item, itemFrame) = strongSelf.item(atPoint: point), let file = item.itemFile {
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
loop: for attribute in file.attributes {
switch attribute {
case let .CustomEmoji(_, displayText, packReference):
text = displayText
emojiAttribute = ChatTextInputTextCustomEmojiAttribute(stickerPack: packReference, fileId: file.fileId.id, file: file)
break loop
default:
break
}
}
if let emojiAttribute = emojiAttribute {
strongSelf.item?.interaction.emojiLongPressed(text, emojiAttribute, strongSelf.containerNode, itemFrame)
}
}
}
self.containerNode.view.addGestureRecognizer(gestureRecognizer)
}
@objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, let (item, _) = self.item(atPoint: location), let file = item.itemFile {
if case .tap = gesture {
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
loop: for attribute in file.attributes {
switch attribute {
case let .CustomEmoji(_, displayText, packReference):
text = displayText
emojiAttribute = ChatTextInputTextCustomEmojiAttribute(stickerPack: packReference, fileId: file.fileId.id, file: file)
break loop
default:
break
}
}
if let emojiAttribute = emojiAttribute {
self.item?.interaction.emojiSelected(text, emojiAttribute)
}
}
}
default:
break
}
}
private func item(atPoint point: CGPoint, extendedHitRange: Bool = false) -> (EmojiPagerContentComponent.Item, CGRect)? {
let localPoint = point
var closestItem: (key: EmojiPagerContentComponent.View.ItemLayer.Key, distance: CGFloat)?
for (key, itemLayer) in self.visibleItemLayers {
if extendedHitRange {
let position = CGPoint(x: itemLayer.frame.midX, y: itemLayer.frame.midY)
let distance = CGPoint(x: localPoint.x - position.x, y: localPoint.y - position.y)
let distance2 = distance.x * distance.x + distance.y * distance.y
if distance2 > pow(max(itemLayer.bounds.width, itemLayer.bounds.height), 2.0) {
continue
}
if let closestItemValue = closestItem {
if closestItemValue.distance > distance2 {
closestItem = (key, distance2)
}
} else {
closestItem = (key, distance2)
}
} else {
if itemLayer.frame.contains(localPoint) {
return (itemLayer.item, itemLayer.frame)
}
}
}
if let key = closestItem?.key {
if let itemLayer = self.visibleItemLayers[key] {
return (itemLayer.item, itemLayer.frame)
}
}
return nil
} }
private var size = CGSize() private var size = CGSize()
@ -233,13 +325,15 @@ final class StickerPackEmojisItemNode: GridItemNode {
var validIds = Set<EmojiPagerContentComponent.View.ItemLayer.Key>() var validIds = Set<EmojiPagerContentComponent.View.ItemLayer.Key>()
let itemLayout: ItemLayout let itemLayout: ItemLayout
if let current = self.itemLayout, current.width == self.size.width && current.itemsCount == items.count && current.hasTitle == (item.title != nil) { if let current = self.itemLayout, current.width == self.size.width && current.itemsCount == items.count {
itemLayout = current itemLayout = current
} else { } else {
itemLayout = ItemLayout(width: self.size.width, itemsCount: items.count, hasTitle: item.title != nil) itemLayout = ItemLayout(width: self.size.width, itemsCount: items.count)
self.itemLayout = itemLayout self.itemLayout = itemLayout
} }
self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: item.title != nil ? 61.0 : 0.0), size: CGSize(width: itemLayout.width, height: itemLayout.height))
for index in 0 ..< items.count { for index in 0 ..< items.count {
let item = items[index] let item = items[index]
let itemId = EmojiPagerContentComponent.View.ItemLayer.Key( let itemId = EmojiPagerContentComponent.View.ItemLayer.Key(
@ -325,7 +419,7 @@ final class StickerPackEmojisItemNode: GridItemNode {
} }
} }
) )
self.layer.addSublayer(itemLayer) self.containerNode.layer.addSublayer(itemLayer)
self.visibleItemLayers[itemId] = itemLayer self.visibleItemLayers[itemId] = itemLayer
} }

View File

@ -143,7 +143,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
super.init() super.init()
self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false, addStickerPack: { _, _ in }, removeStickerPack: { _ in }) self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false, addStickerPack: { _, _ in }, removeStickerPack: { _ in }, emojiSelected: { _, _ in }, emojiLongPressed: { _, _, _, _ in })
self.backgroundColor = nil self.backgroundColor = nil
self.isOpaque = false self.isOpaque = false

View File

@ -12,6 +12,7 @@ import TelegramAnimatedStickerNode
import TelegramPresentationData import TelegramPresentationData
import ShimmerEffect import ShimmerEffect
import StickerPeekUI import StickerPeekUI
import TextFormat
final class StickerPackPreviewInteraction { final class StickerPackPreviewInteraction {
var previewedItem: StickerPreviewPeekItem? var previewedItem: StickerPreviewPeekItem?
@ -19,11 +20,15 @@ final class StickerPackPreviewInteraction {
let addStickerPack: (StickerPackCollectionInfo, [StickerPackItem]) -> Void let addStickerPack: (StickerPackCollectionInfo, [StickerPackItem]) -> Void
let removeStickerPack: (StickerPackCollectionInfo) -> Void let removeStickerPack: (StickerPackCollectionInfo) -> Void
let emojiSelected: (String, ChatTextInputTextCustomEmojiAttribute) -> Void
let emojiLongPressed: (String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void
init(playAnimatedStickers: Bool, addStickerPack: @escaping (StickerPackCollectionInfo, [StickerPackItem]) -> Void, removeStickerPack: @escaping (StickerPackCollectionInfo) -> Void) { init(playAnimatedStickers: Bool, addStickerPack: @escaping (StickerPackCollectionInfo, [StickerPackItem]) -> Void, removeStickerPack: @escaping (StickerPackCollectionInfo) -> Void, emojiSelected: @escaping (String, ChatTextInputTextCustomEmojiAttribute) -> Void, emojiLongPressed: @escaping (String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void) {
self.playAnimatedStickers = playAnimatedStickers self.playAnimatedStickers = playAnimatedStickers
self.addStickerPack = addStickerPack self.addStickerPack = addStickerPack
self.removeStickerPack = removeStickerPack self.removeStickerPack = removeStickerPack
self.emojiSelected = emojiSelected
self.emojiLongPressed = emojiLongPressed
} }
} }

View File

@ -21,6 +21,7 @@ import PresentationDataUtils
import StickerPeekUI import StickerPeekUI
import AnimationCache import AnimationCache
import MultiAnimationRenderer import MultiAnimationRenderer
import Pasteboard
private enum StickerPackPreviewGridEntry: Comparable, Identifiable { private enum StickerPackPreviewGridEntry: Comparable, Identifiable {
case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool) case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool)
@ -145,7 +146,21 @@ private final class StickerPackContainer: ASDisplayNode {
var onReady: () -> Void = {} var onReady: () -> Void = {}
var onError: () -> Void = {} var onError: () -> Void = {}
init(index: Int, context: AccountContext, presentationData: PresentationData, stickerPacks: [StickerPackReference], decideNextAction: @escaping (StickerPackContainer, StickerPackAction) -> StickerPackNextAction, requestDismiss: @escaping () -> Void, expandProgressUpdated: @escaping (StickerPackContainer, ContainedViewLayoutTransition, ContainedViewLayoutTransition) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, openMention: @escaping (String) -> Void, controller: StickerPackScreenImpl?) { init(
index: Int,
context: AccountContext,
presentationData: PresentationData,
stickerPacks: [StickerPackReference],
decideNextAction: @escaping (StickerPackContainer, StickerPackAction) -> StickerPackNextAction,
requestDismiss: @escaping () -> Void,
expandProgressUpdated: @escaping (StickerPackContainer, ContainedViewLayoutTransition, ContainedViewLayoutTransition) -> Void,
presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void,
sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?,
sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?,
longPressEmoji: ((String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void)?,
openMention: @escaping (String) -> Void,
controller: StickerPackScreenImpl?)
{
self.index = index self.index = index
self.context = context self.context = context
self.controller = controller self.controller = controller
@ -201,10 +216,16 @@ private final class StickerPackContainer: ASDisplayNode {
var addStickerPackImpl: ((StickerPackCollectionInfo, [StickerPackItem]) -> Void)? var addStickerPackImpl: ((StickerPackCollectionInfo, [StickerPackItem]) -> Void)?
var removeStickerPackImpl: ((StickerPackCollectionInfo) -> Void)? var removeStickerPackImpl: ((StickerPackCollectionInfo) -> Void)?
var emojiSelectedImpl: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?
var emojiLongPressedImpl: ((String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void)?
self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: true, addStickerPack: { info, items in self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: true, addStickerPack: { info, items in
addStickerPackImpl?(info, items) addStickerPackImpl?(info, items)
}, removeStickerPack: { info in }, removeStickerPack: { info in
removeStickerPackImpl?(info) removeStickerPackImpl?(info)
}, emojiSelected: { text, attribute in
emojiSelectedImpl?(text, attribute)
}, emojiLongPressed: { text, attribute, node, frame in
emojiLongPressedImpl?(text, attribute, node, frame)
}) })
super.init() super.init()
@ -215,7 +236,6 @@ private final class StickerPackContainer: ASDisplayNode {
self.addSubnode(self.actionAreaSeparatorNode) self.addSubnode(self.actionAreaSeparatorNode)
self.addSubnode(self.buttonNode) self.addSubnode(self.buttonNode)
// self.addSubnode(self.titleBackgroundnode)
self.titleContainer.addSubnode(self.titleNode) self.titleContainer.addSubnode(self.titleNode)
self.addSubnode(self.titleContainer) self.addSubnode(self.titleContainer)
self.addSubnode(self.titleSeparatorNode) self.addSubnode(self.titleSeparatorNode)
@ -395,6 +415,14 @@ private final class StickerPackContainer: ASDisplayNode {
let _ = strongSelf.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete).start() let _ = strongSelf.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete).start()
} }
} }
emojiSelectedImpl = { text, attribute in
sendEmoji?(text, attribute)
}
emojiLongPressedImpl = { text, attribute, node, frame in
longPressEmoji?(text, attribute, node, frame)
}
} }
deinit { deinit {
@ -1017,13 +1045,13 @@ private final class StickerPackContainer: ASDisplayNode {
if !self.currentStickerPacks.isEmpty { if !self.currentStickerPacks.isEmpty {
var packsHeight = 0.0 var packsHeight = 0.0
for stickerPack in currentStickerPacks { for stickerPack in currentStickerPacks {
let layout = ItemLayout(width: fillingWidth, itemsCount: stickerPack.1.count, hasTitle: true) let layout = ItemLayout(width: fillingWidth, itemsCount: stickerPack.1.count)
packsHeight += layout.height packsHeight += layout.height + 61.0
} }
contentHeight = packsHeight + 8.0 contentHeight = packsHeight + 8.0
} else if let (info, items, _) = self.currentStickerPack { } else if let (info, items, _) = self.currentStickerPack {
if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
let layout = ItemLayout(width: fillingWidth, itemsCount: items.count, hasTitle: false) let layout = ItemLayout(width: fillingWidth, itemsCount: items.count)
contentHeight = layout.height contentHeight = layout.height
} else { } else {
let rowCount = items.count / itemsPerRow + ((items.count % itemsPerRow) == 0 ? 0 : 1) let rowCount = items.count / itemsPerRow + ((items.count % itemsPerRow) == 0 ? 0 : 1)
@ -1186,6 +1214,8 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
private let dismissed: () -> Void private let dismissed: () -> Void
private let presentInGlobalOverlay: (ViewController, Any?) -> Void private let presentInGlobalOverlay: (ViewController, Any?) -> Void
private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
private let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?
private let longPressEmoji: ((String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void)?
private let openMention: (String) -> Void private let openMention: (String) -> Void
private let dimNode: ASDisplayNode private let dimNode: ASDisplayNode
@ -1207,7 +1237,19 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
var onReady: () -> Void = {} var onReady: () -> Void = {}
var onError: () -> Void = {} var onError: () -> Void = {}
init(context: AccountContext, controller: StickerPackScreenImpl, stickerPacks: [StickerPackReference], initialSelectedStickerPackIndex: Int, modalProgressUpdated: @escaping (CGFloat, ContainedViewLayoutTransition) -> Void, dismissed: @escaping () -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, openMention: @escaping (String) -> Void) { init(
context: AccountContext,
controller: StickerPackScreenImpl,
stickerPacks: [StickerPackReference],
initialSelectedStickerPackIndex: Int,
modalProgressUpdated: @escaping (CGFloat, ContainedViewLayoutTransition) -> Void,
dismissed: @escaping () -> Void,
presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void,
sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?,
sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?,
longPressEmoji: ((String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void)?,
openMention: @escaping (String) -> Void)
{
self.context = context self.context = context
self.controller = controller self.controller = controller
self.presentationData = controller.presentationData self.presentationData = controller.presentationData
@ -1217,6 +1259,8 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
self.dismissed = dismissed self.dismissed = dismissed
self.presentInGlobalOverlay = presentInGlobalOverlay self.presentInGlobalOverlay = presentInGlobalOverlay
self.sendSticker = sendSticker self.sendSticker = sendSticker
self.sendEmoji = sendEmoji
self.longPressEmoji = longPressEmoji
self.openMention = openMention self.openMention = openMention
self.dimNode = ASDisplayNode() self.dimNode = ASDisplayNode()
@ -1327,7 +1371,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
} }
} }
} }
}, presentInGlobalOverlay: presentInGlobalOverlay, sendSticker: sendSticker, openMention: openMention, controller: self.controller) }, presentInGlobalOverlay: presentInGlobalOverlay, sendSticker: self.sendSticker, sendEmoji: self.sendEmoji, longPressEmoji: self.longPressEmoji, openMention: self.openMention, controller: self.controller)
container.onReady = { [weak self] in container.onReady = { [weak self] in
self?.onReady() self?.onReady()
} }
@ -1518,6 +1562,7 @@ public final class StickerPackScreenImpl: ViewController {
private let initialSelectedStickerPackIndex: Int private let initialSelectedStickerPackIndex: Int
fileprivate weak var parentNavigationController: NavigationController? fileprivate weak var parentNavigationController: NavigationController?
private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
private let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?
private var controllerNode: StickerPackScreenNode { private var controllerNode: StickerPackScreenNode {
return self.displayNode as! StickerPackScreenNode return self.displayNode as! StickerPackScreenNode
@ -1539,13 +1584,23 @@ public final class StickerPackScreenImpl: ViewController {
let animationCache: AnimationCache let animationCache: AnimationCache
let animationRenderer: MultiAnimationRenderer let animationRenderer: MultiAnimationRenderer
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, stickerPacks: [StickerPackReference], selectedStickerPackIndex: Int = 0, parentNavigationController: NavigationController? = nil, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil, actionPerformed: (([(StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction)]) -> Void)? = nil) { public init(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
stickerPacks: [StickerPackReference],
selectedStickerPackIndex: Int = 0,
parentNavigationController: NavigationController? = nil,
sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil,
sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?,
actionPerformed: (([(StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction)]) -> Void)? = nil
) {
self.context = context self.context = context
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
self.stickerPacks = stickerPacks self.stickerPacks = stickerPacks
self.initialSelectedStickerPackIndex = selectedStickerPackIndex self.initialSelectedStickerPackIndex = selectedStickerPackIndex
self.parentNavigationController = parentNavigationController self.parentNavigationController = parentNavigationController
self.sendSticker = sendSticker self.sendSticker = sendSticker
self.sendEmoji = sendEmoji
self.actionPerformed = actionPerformed self.actionPerformed = actionPerformed
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: { self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
@ -1605,6 +1660,46 @@ public final class StickerPackScreenImpl: ViewController {
return false return false
} }
} }
}, sendEmoji: self.sendEmoji.flatMap { [weak self] sendEmoji in
return { text, attribute in
sendEmoji(text, attribute)
self?.controllerNode.dismiss()
}
}, longPressEmoji: { [weak self] text, attribute, node, frame in
guard let strongSelf = self else {
return
}
var actions: [ContextMenuAction] = []
actions.append(ContextMenuAction(content: .text(title: strongSelf.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: strongSelf.presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in
storeMessageTextInPasteboard(
text,
entities: [
MessageTextEntity(
range: 0 ..< (text as NSString).length,
type: .CustomEmoji(
stickerPack: attribute.stickerPack,
fileId: attribute.fileId
)
)
]
)
if let strongSelf = self, let file = attribute.file {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: file, title: nil, text: presentationData.strings.Conversation_EmojiCopied, undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
}
}))
let contextMenuController = ContextMenuController(actions: actions)
strongSelf.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
if let strongSelf = self {
return (node, frame.insetBy(dx: -40.0, dy: 0.0), strongSelf.controllerNode, strongSelf.controllerNode.view.bounds)
} else {
return nil
}
}))
}, openMention: { [weak self] mention in }, openMention: { [weak self] mention in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -1718,16 +1813,29 @@ public enum StickerPackScreenPerformedAction {
case remove(positionInList: Int) case remove(positionInList: Int)
} }
public func StickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: StickerPackPreviewControllerMode = .default, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], parentNavigationController: NavigationController? = nil, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil, actionPerformed: (([(StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction)]) -> Void)? = nil, dismissed: (() -> Void)? = nil) -> ViewController { public func StickerPackScreen(
//let stickerPacks = [mainStickerPack] context: AccountContext,
let controller = StickerPackScreenImpl(context: context, stickerPacks: stickerPacks, selectedStickerPackIndex: stickerPacks.firstIndex(of: mainStickerPack) ?? 0, parentNavigationController: parentNavigationController, sendSticker: sendSticker, actionPerformed: actionPerformed) updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
mode: StickerPackPreviewControllerMode = .default,
mainStickerPack: StickerPackReference,
stickerPacks: [StickerPackReference],
parentNavigationController: NavigationController? = nil,
sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil,
sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? = nil,
actionPerformed: (([(StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction)]) -> Void)? = nil,
dismissed: (() -> Void)? = nil) -> ViewController
{
let controller = StickerPackScreenImpl(
context: context,
stickerPacks: stickerPacks,
selectedStickerPackIndex: stickerPacks.firstIndex(of: mainStickerPack) ?? 0,
parentNavigationController: parentNavigationController,
sendSticker: sendSticker,
sendEmoji: sendEmoji,
actionPerformed: actionPerformed
)
controller.dismissed = dismissed controller.dismissed = dismissed
return controller return controller
// let controller = StickerPackPreviewController(context: context, updatedPresentationData: updatedPresentationData, stickerPack: mainStickerPack, mode: mode, parentNavigationController: parentNavigationController, actionPerformed: actionPerformed)
// controller.dismissed = dismissed
// controller.sendSticker = sendSticker
// return controller
} }

View File

@ -595,6 +595,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
dict[512535275] = { return Api.PostAddress.parse_postAddress($0) } dict[512535275] = { return Api.PostAddress.parse_postAddress($0) }
dict[1958953753] = { return Api.PremiumGiftOption.parse_premiumGiftOption($0) } dict[1958953753] = { return Api.PremiumGiftOption.parse_premiumGiftOption($0) }
dict[-1225711938] = { return Api.PremiumSubscriptionOption.parse_premiumSubscriptionOption($0) }
dict[1124062251] = { return Api.PrivacyKey.parse_privacyKeyAddedByPhone($0) } dict[1124062251] = { return Api.PrivacyKey.parse_privacyKeyAddedByPhone($0) }
dict[1343122938] = { return Api.PrivacyKey.parse_privacyKeyChatInvite($0) } dict[1343122938] = { return Api.PrivacyKey.parse_privacyKeyChatInvite($0) }
dict[1777096355] = { return Api.PrivacyKey.parse_privacyKeyForwards($0) } dict[1777096355] = { return Api.PrivacyKey.parse_privacyKeyForwards($0) }
@ -953,7 +954,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[415997816] = { return Api.help.InviteText.parse_inviteText($0) } dict[415997816] = { return Api.help.InviteText.parse_inviteText($0) }
dict[-1600596305] = { return Api.help.PassportConfig.parse_passportConfig($0) } dict[-1600596305] = { return Api.help.PassportConfig.parse_passportConfig($0) }
dict[-1078332329] = { return Api.help.PassportConfig.parse_passportConfigNotModified($0) } dict[-1078332329] = { return Api.help.PassportConfig.parse_passportConfigNotModified($0) }
dict[-1974518743] = { return Api.help.PremiumPromo.parse_premiumPromo($0) } dict[1395946908] = { return Api.help.PremiumPromo.parse_premiumPromo($0) }
dict[-1942390465] = { return Api.help.PromoData.parse_promoData($0) } dict[-1942390465] = { return Api.help.PromoData.parse_promoData($0) }
dict[-1728664459] = { return Api.help.PromoData.parse_promoDataEmpty($0) } dict[-1728664459] = { return Api.help.PromoData.parse_promoDataEmpty($0) }
dict[235081943] = { return Api.help.RecentMeUrls.parse_recentMeUrls($0) } dict[235081943] = { return Api.help.RecentMeUrls.parse_recentMeUrls($0) }
@ -1497,6 +1498,8 @@ public extension Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.PremiumGiftOption: case let _1 as Api.PremiumGiftOption:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.PremiumSubscriptionOption:
_1.serialize(buffer, boxed)
case let _1 as Api.PrivacyKey: case let _1 as Api.PrivacyKey:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.PrivacyRule: case let _1 as Api.PrivacyRule:

View File

@ -652,6 +652,62 @@ public extension Api {
} }
} }
public extension Api {
enum PremiumSubscriptionOption: TypeConstructorDescription {
case premiumSubscriptionOption(flags: Int32, months: Int32, currency: String, amount: Int64, botUrl: String, storeProduct: String?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .premiumSubscriptionOption(let flags, let months, let currency, let amount, let botUrl, let storeProduct):
if boxed {
buffer.appendInt32(-1225711938)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(months, buffer: buffer, boxed: false)
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(amount, buffer: buffer, boxed: false)
serializeString(botUrl, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(storeProduct!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .premiumSubscriptionOption(let flags, let months, let currency, let amount, let botUrl, let storeProduct):
return ("premiumSubscriptionOption", [("flags", String(describing: flags)), ("months", String(describing: months)), ("currency", String(describing: currency)), ("amount", String(describing: amount)), ("botUrl", String(describing: botUrl)), ("storeProduct", String(describing: storeProduct))])
}
}
public static func parse_premiumSubscriptionOption(_ reader: BufferReader) -> PremiumSubscriptionOption? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
var _4: Int64?
_4 = reader.readInt64()
var _5: String?
_5 = parseString(reader)
var _6: String?
if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.PremiumSubscriptionOption.premiumSubscriptionOption(flags: _1!, months: _2!, currency: _3!, amount: _4!, botUrl: _5!, storeProduct: _6)
}
else {
return nil
}
}
}
}
public extension Api { public extension Api {
enum PrivacyKey: TypeConstructorDescription { enum PrivacyKey: TypeConstructorDescription {
case privacyKeyAddedByPhone case privacyKeyAddedByPhone

View File

@ -414,13 +414,13 @@ public extension Api.help {
} }
public extension Api.help { public extension Api.help {
enum PremiumPromo: TypeConstructorDescription { enum PremiumPromo: TypeConstructorDescription {
case premiumPromo(statusText: String, statusEntities: [Api.MessageEntity], videoSections: [String], videos: [Api.Document], currency: String, monthlyAmount: Int64, users: [Api.User]) case premiumPromo(statusText: String, statusEntities: [Api.MessageEntity], videoSections: [String], videos: [Api.Document], periodOptions: [Api.PremiumSubscriptionOption], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let currency, let monthlyAmount, let users): case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let periodOptions, let users):
if boxed { if boxed {
buffer.appendInt32(-1974518743) buffer.appendInt32(1395946908)
} }
serializeString(statusText, buffer: buffer, boxed: false) serializeString(statusText, buffer: buffer, boxed: false)
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
@ -438,8 +438,11 @@ public extension Api.help {
for item in videos { for item in videos {
item.serialize(buffer, true) item.serialize(buffer, true)
} }
serializeString(currency, buffer: buffer, boxed: false) buffer.appendInt32(481674261)
serializeInt64(monthlyAmount, buffer: buffer, boxed: false) buffer.appendInt32(Int32(periodOptions.count))
for item in periodOptions {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count)) buffer.appendInt32(Int32(users.count))
for item in users { for item in users {
@ -451,8 +454,8 @@ public extension Api.help {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let currency, let monthlyAmount, let users): case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let periodOptions, let users):
return ("premiumPromo", [("statusText", String(describing: statusText)), ("statusEntities", String(describing: statusEntities)), ("videoSections", String(describing: videoSections)), ("videos", String(describing: videos)), ("currency", String(describing: currency)), ("monthlyAmount", String(describing: monthlyAmount)), ("users", String(describing: users))]) return ("premiumPromo", [("statusText", String(describing: statusText)), ("statusEntities", String(describing: statusEntities)), ("videoSections", String(describing: videoSections)), ("videos", String(describing: videos)), ("periodOptions", String(describing: periodOptions)), ("users", String(describing: users))])
} }
} }
@ -471,13 +474,13 @@ public extension Api.help {
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
} }
var _5: String? var _5: [Api.PremiumSubscriptionOption]?
_5 = parseString(reader)
var _6: Int64?
_6 = reader.readInt64()
var _7: [Api.User]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumSubscriptionOption.self)
}
var _6: [Api.User]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
@ -485,9 +488,8 @@ public extension Api.help {
let _c4 = _4 != nil let _c4 = _4 != nil
let _c5 = _5 != nil let _c5 = _5 != nil
let _c6 = _6 != nil let _c6 = _6 != nil
let _c7 = _7 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { return Api.help.PremiumPromo.premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, periodOptions: _5!, users: _6!)
return Api.help.PremiumPromo.premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, currency: _5!, monthlyAmount: _6!, users: _7!)
} }
else { else {
return nil return nil

View File

@ -5383,13 +5383,13 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.messages { public extension Api.functions.messages {
static func reportReaction(peer: Api.InputPeer, id: Int32, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func reportReaction(peer: Api.InputPeer, id: Int32, reactionPeer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1631726152) buffer.appendInt32(1063567478)
peer.serialize(buffer, true) peer.serialize(buffer, true)
serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false)
userId.serialize(buffer, true) reactionPeer.serialize(buffer, true)
return (FunctionDescription(name: "messages.reportReaction", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in return (FunctionDescription(name: "messages.reportReaction", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("reactionPeer", String(describing: reactionPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Bool? var result: Api.Bool?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -5432,14 +5432,15 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.messages { public extension Api.functions.messages {
static func requestSimpleWebView(flags: Int32, bot: Api.InputUser, url: String, themeParams: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.SimpleWebViewResult>) { static func requestSimpleWebView(flags: Int32, bot: Api.InputUser, url: String, themeParams: Api.DataJSON?, platform: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.SimpleWebViewResult>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1790652275) buffer.appendInt32(698084494)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
bot.serialize(buffer, true) bot.serialize(buffer, true)
serializeString(url, buffer: buffer, boxed: false) serializeString(url, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {themeParams!.serialize(buffer, true)} if Int(flags) & Int(1 << 0) != 0 {themeParams!.serialize(buffer, true)}
return (FunctionDescription(name: "messages.requestSimpleWebView", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("themeParams", String(describing: themeParams))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SimpleWebViewResult? in serializeString(platform, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.requestSimpleWebView", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("themeParams", String(describing: themeParams)), ("platform", String(describing: platform))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SimpleWebViewResult? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.SimpleWebViewResult? var result: Api.SimpleWebViewResult?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -5469,18 +5470,19 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.messages { public extension Api.functions.messages {
static func requestWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, replyToMsgId: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.WebViewResult>) { static func requestWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, platform: String, replyToMsgId: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.WebViewResult>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-1850648527) buffer.appendInt32(-58219204)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true) peer.serialize(buffer, true)
bot.serialize(buffer, true) bot.serialize(buffer, true)
if Int(flags) & Int(1 << 1) != 0 {serializeString(url!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeString(url!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 3) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {themeParams!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {themeParams!.serialize(buffer, true)}
serializeString(platform, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)}
return (FunctionDescription(name: "messages.requestWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("startParam", String(describing: startParam)), ("themeParams", String(describing: themeParams)), ("replyToMsgId", String(describing: replyToMsgId)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in return (FunctionDescription(name: "messages.requestWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("startParam", String(describing: startParam)), ("themeParams", String(describing: themeParams)), ("platform", String(describing: platform)), ("replyToMsgId", String(describing: replyToMsgId)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.WebViewResult? var result: Api.WebViewResult?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {

View File

@ -41,8 +41,7 @@ private func presentLiveLocationController(context: AccountContext, peerId: Peer
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, enqueueMessage: { message in }, enqueueMessage: { message in
let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start()
}, sendSticker: nil, }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in
setupTemporaryHiddenMedia: { _, _, _ in
}, chatAvatarHiddenMedia: { _, _ in }, chatAvatarHiddenMedia: { _, _ in
})) }))
} }

View File

@ -322,6 +322,31 @@ public enum AuthorizationEmailVerificationError {
case timeout case timeout
} }
public struct ChangeLoginEmailData: Equatable {
public let email: String
public let length: Int32
}
public func sendLoginEmailChangeCode(account: Account, email: String) -> Signal<ChangeLoginEmailData, AuthorizationSendEmailCodeError> {
return account.network.request(Api.functions.account.sendVerifyEmailCode(purpose: .emailVerifyPurposeLoginChange, email: email), automaticFloodWait: false)
|> `catch` { error -> Signal<Api.account.SentEmailCode, AuthorizationSendEmailCodeError> in
let errorDescription = error.errorDescription ?? ""
if errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.limitExceeded)
} else if errorDescription == "CODE_HASH_EXPIRED" || errorDescription == "PHONE_CODE_EXPIRED" {
return .fail(.codeExpired)
} else {
return .fail(.generic)
}
}
|> map { result -> ChangeLoginEmailData in
switch result {
case let .sentEmailCode(_, length):
return ChangeLoginEmailData(email: email, length: length)
}
}
}
public func sendLoginEmailCode(account: UnauthorizedAccount, email: String) -> Signal<Never, AuthorizationSendEmailCodeError> { public func sendLoginEmailCode(account: UnauthorizedAccount, email: String) -> Signal<Never, AuthorizationSendEmailCodeError> {
return account.postbox.transaction { transaction -> Signal<Never, AuthorizationSendEmailCodeError> in return account.postbox.transaction { transaction -> Signal<Never, AuthorizationSendEmailCodeError> in
if let state = transaction.getState() as? UnauthorizedAccountState { if let state = transaction.getState() as? UnauthorizedAccountState {

View File

@ -19,7 +19,7 @@ func updatePremiumPromoConfigurationOnce(postbox: Postbox, network: Network) ->
return .complete() return .complete()
} }
return postbox.transaction { transaction -> Void in return postbox.transaction { transaction -> Void in
if case let .premiumPromo(_, _, _, _, _, _, apiUsers) = result { if case let .premiumPromo(_, _, _, _, _, apiUsers) = result {
let users = apiUsers.map { TelegramUser(user: $0) } let users = apiUsers.map { TelegramUser(user: $0) }
updatePeers(transaction: transaction, peers: users, update: { current, updated -> Peer in updatePeers(transaction: transaction, peers: users, update: { current, updated -> Peer in
if let updated = updated as? TelegramUser { if let updated = updated as? TelegramUser {
@ -65,11 +65,10 @@ private func updatePremiumPromoConfiguration(transaction: Transaction, _ f: (Pre
private extension PremiumPromoConfiguration { private extension PremiumPromoConfiguration {
init(apiPremiumPromo: Api.help.PremiumPromo) { init(apiPremiumPromo: Api.help.PremiumPromo) {
switch apiPremiumPromo { switch apiPremiumPromo {
case let .premiumPromo(statusText, statusEntities, videoSections, videoFiles, currency, monthlyAmount, _): case let .premiumPromo(statusText, statusEntities, videoSections, videoFiles, options, _):
self.status = statusText self.status = statusText
self.statusEntities = messageTextEntitiesFromApiEntities(statusEntities) self.statusEntities = messageTextEntitiesFromApiEntities(statusEntities)
self.currency = currency
self.monthlyAmount = monthlyAmount
var videos: [String: TelegramMediaFile] = [:] var videos: [String: TelegramMediaFile] = [:]
for (key, document) in zip(videoSections, videoFiles) { for (key, document) in zip(videoSections, videoFiles) {
if let file = telegramMediaFileFromApiDocument(document) { if let file = telegramMediaFileFromApiDocument(document) {
@ -77,6 +76,14 @@ private extension PremiumPromoConfiguration {
} }
} }
self.videos = videos self.videos = videos
var productOptions: [PremiumProductOption] = []
for option in options {
if case let .premiumSubscriptionOption(_, months, currency, amount, botUrl, storeProduct) = option {
productOptions.append(PremiumProductOption(months: months, currency: currency, amount: amount, botUrl: botUrl, storeProductId: storeProduct))
}
}
self.premiumProductOptions = productOptions
} }
} }
} }

View File

@ -4,10 +4,9 @@ import Postbox
public struct PremiumPromoConfiguration: Codable, Equatable { public struct PremiumPromoConfiguration: Codable, Equatable {
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case status case status
case currency
case monthlyAmount
case statusEntities case statusEntities
case videos case videos
case premiumProductOptions
} }
private struct DictionaryPair: Codable { private struct DictionaryPair: Codable {
@ -34,23 +33,56 @@ public struct PremiumPromoConfiguration: Codable, Equatable {
} }
} }
public struct PremiumProductOption: Codable, Equatable {
public let months: Int32
public let currency: String
public let amount: Int64
public let botUrl: String
public let storeProductId: String?
public init(months: Int32, currency: String, amount: Int64, botUrl: String, storeProductId: String?) {
self.months = months
self.currency = currency
self.amount = amount
self.botUrl = botUrl
self.storeProductId = storeProductId
}
public init(decoder: PostboxDecoder) {
self.months = decoder.decodeInt32ForKey("months", orElse: 0)
self.currency = decoder.decodeStringForKey("currency", orElse: "")
self.amount = decoder.decodeInt64ForKey("amount", orElse: 0)
self.botUrl = decoder.decodeStringForKey("botUrl", orElse: "")
self.storeProductId = decoder.decodeOptionalStringForKey("storeProductId")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.months, forKey: "months")
encoder.encodeString(self.currency, forKey: "currency")
encoder.encodeInt64(self.amount, forKey: "amount")
encoder.encodeString(self.botUrl, forKey: "botUrl")
if let storeProductId = self.storeProductId {
encoder.encodeString(storeProductId, forKey: "storeProductId")
} else {
encoder.encodeNil(forKey: "storeProductId")
}
}
}
public var status: String public var status: String
public var statusEntities: [MessageTextEntity] public var statusEntities: [MessageTextEntity]
public var videos: [String: TelegramMediaFile] public var videos: [String: TelegramMediaFile]
public var premiumProductOptions: [PremiumProductOption]
public var currency: String
public var monthlyAmount: Int64
public static var defaultValue: PremiumPromoConfiguration { public static var defaultValue: PremiumPromoConfiguration {
return PremiumPromoConfiguration(status: "", statusEntities: [], videos: [:], currency: "", monthlyAmount: 0) return PremiumPromoConfiguration(status: "", statusEntities: [], videos: [:], premiumProductOptions: [])
} }
init(status: String, statusEntities: [MessageTextEntity], videos: [String: TelegramMediaFile], currency: String, monthlyAmount: Int64) { init(status: String, statusEntities: [MessageTextEntity], videos: [String: TelegramMediaFile], premiumProductOptions: [PremiumProductOption]) {
self.status = status self.status = status
self.currency = currency
self.monthlyAmount = monthlyAmount
self.statusEntities = statusEntities self.statusEntities = statusEntities
self.videos = videos self.videos = videos
self.premiumProductOptions = premiumProductOptions
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
@ -66,9 +98,7 @@ public struct PremiumPromoConfiguration: Codable, Equatable {
} }
self.videos = videos self.videos = videos
self.currency = try container.decode(String.self, forKey: .currency) self.premiumProductOptions = try container.decode([PremiumProductOption].self, forKey: .premiumProductOptions)
self.monthlyAmount = try container.decode(Int64.self, forKey: .monthlyAmount)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -76,13 +106,13 @@ public struct PremiumPromoConfiguration: Codable, Equatable {
try container.encode(self.status, forKey: .status) try container.encode(self.status, forKey: .status)
try container.encode(self.statusEntities, forKey: .statusEntities) try container.encode(self.statusEntities, forKey: .statusEntities)
try container.encode(self.currency, forKey: .currency)
try container.encode(self.monthlyAmount, forKey: .monthlyAmount)
var pairs: [DictionaryPair] = [] var pairs: [DictionaryPair] = []
for (key, file) in self.videos { for (key, file) in self.videos {
pairs.append(DictionaryPair(key, value: file)) pairs.append(DictionaryPair(key, value: file))
} }
try container.encode(pairs, forKey: .videos) try container.encode(pairs, forKey: .videos)
try container.encode(self.premiumProductOptions, forKey: .premiumProductOptions)
} }
} }

View File

@ -4,6 +4,12 @@ import SwiftSignalKit
import TelegramApi import TelegramApi
import MtProtoKit import MtProtoKit
#if os(macOS)
private let botWebViewPlatform = "macos"
#else
private let botWebViewPlatform = "ios"
#endif
public enum RequestSimpleWebViewError { public enum RequestSimpleWebViewError {
case generic case generic
} }
@ -22,7 +28,7 @@ func _internal_requestSimpleWebView(postbox: Postbox, network: Network, botId: P
if let _ = serializedThemeParams { if let _ = serializedThemeParams {
flags |= (1 << 0) flags |= (1 << 0)
} }
return network.request(Api.functions.messages.requestSimpleWebView(flags: flags, bot: inputUser, url: url, themeParams: serializedThemeParams)) return network.request(Api.functions.messages.requestSimpleWebView(flags: flags, bot: inputUser, url: url, themeParams: serializedThemeParams, platform: botWebViewPlatform))
|> mapError { _ -> RequestSimpleWebViewError in |> mapError { _ -> RequestSimpleWebViewError in
return .generic return .generic
} }
@ -125,7 +131,7 @@ func _internal_requestWebView(postbox: Postbox, network: Network, stateManager:
// if _ { // if _ {
// flags |= (1 << 13) // flags |= (1 << 13)
// } // }
return network.request(Api.functions.messages.requestWebView(flags: flags, peer: inputPeer, bot: inputBot, url: url, startParam: payload, themeParams: serializedThemeParams, replyToMsgId: replyToMsgId, sendAs: nil)) return network.request(Api.functions.messages.requestWebView(flags: flags, peer: inputPeer, bot: inputBot, url: url, startParam: payload, themeParams: serializedThemeParams, platform: botWebViewPlatform, replyToMsgId: replyToMsgId, sendAs: nil))
|> mapError { _ -> RequestWebViewError in |> mapError { _ -> RequestWebViewError in
return .generic return .generic
} }

View File

@ -171,11 +171,11 @@ func _internal_reportPeerMessages(account: Account, messageIds: [MessageId], rea
} }
func _internal_reportPeerReaction(account: Account, authorId: PeerId, messageId: MessageId) -> Signal<Never, NoError> { func _internal_reportPeerReaction(account: Account, authorId: PeerId, messageId: MessageId) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> (Api.InputPeer, Api.InputUser)? in return account.postbox.transaction { transaction -> (Api.InputPeer, Api.InputPeer)? in
guard let peer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else { guard let peer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else {
return nil return nil
} }
guard let author = transaction.getPeer(authorId).flatMap(apiInputUser) else { guard let author = transaction.getPeer(authorId).flatMap(apiInputPeer) else {
return nil return nil
} }
return (peer, author) return (peer, author)
@ -184,7 +184,7 @@ func _internal_reportPeerReaction(account: Account, authorId: PeerId, messageId:
guard let (inputPeer, inputUser) = inputData else { guard let (inputPeer, inputUser) = inputData else {
return .complete() return .complete()
} }
return account.network.request(Api.functions.messages.reportReaction(peer: inputPeer, id: messageId.id, userId: inputUser)) return account.network.request(Api.functions.messages.reportReaction(peer: inputPeer, id: messageId.id, reactionPeer: inputUser))
|> `catch` { _ -> Signal<Api.Bool, NoError> in |> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse) return .single(.boolFalse)
} }

View File

@ -163,6 +163,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case sharedMediaScrollingTooltip = 29 case sharedMediaScrollingTooltip = 29
case sharedMediaFastScrollingTooltip = 30 case sharedMediaFastScrollingTooltip = 30
case forcedPasswordSetup = 31 case forcedPasswordSetup = 31
case emojiTooltip = 32
var key: ValueBoxKey { var key: ValueBoxKey {
let v = ValueBoxKey(length: 4) let v = ValueBoxKey(length: 4)
@ -339,6 +340,10 @@ private struct ApplicationSpecificNoticeKeys {
static func sharedMediaFastScrollingTooltip() -> NoticeEntryKey { static func sharedMediaFastScrollingTooltip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sharedMediaFastScrollingTooltip.key) return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sharedMediaFastScrollingTooltip.key)
} }
static func emojiTooltip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.emojiTooltip.key)
}
} }
public struct ApplicationSpecificNotice { public struct ApplicationSpecificNotice {
@ -953,6 +958,30 @@ public struct ApplicationSpecificNotice {
} }
} }
public static func getEmojiTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.emojiTooltip())?.get(ApplicationSpecificCounterNotice.self) {
return value.value
} else {
return 0
}
}
}
public static func incrementEmojiTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int32 = 1) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
var currentValue: Int32 = 0
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.emojiTooltip())?.get(ApplicationSpecificCounterNotice.self) {
currentValue = value.value
}
currentValue += count
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) {
transaction.setNotice(ApplicationSpecificNoticeKeys.emojiTooltip(), entry)
}
}
}
public static func dismissedTrendingStickerPacks(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<[Int64]?, NoError> { public static func dismissedTrendingStickerPacks(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<[Int64]?, NoError> {
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedTrendingStickerPacks()) return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedTrendingStickerPacks())
|> map { view -> [Int64]? in |> map { view -> [Int64]? in

View File

@ -2160,7 +2160,7 @@ public final class EmojiPagerContentComponent: Component {
case featured case featured
} }
let item: Item public let item: Item
private let content: ItemContent private let content: ItemContent
private let placeholderColor: UIColor private let placeholderColor: UIColor

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "LoginEmail.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,122 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.345098 0.337255 0.839216 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 5.201416 5.201416 cm
1.000000 1.000000 1.000000 scn
0.000000 9.798596 m
0.000000 15.210198 4.386970 19.597168 9.798572 19.597168 c
15.210174 19.597168 19.597141 15.210198 19.597141 9.798596 c
19.597141 7.536692 l
19.597141 5.872736 18.248240 4.523834 16.584286 4.523834 c
15.334453 4.523834 14.262368 5.284863 13.806146 6.368805 c
12.838721 5.239468 11.402221 4.523834 9.798572 4.523834 c
6.885401 4.523834 4.523810 6.885427 4.523810 9.798597 c
4.523810 12.711767 6.885401 15.073359 9.798572 15.073359 c
11.278581 15.073359 12.616220 14.463821 13.574155 13.482087 c
13.606840 13.866659 13.929350 14.168596 14.322381 14.168596 c
14.737121 14.168596 15.073334 13.832384 15.073334 13.417645 c
15.073334 9.798597 l
15.073334 7.536693 l
15.073334 6.702216 15.749810 6.025740 16.584286 6.025740 c
17.418760 6.025740 18.095238 6.702215 18.095238 7.536692 c
18.095238 9.798596 l
18.095238 14.380720 14.380694 18.095263 9.798572 18.095263 c
5.216448 18.095263 1.501905 14.380720 1.501905 9.798596 c
1.501905 9.710638 1.503271 9.623017 1.505982 9.535753 c
1.644797 5.075261 5.304389 1.501930 9.798572 1.501930 c
11.100951 1.501930 12.331239 1.801481 13.426208 2.334747 c
13.799078 2.516340 14.248561 2.361280 14.430154 1.988409 c
14.611748 1.615540 14.456687 1.166056 14.083817 0.984463 c
12.788459 0.353603 11.333742 0.000027 9.798572 0.000027 c
4.386970 0.000027 0.000000 4.386994 0.000000 9.798596 c
h
9.798572 13.571454 m
11.882263 13.571454 13.571428 11.882288 13.571428 9.798597 c
13.571428 7.714905 11.882263 6.025740 9.798572 6.025740 c
7.714880 6.025740 6.025714 7.714905 6.025714 9.798597 c
6.025714 11.882288 7.714880 13.571454 9.798572 13.571454 c
h
f*
n
Q
endstream
endobj
3 0 obj
2642
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000002732 00000 n
0000002755 00000 n
0000002928 00000 n
0000003002 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
3061
%%EOF

View File

@ -25,6 +25,7 @@ import AppLock
import AccountUtils import AccountUtils
import ContextUI import ContextUI
import TelegramCallsUI import TelegramCallsUI
import AuthorizationUI
final class UnauthorizedApplicationContext { final class UnauthorizedApplicationContext {
let sharedContext: SharedAccountContextImpl let sharedContext: SharedAccountContextImpl
@ -48,6 +49,7 @@ final class UnauthorizedApplicationContext {
self.rootController = AuthorizationSequenceController(sharedContext: sharedContext, account: account, otherAccountPhoneNumbers: otherAccountPhoneNumbers, presentationData: presentationData, openUrl: sharedContext.applicationBindings.openUrl, apiId: apiId, apiHash: apiHash, authorizationCompleted: { self.rootController = AuthorizationSequenceController(sharedContext: sharedContext, account: account, otherAccountPhoneNumbers: otherAccountPhoneNumbers, presentationData: presentationData, openUrl: sharedContext.applicationBindings.openUrl, apiId: apiId, apiHash: apiHash, authorizationCompleted: {
authorizationCompleted?() authorizationCompleted?()
}) })
(self.rootController as NavigationController).statusBarHost = sharedContext.mainWindow?.statusBarHost
authorizationCompleted = { [weak self] in authorizationCompleted = { [weak self] in
self?.authorizationCompleted = true self?.authorizationCompleted = true

View File

@ -407,6 +407,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var raiseToListen: RaiseToListenManager? private var raiseToListen: RaiseToListenManager?
private var voicePlaylistDidEndTimestamp: Double = 0.0 private var voicePlaylistDidEndTimestamp: Double = 0.0
private weak var emojiTooltipController: TooltipController?
private weak var sendingOptionsTooltipController: TooltipController? private weak var sendingOptionsTooltipController: TooltipController?
private weak var searchResultsTooltipController: TooltipController? private weak var searchResultsTooltipController: TooltipController?
private weak var messageTooltipController: TooltipController? private weak var messageTooltipController: TooltipController?
@ -828,6 +829,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self?.sendMessages([message]) self?.sendMessages([message])
}, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference, sourceNode, sourceRect in }, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference, sourceNode, sourceRect in
return self?.controllerInteraction?.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil) ?? false return self?.controllerInteraction?.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil) ?? false
} : nil, sendEmoji: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { text, attribute in
self?.controllerInteraction?.sendEmoji(text, attribute)
} : nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in } : nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in
if let strongSelf = self { if let strongSelf = self {
strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { entry in strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { entry in
@ -1949,6 +1952,29 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.sendMessages(transformedMessages) strongSelf.sendMessages(transformedMessages)
} }
return true return true
}, sendEmoji: { [weak self] text, attribute in
if let strongSelf = self {
strongSelf.interfaceInteraction?.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: attribute]))
strongSelf.updateChatPresentationInterfaceState(interactive: true, { state in
return state.updatedInputMode({ _ in
return .text
})
})
let _ = (ApplicationSpecificNotice.getEmojiTooltip(accountManager: strongSelf.context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { count in
guard let strongSelf = self else {
return
}
if count < 2 {
let _ = ApplicationSpecificNotice.incrementEmojiTooltip(accountManager: strongSelf.context.sharedContext.accountManager).start()
Queue.mainQueue().after(0.5, {
strongSelf.displayEmojiTooltip()
})
}
})
}
}, sendGif: { [weak self] fileReference, sourceView, sourceRect, silentPosting, schedule in }, sendGif: { [weak self] fileReference, sourceView, sourceRect, silentPosting, schedule in
if let strongSelf = self { if let strongSelf = self {
if let _ = strongSelf.presentationInterfaceState.slowmodeState, strongSelf.presentationInterfaceState.subject != .scheduledMessages { if let _ = strongSelf.presentationInterfaceState.slowmodeState, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
@ -15953,6 +15979,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})) }))
} }
private func displayEmojiTooltip() {
guard let rect = self.chatDisplayNode.frameForEmojiButton(), self.effectiveNavigationController?.topViewController === self else {
return
}
self.emojiTooltipController?.dismiss()
let tooltipController = TooltipController(content: .text(self.presentationData.strings.Conversation_EmojiTooltip), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 3.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 2.0)
self.emojiTooltipController = tooltipController
tooltipController.dismissed = { [weak self, weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.emojiTooltipController === tooltipController {
strongSelf.emojiTooltipController = nil
}
}
self.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
if let strongSelf = self {
return (strongSelf.chatDisplayNode, rect.offsetBy(dx: 0.0, dy: -3.0))
}
return nil
}))
}
private func displayChecksTooltip() { private func displayChecksTooltip() {
self.checksTooltipController?.dismiss() self.checksTooltipController?.dismiss()
@ -15993,6 +16039,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
private func dismissAllTooltips() { private func dismissAllTooltips() {
self.emojiTooltipController?.dismiss()
self.sendingOptionsTooltipController?.dismiss() self.sendingOptionsTooltipController?.dismiss()
self.searchResultsTooltipController?.dismiss() self.searchResultsTooltipController?.dismiss()
self.messageTooltipController?.dismiss() self.messageTooltipController?.dismiss()
@ -16750,7 +16797,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.chatDisplayNode.dismissTextInput() self.chatDisplayNode.dismissTextInput()
let presentationData = self.presentationData let presentationData = self.presentationData
let controller = StickerPackScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mainStickerPack: packReference, stickerPacks: Array(references), parentNavigationController: self.effectiveNavigationController, actionPerformed: { [weak self] actions in let controller = StickerPackScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mainStickerPack: packReference, stickerPacks: Array(references), parentNavigationController: self.effectiveNavigationController, sendEmoji: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] text, attribute in
if let strongSelf = self {
strongSelf.controllerInteraction?.sendEmoji(text, attribute)
}
} : nil, actionPerformed: { [weak self] actions in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -13,6 +13,7 @@ import ChatInterfaceState
import UndoUI import UndoUI
import TelegramPresentationData import TelegramPresentationData
import ChatPresentationInterfaceState import ChatPresentationInterfaceState
import TextFormat
struct ChatInterfaceHighlightedState: Equatable { struct ChatInterfaceHighlightedState: Equatable {
let messageStableId: UInt32 let messageStableId: UInt32
@ -74,6 +75,7 @@ public final class ChatControllerInteraction {
let sendCurrentMessage: (Bool) -> Void let sendCurrentMessage: (Bool) -> Void
let sendMessage: (String) -> Void let sendMessage: (String) -> Void
let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Bool let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Bool
let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute) -> Void
let sendGif: (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool let sendGif: (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool
let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool) -> Bool let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool) -> Bool
let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void
@ -179,6 +181,7 @@ public final class ChatControllerInteraction {
sendCurrentMessage: @escaping (Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void,
sendMessage: @escaping (String) -> Void, sendMessage: @escaping (String) -> Void,
sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Bool, sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Bool,
sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute) -> Void,
sendGif: @escaping (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool, sendGif: @escaping (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool,
sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool) -> Bool,
requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void,
@ -267,6 +270,7 @@ public final class ChatControllerInteraction {
self.sendCurrentMessage = sendCurrentMessage self.sendCurrentMessage = sendCurrentMessage
self.sendMessage = sendMessage self.sendMessage = sendMessage
self.sendSticker = sendSticker self.sendSticker = sendSticker
self.sendEmoji = sendEmoji
self.sendGif = sendGif self.sendGif = sendGif
self.sendBotContextResultAsGif = sendBotContextResultAsGif self.sendBotContextResultAsGif = sendBotContextResultAsGif
self.requestMessageActionCallback = requestMessageActionCallback self.requestMessageActionCallback = requestMessageActionCallback

View File

@ -2563,6 +2563,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
return nil return nil
} }
func frameForEmojiButton() -> CGRect? {
if let textInputPanelNode = self.textInputPanelNode, self.inputPanelNode === textInputPanelNode {
return textInputPanelNode.frameForEmojiButton().flatMap {
return $0.offsetBy(dx: textInputPanelNode.frame.minX, dy: textInputPanelNode.frame.minY)
}
}
return nil
}
var isTextInputPanelActive: Bool { var isTextInputPanelActive: Bool {
return self.inputPanelNode is ChatTextInputPanelNode return self.inputPanelNode is ChatTextInputPanelNode
} }

View File

@ -225,7 +225,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, callPeer: { peerId, isVideo in }, callPeer: { peerId, isVideo in
self?.controllerInteraction?.callPeer(peerId, isVideo) self?.controllerInteraction?.callPeer(peerId, isVideo)
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { signal, media in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { signal, media in
if let strongSelf = self { if let strongSelf = self {
strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { messageId in strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { messageId in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
@ -261,7 +261,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, activateMessagePinch: { _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _ in return false }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
self?.openUrl(url) self?.openUrl(url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {

View File

@ -3468,6 +3468,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
return nil return nil
} }
func frameForEmojiButton() -> CGRect? {
for (item, button) in self.accessoryItemButtons {
if case let .input(_, inputMode) = item, case .emoji = inputMode {
return button.frame.insetBy(dx: 0.0, dy: 6.0)
}
}
return nil
}
func makeSnapshotForTransition() -> ChatMessageTransitionNode.Source.TextInput? { func makeSnapshotForTransition() -> ChatMessageTransitionNode.Source.TextInput? {
guard let backgroundImage = self.transparentTextInputBackgroundImage else { guard let backgroundImage = self.transparentTextInputBackgroundImage else {
return nil return nil

View File

@ -112,7 +112,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
return false }, openPeer: { _, _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in return false }, openPeer: { _, _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect, _ in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect, _ in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in }, presentController: { _, _ in
}, presentControllerInCurrent: { _, _ in }, presentControllerInCurrent: { _, _ in
}, navigationController: { }, navigationController: {

View File

@ -78,7 +78,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
params.navigationController?.pushViewController(controller) params.navigationController?.pushViewController(controller)
return true return true
case let .stickerPack(reference): case let .stickerPack(reference):
let controller = StickerPackScreen(context: params.context, updatedPresentationData: params.updatedPresentationData, mainStickerPack: reference, stickerPacks: [reference], parentNavigationController: params.navigationController, sendSticker: params.sendSticker, actionPerformed: { actions in let controller = StickerPackScreen(context: params.context, updatedPresentationData: params.updatedPresentationData, mainStickerPack: reference, stickerPacks: [reference], parentNavigationController: params.navigationController, sendSticker: params.sendSticker, sendEmoji: params.sendEmoji, actionPerformed: { actions in
let presentationData = params.context.sharedContext.currentPresentationData.with { $0 } let presentationData = params.context.sharedContext.currentPresentationData.with { $0 }
if actions.count > 1, let first = actions.first { if actions.count > 1, let first = actions.first {

View File

@ -82,6 +82,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, sendMessage: { _ in }, sendMessage: { _ in
}, sendSticker: { _, _, _, _, _, _, _, _ in }, sendSticker: { _, _, _, _, _, _, _, _ in
return false return false
}, sendEmoji: { _, _ in
}, sendGif: { _, _, _, _, _ in }, sendGif: { _, _, _, _, _ in
return false return false
}, sendBotContextResultAsGif: { _, _, _, _, _ in }, sendBotContextResultAsGif: { _, _, _, _, _ in
@ -282,7 +283,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, _, loadMore) = location { if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, _, loadMore) = location {
playlistLocation = .custom(messages: messages, at: id, loadMore: loadMore) playlistLocation = .custom(messages: messages, at: id, loadMore: loadMore)
} }
return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation)) return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation))
} }
return false return false
} }

View File

@ -1759,8 +1759,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
private let archivedPacks = Promise<[ArchivedStickerPackItem]?>() private let archivedPacks = Promise<[ArchivedStickerPackItem]?>()
private let blockedPeers = Promise<BlockedPeersContext?>(nil) private let blockedPeers = Promise<BlockedPeersContext?>(nil)
private let hasTwoStepAuth = Promise<Bool?>(nil) private let hasTwoStepAuth = Promise<Bool?>(nil)
private let twoStepAuthData = Promise<TwoStepVerificationAccessConfiguration?>(nil) private let twoStepAccessConfiguration = Promise<TwoStepVerificationAccessConfiguration?>(nil)
private let hasPassport = Promise<Bool>(false) private let twoStepAuthData = Promise<TwoStepAuthData?>(nil)
private let supportPeerDisposable = MetaDisposable() private let supportPeerDisposable = MetaDisposable()
private let tipsPeerDisposable = MetaDisposable() private let tipsPeerDisposable = MetaDisposable()
private let cachedFaq = Promise<ResolvedUrl?>(nil) private let cachedFaq = Promise<ResolvedUrl?>(nil)
@ -2271,6 +2271,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, sendMessage: { _ in }, sendMessage: { _ in
}, sendSticker: { _, _, _, _, _, _, _, _ in }, sendSticker: { _, _, _, _, _, _, _, _ in
return false return false
}, sendEmoji: { _, _ in
}, sendGif: { _, _, _, _, _ in }, sendGif: { _, _, _, _, _ in
return false return false
}, sendBotContextResultAsGif: { _, _, _, _, _ in }, sendBotContextResultAsGif: { _, _, _, _, _ in
@ -3030,21 +3031,28 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
)) ))
self.privacySettings.set(.single(nil) |> then(context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init))) self.privacySettings.set(.single(nil) |> then(context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init)))
self.archivedPacks.set(.single(nil) |> then(context.engine.stickers.archivedStickerPacks() |> map(Optional.init))) self.archivedPacks.set(.single(nil) |> then(context.engine.stickers.archivedStickerPacks() |> map(Optional.init)))
self.twoStepAuthData.set(.single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() self.twoStepAccessConfiguration.set(.single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration()
|> map { value -> TwoStepVerificationAccessConfiguration? in |> map { value -> TwoStepVerificationAccessConfiguration? in
return TwoStepVerificationAccessConfiguration(configuration: value, password: nil) return TwoStepVerificationAccessConfiguration(configuration: value, password: nil)
})) }))
self.hasPassport.set(.single(false) |> then(context.engine.auth.twoStepAuthData()
|> map { value -> Bool in self.twoStepAuthData.set(.single(nil)
return value.hasSecretValues |> then(
context.engine.auth.twoStepAuthData()
|> map(Optional.init)
|> `catch` { _ -> Signal<TwoStepAuthData?, NoError> in
return .single(nil)
}
))
let hasPassport = self.twoStepAuthData.get()
|> map { data -> Bool in
return data?.hasSecretValues ?? false
} }
|> `catch` { _ -> Signal<Bool, NoError> in
return .single(false)
}))
self.cachedFaq.set(.single(nil) |> then(cachedFaqInstantPage(context: self.context) |> map(Optional.init))) self.cachedFaq.set(.single(nil) |> then(cachedFaqInstantPage(context: self.context) |> map(Optional.init)))
screenData = peerInfoScreenSettingsData(context: context, peerId: peerId, accountsAndPeers: self.accountsAndPeers.get(), activeSessionsContextAndCount: self.activeSessionsContextAndCount.get(), notificationExceptions: self.notificationExceptions.get(), privacySettings: self.privacySettings.get(), archivedStickerPacks: self.archivedPacks.get(), hasPassport: self.hasPassport.get()) screenData = peerInfoScreenSettingsData(context: context, peerId: peerId, accountsAndPeers: self.accountsAndPeers.get(), activeSessionsContextAndCount: self.activeSessionsContextAndCount.get(), notificationExceptions: self.notificationExceptions.get(), privacySettings: self.privacySettings.get(), archivedStickerPacks: self.archivedPacks.get(), hasPassport: hasPassport)
self.headerNode.displayCopyContextMenu = { [weak self] node, copyPhone, copyUsername in self.headerNode.displayCopyContextMenu = { [weak self] node, copyPhone, copyUsername in
@ -3404,7 +3412,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, callPeer: { peerId, isVideo in }, callPeer: { peerId, isVideo in
//self?.controllerInteraction?.callPeer(peerId) //self?.controllerInteraction?.callPeer(peerId)
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, actionInteraction: GalleryControllerActionInteraction(openUrl: { [weak self] url, concealed in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, actionInteraction: GalleryControllerActionInteraction(openUrl: { [weak self] url, concealed in
if let strongSelf = self { if let strongSelf = self {
strongSelf.openUrl(url: url, concealed: false, external: false) strongSelf.openUrl(url: url, concealed: false, external: false)
} }
@ -6369,13 +6377,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak self] blockedPeersContext, hasTwoStepAuth in |> deliverOnMainQueue).start(next: { [weak self] blockedPeersContext, hasTwoStepAuth in
if let strongSelf = self { if let strongSelf = self {
let loginEmailPattern = strongSelf.twoStepAuthData.get() |> map { data -> String? in
return data?.loginEmailPattern
}
push(privacyAndSecurityController(context: strongSelf.context, initialSettings: settings.privacySettings, updatedSettings: { [weak self] settings in push(privacyAndSecurityController(context: strongSelf.context, initialSettings: settings.privacySettings, updatedSettings: { [weak self] settings in
self?.privacySettings.set(.single(settings)) self?.privacySettings.set(.single(settings))
}, updatedBlockedPeers: { [weak self] blockedPeersContext in }, updatedBlockedPeers: { [weak self] blockedPeersContext in
self?.blockedPeers.set(.single(blockedPeersContext)) self?.blockedPeers.set(.single(blockedPeersContext))
}, updatedHasTwoStepAuth: { [weak self] hasTwoStepAuthValue in }, updatedHasTwoStepAuth: { [weak self] hasTwoStepAuthValue in
self?.hasTwoStepAuth.set(.single(hasTwoStepAuthValue)) self?.hasTwoStepAuth.set(.single(hasTwoStepAuthValue))
}, focusOnItemTag: nil, activeSessionsContext: settings.activeSessionsContext, webSessionsContext: settings.webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth)) }, focusOnItemTag: nil, activeSessionsContext: settings.activeSessionsContext, webSessionsContext: settings.webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth, loginEmailPattern: loginEmailPattern))
} }
}) })
} }
@ -6883,7 +6894,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
} }
}) })
} }
}, resolvedFaqUrl: self.cachedFaq.get(), exceptionsList: .single(settings.notificationExceptions), archivedStickerPacks: .single(settings.archivedStickerPacks), privacySettings: .single(settings.privacySettings), hasTwoStepAuth: self.hasTwoStepAuth.get(), twoStepAuthData: self.twoStepAuthData.get(), activeSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.0 }, webSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.2 }), cancel: { [weak self] in }, resolvedFaqUrl: self.cachedFaq.get(), exceptionsList: .single(settings.notificationExceptions), archivedStickerPacks: .single(settings.archivedStickerPacks), privacySettings: .single(settings.privacySettings), hasTwoStepAuth: self.hasTwoStepAuth.get(), twoStepAuthData: self.twoStepAccessConfiguration.get(), activeSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.0 }, webSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.2 }), cancel: { [weak self] in
self?.deactivateSearch() self?.deactivateSearch()
}) })
} }

View File

@ -1281,7 +1281,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
tapMessage?(message) tapMessage?(message)
}, clickThroughMessage: { }, clickThroughMessage: {
clickThroughMessage?() clickThroughMessage?()
}, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _ in return false }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in
return false return false
}, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in }, presentController: { _, _ in
@ -1510,6 +1510,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController { public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController {
return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, parentNavigationController: parentNavigationController, sendSticker: sendSticker) return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, parentNavigationController: parentNavigationController, sendSticker: sendSticker)
} }
public func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController {
return proxySettingsController(accountManager: sharedContext.accountManager, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData)
}
} }
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? { private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {