mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +00:00
Various improvements
This commit is contained in:
parent
7f2ab1788f
commit
bb06841df3
@ -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.GiftSubscription" = "Gift Subscription for %@";
|
||||
|
||||
"Premium.Gift.PricePerMonth" = "%@ / month";
|
||||
|
||||
"Premium.Gift.Months_1" = "%@ Month";
|
||||
"Premium.Gift.Months_any" = "%@ Months";
|
||||
|
||||
@ -7986,15 +7984,23 @@ Sorry for the inconvenience.";
|
||||
"Login.EnterCodeSMSTitle" = "Enter Code";
|
||||
"Login.EnterCodeSMSText" = "We've sent and SMS with an activation code to your phone **%@**.";
|
||||
"Login.SendCodeAsSMS" = "Send the code as an SMS";
|
||||
|
||||
"Login.EnterCodeTelegramTitle" = "Enter Code";
|
||||
"Login.EnterCodeTelegramText" = "We've sent the code to the **Telegram app** for %@ on your other device.";
|
||||
|
||||
"Login.AddEmailTitle" = "Add Email";
|
||||
"Login.AddEmailText" = "Please enter your valid email address to protect your account.";
|
||||
"Login.AddEmailPlaceholder" = "Enter your email";
|
||||
|
||||
"Login.EnterCodeEmailTitle" = "Check 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.";
|
||||
|
||||
"PrivacySettings.LoginEmail" = "Login Email";
|
||||
|
||||
"Conversation.EmojiCopied" = "Emoji copied to clipboard";
|
||||
|
||||
"Conversation.EmojiTooltip" = "Tap here to choose more emoji";
|
||||
|
||||
"Premium.PricePerMonth" = "%@/month";
|
||||
"Premium.PricePerYear" = "%@/year";
|
||||
|
@ -23,6 +23,7 @@ swift_library(
|
||||
"//submodules/MeshAnimationCache:MeshAnimationCache",
|
||||
"//submodules/Utils/RangeSet:RangeSet",
|
||||
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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 makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController
|
||||
|
||||
func navigateToCurrentCall()
|
||||
var hasOngoingCall: ValuePromise<Bool> { get }
|
||||
var immediateHasOngoingCall: Bool { get }
|
||||
|
@ -7,6 +7,7 @@ import Display
|
||||
import AsyncDisplayKit
|
||||
import UniversalMediaPlayer
|
||||
import TelegramPresentationData
|
||||
import TextFormat
|
||||
|
||||
public enum ChatControllerInteractionOpenMessageMode {
|
||||
case `default`
|
||||
@ -37,6 +38,7 @@ public final class OpenChatMessageParams {
|
||||
public let callPeer: (PeerId, Bool) -> Void
|
||||
public let enqueueMessage: (EnqueueMessage) -> Void
|
||||
public let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
|
||||
public let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?
|
||||
public let setupTemporaryHiddenMedia: (Signal<Any?, NoError>, Int, Media) -> Void
|
||||
public let chatAvatarHiddenMedia: (Signal<MessageId?, NoError>, Media) -> Void
|
||||
public let actionInteraction: GalleryControllerActionInteraction?
|
||||
@ -64,6 +66,7 @@ public final class OpenChatMessageParams {
|
||||
callPeer: @escaping (PeerId, Bool) -> Void,
|
||||
enqueueMessage: @escaping (EnqueueMessage) -> Void,
|
||||
sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?,
|
||||
sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?,
|
||||
setupTemporaryHiddenMedia: @escaping (Signal<Any?, NoError>, Int, Media) -> Void,
|
||||
chatAvatarHiddenMedia: @escaping (Signal<MessageId?, NoError>, Media) -> Void,
|
||||
actionInteraction: GalleryControllerActionInteraction? = nil,
|
||||
@ -90,6 +93,7 @@ public final class OpenChatMessageParams {
|
||||
self.callPeer = callPeer
|
||||
self.enqueueMessage = enqueueMessage
|
||||
self.sendSticker = sendSticker
|
||||
self.sendEmoji = sendEmoji
|
||||
self.setupTemporaryHiddenMedia = setupTemporaryHiddenMedia
|
||||
self.chatAvatarHiddenMedia = chatAvatarHiddenMedia
|
||||
self.actionInteraction = actionInteraction
|
||||
|
@ -12,10 +12,32 @@ swift_library(
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/Markdown:Markdown",
|
||||
"//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:public",
|
||||
|
@ -4,7 +4,7 @@ import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import AuthorizationUI
|
||||
import AuthorizationUtils
|
||||
|
||||
private func timerValueString(days: Int32, hours: Int32, minutes: Int32, color: UIColor, strings: PresentationStrings) -> NSAttributedString {
|
||||
var daysString = ""
|
@ -6,7 +6,7 @@ import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import ProgressNavigationButtonNode
|
||||
|
||||
final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
public final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
private var controllerNode: AuthorizationSequenceCodeEntryControllerNode {
|
||||
return self.displayNode as! AuthorizationSequenceCodeEntryControllerNode
|
||||
}
|
||||
@ -15,20 +15,20 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
private let theme: PresentationTheme
|
||||
private let openUrl: (String) -> Void
|
||||
|
||||
var loginWithCode: ((String) -> Void)?
|
||||
var signInWithApple: (() -> Void)?
|
||||
public var loginWithCode: ((String) -> Void)?
|
||||
public var signInWithApple: (() -> Void)?
|
||||
|
||||
var reset: (() -> Void)?
|
||||
var requestNextOption: (() -> Void)?
|
||||
|
||||
var data: (String, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?)?
|
||||
var data: (String, String?, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?)?
|
||||
var termsOfService: (UnauthorizedAccountTermsOfService, Bool)?
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
private var appleSignInAllowed = false
|
||||
|
||||
var inProgress: Bool = false {
|
||||
public var inProgress: Bool = false {
|
||||
didSet {
|
||||
// if self.inProgress {
|
||||
// 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.theme = presentationData.theme
|
||||
self.openUrl = openUrl
|
||||
@ -96,16 +96,16 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
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
|
||||
if case let .email(_, _, _, appleSignInAllowedValue, _) = codeType {
|
||||
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)
|
||||
|
||||
self.controllerNode.activateInput()
|
||||
@ -115,19 +115,19 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
self.controllerNode.resetCode()
|
||||
}
|
||||
|
||||
func animateSuccess() {
|
||||
public func animateSuccess() {
|
||||
self.controllerNode.animateSuccess()
|
||||
}
|
||||
|
||||
func animateError(text: String) {
|
||||
public func animateError(text: String) {
|
||||
self.hapticFeedback.error()
|
||||
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
|
||||
if self.data?.0 != number || self.data?.1 != codeType || self.data?.2 != nextType || self.data?.3 != timeout {
|
||||
self.data = (number, codeType, nextType, timeout)
|
||||
if self.data?.0 != number || self.data?.1 != email || self.data?.2 != codeType || self.data?.3 != nextType || self.data?.4 != timeout {
|
||||
self.data = (number, email, codeType, nextType, timeout)
|
||||
|
||||
var appleSignInAllowed = false
|
||||
if case let .email(_, _, _, appleSignInAllowedValue, _) = codeType {
|
||||
@ -135,20 +135,20 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
@objc func nextPressed() {
|
||||
guard let (_, type, _, _) = self.data else {
|
||||
@objc private func nextPressed() {
|
||||
guard let (_, _, type, _, _) = self.data else {
|
||||
return
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import TextFormat
|
||||
import AuthorizationUI
|
||||
import AuthenticationServices
|
||||
import CodeInputView
|
||||
import PhoneNumberFormat
|
||||
@ -14,6 +13,7 @@ import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import SolidRoundedButtonNode
|
||||
import InvisibleInkDustNode
|
||||
import AuthorizationUtils
|
||||
|
||||
final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private let strings: PresentationStrings
|
||||
@ -54,6 +54,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
}
|
||||
}
|
||||
|
||||
var email: String?
|
||||
|
||||
var currentCode: String {
|
||||
return self.codeInputView.text
|
||||
}
|
||||
@ -202,9 +204,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
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.phoneNumber = number
|
||||
self.email = email
|
||||
|
||||
var appleSignInAllowed = appleSignInAllowed
|
||||
if #available(iOS 13.0, *) {
|
||||
@ -213,7 +216,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
}
|
||||
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 {
|
||||
self.currentOptionInfoNode.attributedText = NSAttributedString(string: self.strings.Login_CodePhonePatternInfoText, font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
|
||||
} else {
|
@ -12,7 +12,6 @@ import TelegramPresentationData
|
||||
import TextFormat
|
||||
import AccountContext
|
||||
import CountrySelectionUI
|
||||
import SettingsUI
|
||||
import PhoneNumberFormat
|
||||
import LegacyComponents
|
||||
import LegacyMediaPickerUI
|
||||
@ -247,7 +246,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
|
||||
guard let strongSelf = self, let controller = controller else {
|
||||
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))
|
||||
@ -260,11 +259,11 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
|
||||
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?
|
||||
for c in self.viewControllers {
|
||||
if let c = c as? AuthorizationSequenceCodeEntryController {
|
||||
if c.data?.1 == type {
|
||||
if c.data?.2 == type {
|
||||
currentController = c
|
||||
}
|
||||
break
|
||||
@ -502,11 +501,14 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
|
||||
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
|
||||
}
|
||||
|
||||
private var signInWithAppleSetup = false
|
||||
private var appleSignInAllowed = false
|
||||
private var currentEmail: String?
|
||||
|
||||
private func emailSetupController(number: String, appleSignInAllowed: Bool) -> AuthorizationSequenceEmailEntryController {
|
||||
var currentController: AuthorizationSequenceEmailEntryController?
|
||||
for c in self.viewControllers {
|
||||
@ -519,7 +521,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
|
||||
if let currentController = currentController {
|
||||
controller = currentController
|
||||
} 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 {
|
||||
return
|
||||
}
|
||||
@ -536,9 +538,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
|
||||
controller?.inProgress = true
|
||||
|
||||
strongSelf.actionDisposable.set((sendLoginEmailCode(account: strongSelf.account, email: email)
|
||||
|> deliverOnMainQueue).start(next: { result in
|
||||
controller?.inProgress = false
|
||||
}, error: { error in
|
||||
|> deliverOnMainQueue).start(error: { error in
|
||||
if let strongSelf = self, let controller = controller {
|
||||
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))
|
||||
}
|
||||
}, completed: { [weak self] in
|
||||
controller?.inProgress = false
|
||||
|
||||
if let strongSelf = self {
|
||||
strongSelf.currentEmail = email
|
||||
}
|
||||
}))
|
||||
}
|
||||
controller.signInWithApple = { [weak self] in
|
||||
@ -1017,9 +1023,13 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
|
||||
}
|
||||
controllers.append(self.phoneEntryController(countryCode: defaultCountryCode(), number: "", splashController: nil))
|
||||
if case let .emailSetupRequired(appleSignInAllowed) = type {
|
||||
self.appleSignInAllowed = appleSignInAllowed
|
||||
controllers.append(self.emailSetupController(number: number, appleSignInAllowed: appleSignInAllowed))
|
||||
} 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)
|
||||
case let .passwordEntry(hint, _, _, suggestReset, syncContacts):
|
@ -5,34 +5,42 @@ import AsyncDisplayKit
|
||||
import TelegramPresentationData
|
||||
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 {
|
||||
return self.displayNode as! AuthorizationSequenceEmailEntryControllerNode
|
||||
}
|
||||
|
||||
private let presentationData: PresentationData
|
||||
|
||||
var proceedWithEmail: ((String) -> Void)?
|
||||
var signInWithApple: (() -> Void)?
|
||||
public var proceedWithEmail: ((String) -> Void)?
|
||||
public var signInWithApple: (() -> Void)?
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
private var appleSignInAllowed = false
|
||||
|
||||
var inProgress: Bool = false {
|
||||
public var inProgress: Bool = false {
|
||||
didSet {
|
||||
if self.inProgress {
|
||||
let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.accentTextColor))
|
||||
self.navigationItem.rightBarButtonItem = item
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
|
||||
}
|
||||
// if self.inProgress {
|
||||
// let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.accentTextColor))
|
||||
// self.navigationItem.rightBarButtonItem = item
|
||||
// } else {
|
||||
// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
|
||||
// }
|
||||
self.controllerNode.inProgress = self.inProgress
|
||||
}
|
||||
}
|
||||
|
||||
init(presentationData: PresentationData, back: @escaping () -> Void) {
|
||||
public init(presentationData: PresentationData, mode: Mode, back: @escaping () -> Void) {
|
||||
self.presentationData = presentationData
|
||||
self.mode = mode
|
||||
|
||||
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() {
|
||||
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.controllerNode.view.disableAutomaticKeyboardHandling = [.forward, .backward]
|
||||
@ -73,13 +81,13 @@ final class AuthorizationSequenceEmailEntryController: ViewController {
|
||||
self.controllerNode.updateData(appleSignInAllowed: self.appleSignInAllowed)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.controllerNode.activateInput()
|
||||
}
|
||||
|
||||
func updateData(appleSignInAllowed: Bool) {
|
||||
public func updateData(appleSignInAllowed: Bool) {
|
||||
var appleSignInAllowed = appleSignInAllowed
|
||||
if #available(iOS 13.0, *) {
|
||||
} 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)
|
||||
|
||||
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.appleSignInAllowed {
|
||||
self.signInWithApple?()
|
@ -3,7 +3,7 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import AuthorizationUI
|
||||
import AuthorizationUtils
|
||||
import AuthenticationServices
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
@ -50,6 +50,7 @@ final class AuthorizationDividerNode: ASDisplayNode {
|
||||
final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private let strings: PresentationStrings
|
||||
private let theme: PresentationTheme
|
||||
private let mode: AuthorizationSequenceEmailEntryController.Mode
|
||||
|
||||
private let animationNode: AnimatedStickerNode
|
||||
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.theme = theme
|
||||
self.mode = mode
|
||||
|
||||
self.animationNode = DefaultAnimatedStickerNodeImpl()
|
||||
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.isUserInteractionEnabled = 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.isUserInteractionEnabled = false
|
||||
@ -190,10 +191,10 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
|
||||
insets.top = layout.statusBarHeight ?? 20.0
|
||||
|
||||
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 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.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)))
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
|
||||
if let signInWithAppleButton = self.signInWithAppleButton, self.appleSignInAllowed {
|
@ -3,7 +3,7 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import AuthorizationUI
|
||||
import AuthorizationUtils
|
||||
|
||||
final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private let strings: PresentationStrings
|
@ -3,7 +3,7 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import AuthorizationUI
|
||||
import AuthorizationUtils
|
||||
|
||||
final class AuthorizationSequencePasswordRecoveryControllerNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private let strings: PresentationStrings
|
@ -9,7 +9,6 @@ import TelegramPresentationData
|
||||
import ProgressNavigationButtonNode
|
||||
import AccountContext
|
||||
import CountrySelectionUI
|
||||
import SettingsUI
|
||||
import PhoneNumberFormat
|
||||
import DebugSettingsUI
|
||||
|
||||
@ -29,6 +28,18 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
|
||||
private let back: () -> Void
|
||||
|
||||
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 {
|
||||
didSet {
|
||||
@ -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.controllerNode.inProgress = self.inProgress
|
||||
self.confirmationController?.inProgress = self.inProgress
|
||||
}
|
||||
}
|
||||
var loginWithNumber: ((String, Bool) -> Void)?
|
||||
var accountUpdated: ((UnauthorizedAccount) -> Void)?
|
||||
|
||||
weak var confirmationController: PhoneConfirmationController?
|
||||
|
||||
private let termsDisposable = MetaDisposable()
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
@ -98,13 +112,13 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
|
||||
}
|
||||
|
||||
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) {
|
||||
self.shouldAnimateIn = true
|
||||
|
||||
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 {
|
||||
self.animatingIn = true
|
||||
if let (buttonFrame, animationSnapshot, textSnapshot) = self.transitionInArguments {
|
||||
self.controllerNode.willAnimateIn(buttonFrame: buttonFrame, animationSnapshot: animationSnapshot, textSnapshot: textSnapshot)
|
||||
if let (buttonFrame, buttonTitle, animationSnapshot, textSnapshot) = self.transitionInArguments {
|
||||
self.controllerNode.willAnimateIn(buttonFrame: buttonFrame, buttonTitle: buttonTitle, animationSnapshot: animationSnapshot, textSnapshot: textSnapshot)
|
||||
}
|
||||
Queue.mainQueue().justDispatch {
|
||||
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) {
|
||||
super.containerLayoutUpdated(layout, 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 let (buttonFrame, animationSnapshot, textSnapshot) = self.transitionInArguments {
|
||||
if let (buttonFrame, buttonTitle, animationSnapshot, textSnapshot) = self.transitionInArguments {
|
||||
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: {}))
|
||||
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_PhoneNumberAlreadyAuthorized, actions: actions), in: .window(.root))
|
||||
} else {
|
||||
var actions: [TextAlertAction] = []
|
||||
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 (code, formattedNumber) = self.controllerNode.formattedCodeAndNumber
|
||||
|
||||
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 {
|
||||
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 {
|
||||
self.hapticFeedback.error()
|
@ -6,7 +6,6 @@ import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import PhoneInputNode
|
||||
import CountrySelectionUI
|
||||
import AuthorizationUI
|
||||
import QrCode
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
@ -14,6 +13,8 @@ import AccountContext
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import SolidRoundedButtonNode
|
||||
import AuthorizationUtils
|
||||
import ManagedAnimationNode
|
||||
|
||||
private final class PhoneAndCountryNode: ASDisplayNode {
|
||||
let strings: PresentationStrings
|
||||
@ -23,9 +24,13 @@ private final class PhoneAndCountryNode: ASDisplayNode {
|
||||
|
||||
var selectCountryCode: (() -> Void)?
|
||||
var checkPhone: (() -> Void)?
|
||||
var hasNumberUpdated: ((Bool) -> Void)?
|
||||
var numberUpdated: (() -> Void)?
|
||||
|
||||
var preferredCountryIdForCode: [String: String] = [:]
|
||||
|
||||
var hasCountry = false
|
||||
|
||||
init(strings: PresentationStrings, theme: PresentationTheme) {
|
||||
self.strings = strings
|
||||
|
||||
@ -122,6 +127,7 @@ private final class PhoneAndCountryNode: ASDisplayNode {
|
||||
let flagString = emojiFlagForISOCountryCode(country.id)
|
||||
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.hasCountry = true
|
||||
|
||||
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) }) {
|
||||
@ -140,6 +146,14 @@ private final class PhoneAndCountryNode: ASDisplayNode {
|
||||
self.phoneInputNode.numberTextUpdated = { [weak self] number in
|
||||
if let strongSelf = self {
|
||||
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.numberUpdated?()
|
||||
|
||||
if processNumberChange(strongSelf.phoneInputNode.number) {
|
||||
} else if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] {
|
||||
let flagString = emojiFlagForISOCountryCode(name)
|
||||
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.hasCountry = true
|
||||
|
||||
if strongSelf.phoneInputNode.mask == nil {
|
||||
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 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.hasCountry = true
|
||||
|
||||
if strongSelf.phoneInputNode.mask == nil {
|
||||
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor)
|
||||
}
|
||||
} else {
|
||||
strongSelf.hasCountry = false
|
||||
strongSelf.countryButton.setTitle(strings.Login_SelectCountry, with: Font.regular(20.0), with: theme.list.itemAccentColor, for: [])
|
||||
strongSelf.phoneInputNode.mask = nil
|
||||
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 animationNode: AnimatedStickerNode
|
||||
private let managedAnimationNode: ManagedPhoneAnimationNode
|
||||
private let titleNode: ASTextNode
|
||||
private let noticeNode: ASTextNode
|
||||
private let phoneAndCountryNode: PhoneAndCountryNode
|
||||
@ -278,6 +304,10 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
var formattedCodeAndNumber: (String, String) {
|
||||
return self.phoneAndCountryNode.phoneInputNode.formattedCodeAndNumber
|
||||
}
|
||||
|
||||
var syncContacts: Bool {
|
||||
get {
|
||||
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) {
|
||||
self.sharedContext = sharedContext
|
||||
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.visibility = true
|
||||
|
||||
self.managedAnimationNode = ManagedPhoneAnimationNode()
|
||||
self.managedAnimationNode.isHidden = true
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.isUserInteractionEnabled = true
|
||||
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.progressType = .embedded
|
||||
self.proceedNode.isEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
@ -353,6 +399,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
self.addSubnode(self.contactSyncNode)
|
||||
self.addSubnode(self.proceedNode)
|
||||
self.addSubnode(self.animationNode)
|
||||
self.addSubnode(self.managedAnimationNode)
|
||||
self.contactSyncNode.isHidden = true
|
||||
|
||||
self.phoneAndCountryNode.selectCountryCode = { [weak self] in
|
||||
@ -361,6 +408,16 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
self.phoneAndCountryNode.checkPhone = { [weak self] in
|
||||
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
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
@ -370,6 +427,11 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
self.proceedNode.pressed = { [weak self] in
|
||||
self?.checkPhone?()
|
||||
}
|
||||
|
||||
self.animationNode.completed = { [weak self] _ in
|
||||
self?.animationNode.isHidden = true
|
||||
self?.managedAnimationNode.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -390,9 +452,11 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
private var textSnapshotView: UIView?
|
||||
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.title = "Start Messaging"
|
||||
|
||||
self.proceedNode.isEnabled = true
|
||||
self.proceedNode.title = buttonTitle
|
||||
|
||||
self.animationSnapshotView = animationSnapshot
|
||||
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)
|
||||
|
||||
let duration: Double = 0.3
|
||||
@ -423,6 +487,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
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.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?.removeFromSuperview()
|
||||
@ -437,6 +502,8 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
self.phoneAndCountryNode,
|
||||
self.contactSyncNode
|
||||
]
|
||||
|
||||
self.animationNode.layer.animateScale(from: 0.3, to: 1.0, duration: 0.3)
|
||||
|
||||
for node in nodes {
|
||||
node.alpha = 1.0
|
||||
@ -492,6 +559,8 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
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)
|
||||
|
||||
transition.updateFrame(node: self.managedAnimationNode, frame: self.animationNode.frame)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
@ -5,8 +5,8 @@ import Display
|
||||
import TelegramPresentationData
|
||||
import TextFormat
|
||||
import Markdown
|
||||
import AuthorizationUI
|
||||
import SolidRoundedButtonNode
|
||||
import AuthorizationUtils
|
||||
|
||||
private func roundCorners(diameter: CGFloat) -> UIImage {
|
||||
UIGraphicsBeginImageContextWithOptions(CGSize(width: diameter, height: diameter), false, 0.0)
|
@ -116,6 +116,10 @@ final class AuthorizationSequenceSplashController: ViewController {
|
||||
return self.startButton.frame
|
||||
}
|
||||
|
||||
var buttonTitle: String {
|
||||
return self.startButton.title ?? ""
|
||||
}
|
||||
|
||||
var animationSnapshot: UIView? {
|
||||
return self.controller.createAnimationSnapshot()
|
||||
}
|
23
submodules/AuthorizationUtils/BUILD
Normal file
23
submodules/AuthorizationUtils/BUILD
Normal 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",
|
||||
],
|
||||
)
|
@ -29,10 +29,17 @@ public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, ph
|
||||
return NSAttributedString(string: "", font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center)
|
||||
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 range = (mutableString.string as NSString).range(of: "*******")
|
||||
if range.location != NSNotFound {
|
||||
mutableString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true, range: range)
|
||||
|
||||
let string = mutableString.string
|
||||
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
|
||||
}
|
||||
}
|
@ -1696,7 +1696,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}, openPeer: { _, _ in
|
||||
}, callPeer: { _, _ 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)
|
||||
}, messageId: message.id, loadMore: {
|
||||
loadMore()
|
||||
@ -1815,7 +1815,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}, openPeer: { peer, navigation in
|
||||
}, callPeer: { _, _ 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
|
||||
guard let strongSelf = self, let currentEntries = strongSelf.currentEntries else {
|
||||
return
|
||||
|
@ -215,7 +215,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
return self._displayNode!
|
||||
}
|
||||
|
||||
var statusBarHost: StatusBarHost? {
|
||||
public var statusBarHost: StatusBarHost? {
|
||||
didSet {
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,11 @@ public final class InAppPurchaseManager: NSObject {
|
||||
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 {
|
||||
return self.skProduct.price
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ public func parseMarkdownIntoAttributedString(_ string: String, attributes: Mark
|
||||
|
||||
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)]
|
||||
if !attributes.body.additionalAttributes.isEmpty {
|
||||
if !attributes.bold.additionalAttributes.isEmpty {
|
||||
for (key, value) in attributes.bold.additionalAttributes {
|
||||
boldAttributes[NSAttributedString.Key(rawValue: key)] = value
|
||||
}
|
||||
|
@ -132,6 +132,10 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||
|
||||
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) {
|
||||
get {
|
||||
var code: Int32?
|
||||
|
@ -58,41 +58,25 @@ class EmojiHeaderComponent: Component {
|
||||
return false
|
||||
}
|
||||
|
||||
private var _ready = Promise<Bool>()
|
||||
private var _ready = Promise<Bool>(true)
|
||||
var ready: Signal<Bool, NoError> {
|
||||
return self._ready.get()
|
||||
}
|
||||
|
||||
weak var animateFrom: UIView?
|
||||
weak var containerView: UIView?
|
||||
var animationColor: UIColor?
|
||||
|
||||
private let sceneView: SCNView
|
||||
let statusView: ComponentHostView<Empty>
|
||||
|
||||
private var previousInteractionTimestamp: Double = 0.0
|
||||
private var timer: SwiftSignalKit.Timer?
|
||||
private var hasIdleAnimations = false
|
||||
|
||||
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>()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.sceneView)
|
||||
self.addSubview(self.statusView)
|
||||
|
||||
self.setup()
|
||||
|
||||
let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
|
||||
self.addGestureRecognizer(tapGestureRecoginzer)
|
||||
|
||||
self.disablesInteractiveModalDismiss = true
|
||||
self.disablesInteractiveTransitionGestureRecognizer = true
|
||||
}
|
||||
@ -100,42 +84,8 @@ class EmojiHeaderComponent: Component {
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
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() {
|
||||
|
||||
func animateIn() {
|
||||
guard let animateFrom = self.animateFrom, var containerView = self.containerView else {
|
||||
return
|
||||
}
|
||||
@ -150,8 +100,8 @@ class EmojiHeaderComponent: Component {
|
||||
self.statusView.center = targetPosition
|
||||
|
||||
animateFrom.alpha = 0.0
|
||||
self.statusView.layer.animateScale(from: 0.05, to: 1.0, duration: 0.8, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.8, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
|
||||
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.55, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
|
||||
self.addSubview(self.statusView)
|
||||
self.statusView.center = initialPosition
|
||||
})
|
||||
@ -164,129 +114,7 @@ class EmojiHeaderComponent: Component {
|
||||
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 {
|
||||
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
|
||||
|
||||
let size = self.statusView.update(
|
||||
@ -307,7 +135,7 @@ class EmojiHeaderComponent: Component {
|
||||
containerSize: CGSize(width: 96.0, height: 96.0)
|
||||
)
|
||||
self.statusView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - size.width) / 2.0), y: 63.0), size: size)
|
||||
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
@ -257,13 +257,16 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
|
||||
discount = ""
|
||||
}
|
||||
|
||||
let pricePerMonth = product.storeProduct.pricePerMonth(Int(product.months))
|
||||
|
||||
items.append(SectionGroupComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: product.id,
|
||||
component: AnyComponent(
|
||||
PremiumOptionComponent(
|
||||
title: giftTitle,
|
||||
totalPrice: product.price,
|
||||
subtitle: product.price,
|
||||
labelPrice: pricePerMonth,
|
||||
discount: discount,
|
||||
selected: product.id == component.selectedProductId,
|
||||
primaryTextColor: textColor,
|
||||
|
@ -307,7 +307,8 @@ struct PremiumIntroConfiguration {
|
||||
|
||||
final class PremiumOptionComponent: CombinedComponent {
|
||||
let title: String
|
||||
let totalPrice: String
|
||||
let subtitle: String
|
||||
let labelPrice: String
|
||||
let discount: String
|
||||
let selected: Bool
|
||||
let primaryTextColor: UIColor
|
||||
@ -318,7 +319,8 @@ final class PremiumOptionComponent: CombinedComponent {
|
||||
|
||||
init(
|
||||
title: String,
|
||||
totalPrice: String,
|
||||
subtitle: String,
|
||||
labelPrice: String,
|
||||
discount: String,
|
||||
selected: Bool,
|
||||
primaryTextColor: UIColor,
|
||||
@ -328,7 +330,8 @@ final class PremiumOptionComponent: CombinedComponent {
|
||||
checkBorderColor: UIColor
|
||||
) {
|
||||
self.title = title
|
||||
self.totalPrice = totalPrice
|
||||
self.subtitle = subtitle
|
||||
self.labelPrice = labelPrice
|
||||
self.discount = discount
|
||||
self.selected = selected
|
||||
self.primaryTextColor = primaryTextColor
|
||||
@ -342,7 +345,10 @@ final class PremiumOptionComponent: CombinedComponent {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.totalPrice != rhs.totalPrice {
|
||||
if lhs.subtitle != rhs.subtitle {
|
||||
return false
|
||||
}
|
||||
if lhs.labelPrice != rhs.labelPrice {
|
||||
return false
|
||||
}
|
||||
if lhs.discount != rhs.discount {
|
||||
@ -372,6 +378,7 @@ final class PremiumOptionComponent: CombinedComponent {
|
||||
static var body: Body {
|
||||
let check = Child(CheckComponent.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let subtitle = Child(MultilineTextComponent.self)
|
||||
let discountBackground = Child(RoundedRectangle.self)
|
||||
let discount = Child(MultilineTextComponent.self)
|
||||
let label = Child(MultilineTextComponent.self)
|
||||
@ -379,13 +386,13 @@ final class PremiumOptionComponent: CombinedComponent {
|
||||
return { context in
|
||||
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(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(
|
||||
NSAttributedString(
|
||||
string: component.totalPrice,
|
||||
string: component.labelPrice,
|
||||
font: Font.regular(17),
|
||||
textColor: component.secondaryTextColor
|
||||
)
|
||||
@ -410,6 +417,41 @@ final class PremiumOptionComponent: CombinedComponent {
|
||||
availableSize: CGSize(width: context.availableSize.width - insets.left - insets.right - label.size.width, height: context.availableSize.height),
|
||||
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
|
||||
if !component.discount.isEmpty {
|
||||
@ -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))
|
||||
)
|
||||
|
||||
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
|
||||
.position(CGPoint(x: context.availableSize.width - insets.right - label.size.width / 2.0, y: size.height / 2.0))
|
||||
@ -1198,72 +1240,210 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
let buy = context.component.buy
|
||||
let updateIsFocused = context.component.updateIsFocused
|
||||
|
||||
if state.isPremium == true {
|
||||
|
||||
} else if let products = state.products {
|
||||
var optionsItems: [SectionGroupComponent.Item] = []
|
||||
let gradientColors: [UIColor] = [
|
||||
UIColor(rgb: 0x8e77ff),
|
||||
UIColor(rgb: 0x9a6fff),
|
||||
UIColor(rgb: 0xb36eee)
|
||||
]
|
||||
|
||||
let shortestOptionPrice: Int64
|
||||
if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) {
|
||||
shortestOptionPrice = Int64(Float(product.priceCurrencyAndAmount.amount))
|
||||
} else {
|
||||
shortestOptionPrice = 1
|
||||
}
|
||||
|
||||
var i = 0
|
||||
for product in products {
|
||||
let giftTitle: String
|
||||
let months: Float
|
||||
if product.id.hasSuffix(".monthly") {
|
||||
giftTitle = strings.Premium_Monthly
|
||||
months = 1
|
||||
let layoutOptions = {
|
||||
if state.isPremium == true {
|
||||
|
||||
} else if let products = state.products {
|
||||
var optionsItems: [SectionGroupComponent.Item] = []
|
||||
let gradientColors: [UIColor] = [
|
||||
UIColor(rgb: 0x8e77ff),
|
||||
UIColor(rgb: 0x9a6fff),
|
||||
UIColor(rgb: 0xb36eee)
|
||||
]
|
||||
|
||||
let shortestOptionPrice: (Int64, NSDecimalNumber)
|
||||
if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) {
|
||||
shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
|
||||
} else {
|
||||
giftTitle = strings.Premium_Annual
|
||||
months = 12
|
||||
}
|
||||
|
||||
let discountValue = Int((1.0 - Float(product.priceCurrencyAndAmount.amount) / months / Float(shortestOptionPrice)) * 100.0)
|
||||
let discount: String
|
||||
if discountValue > 0 {
|
||||
discount = "-\(discountValue)%"
|
||||
} else {
|
||||
discount = ""
|
||||
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
|
||||
}
|
||||
|
||||
optionsItems.append(
|
||||
SectionGroupComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: product.id,
|
||||
component: AnyComponent(
|
||||
PremiumOptionComponent(
|
||||
title: giftTitle,
|
||||
totalPrice: product.price,
|
||||
discount: discount,
|
||||
selected: product.id == state.selectedProductId,
|
||||
primaryTextColor: textColor,
|
||||
secondaryTextColor: subtitleColor,
|
||||
accentColor: gradientColors[i],
|
||||
checkForegroundColor: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
checkBorderColor: environment.theme.list.itemCheckColors.strokeColor
|
||||
)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
selectProduct(product.id)
|
||||
var i = 0
|
||||
for product in products {
|
||||
let giftTitle: String
|
||||
let months: Float
|
||||
if product.id.hasSuffix(".monthly") {
|
||||
giftTitle = strings.Premium_Monthly
|
||||
months = 1
|
||||
} else {
|
||||
giftTitle = strings.Premium_Annual
|
||||
months = 12
|
||||
}
|
||||
|
||||
let discountValue = Int((1.0 - Float(product.priceCurrencyAndAmount.amount) / months / Float(shortestOptionPrice.0)) * 100.0)
|
||||
let discount: String
|
||||
if discountValue > 0 {
|
||||
discount = "-\(discountValue)%"
|
||||
} else {
|
||||
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(
|
||||
SectionGroupComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: product.id,
|
||||
component: AnyComponent(
|
||||
PremiumOptionComponent(
|
||||
title: giftTitle,
|
||||
subtitle: subtitle,
|
||||
labelPrice: pricePerMonth,
|
||||
discount: discount,
|
||||
selected: product.id == state.selectedProductId,
|
||||
primaryTextColor: textColor,
|
||||
secondaryTextColor: subtitleColor,
|
||||
accentColor: gradientColors[i],
|
||||
checkForegroundColor: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
checkBorderColor: environment.theme.list.itemCheckColors.strokeColor
|
||||
)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
selectProduct(product.id)
|
||||
}
|
||||
)
|
||||
)
|
||||
i += 1
|
||||
}
|
||||
|
||||
let optionsSection = optionsSection.update(
|
||||
component: SectionGroupComponent(
|
||||
items: optionsItems,
|
||||
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
|
||||
selectionColor: environment.theme.list.itemHighlightedBackgroundColor,
|
||||
separatorColor: environment.theme.list.itemBlocksSeparatorColor
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(optionsSection
|
||||
.position(CGPoint(x: availableWidth / 2.0, y: size.height + optionsSection.size.height / 2.0))
|
||||
.clipsToBounds(true)
|
||||
.cornerRadius(10.0)
|
||||
)
|
||||
size.height += optionsSection.size.height
|
||||
|
||||
if case .emojiStatus = context.component.source {
|
||||
size.height -= 18.0
|
||||
} else {
|
||||
size.height += 26.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let layoutPerks = {
|
||||
var i = 0
|
||||
var perksItems: [SectionGroupComponent.Item] = []
|
||||
for perk in state.configuration.perks {
|
||||
let iconBackgroundColors = gradientColors[i]
|
||||
perksItems.append(SectionGroupComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: perk.identifier,
|
||||
component: AnyComponent(
|
||||
PerkComponent(
|
||||
iconName: perk.iconName,
|
||||
iconBackgroundColors: [
|
||||
iconBackgroundColors
|
||||
],
|
||||
title: perk.title(strings: strings),
|
||||
titleColor: titleColor,
|
||||
subtitle: perk.subtitle(strings: strings),
|
||||
subtitleColor: subtitleColor,
|
||||
arrowColor: arrowColor
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { [weak state] in
|
||||
var demoSubject: PremiumDemoScreen.Subject
|
||||
switch perk {
|
||||
case .doubleLimits:
|
||||
let isPremium = state?.isPremium == true
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremimLimitsListScreen(context: accountContext, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "–").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium)
|
||||
controller.action = { [weak state] in
|
||||
dismissImpl?()
|
||||
if state?.isPremium == false {
|
||||
buy()
|
||||
}
|
||||
}
|
||||
controller.disposed = {
|
||||
updateIsFocused(false)
|
||||
}
|
||||
present(controller)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
updateIsFocused(true)
|
||||
return
|
||||
case .moreUpload:
|
||||
demoSubject = .moreUpload
|
||||
case .fasterDownload:
|
||||
demoSubject = .fasterDownload
|
||||
case .voiceToText:
|
||||
demoSubject = .voiceToText
|
||||
case .noAds:
|
||||
demoSubject = .noAds
|
||||
case .uniqueReactions:
|
||||
demoSubject = .uniqueReactions
|
||||
case .premiumStickers:
|
||||
demoSubject = .premiumStickers
|
||||
case .advancedChatManagement:
|
||||
demoSubject = .advancedChatManagement
|
||||
case .profileBadge:
|
||||
demoSubject = .profileBadge
|
||||
case .animatedUserpics:
|
||||
demoSubject = .animatedUserpics
|
||||
case .appIcons:
|
||||
demoSubject = .appIcons
|
||||
case .animatedEmoji:
|
||||
demoSubject = .animatedEmoji
|
||||
}
|
||||
|
||||
let controller = PremiumDemoScreen(
|
||||
context: accountContext,
|
||||
subject: demoSubject,
|
||||
source: .intro(state?.price),
|
||||
order: state?.configuration.perks,
|
||||
action: {
|
||||
if state?.isPremium == false {
|
||||
buy()
|
||||
}
|
||||
}
|
||||
)
|
||||
controller.disposed = {
|
||||
updateIsFocused(false)
|
||||
}
|
||||
present(controller)
|
||||
updateIsFocused(true)
|
||||
|
||||
addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier])
|
||||
}
|
||||
))
|
||||
i += 1
|
||||
}
|
||||
|
||||
let optionsSection = optionsSection.update(
|
||||
let perksSection = perksSection.update(
|
||||
component: SectionGroupComponent(
|
||||
items: optionsItems,
|
||||
items: perksItems,
|
||||
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
|
||||
selectionColor: environment.theme.list.itemHighlightedBackgroundColor,
|
||||
separatorColor: environment.theme.list.itemBlocksSeparatorColor
|
||||
@ -1272,264 +1452,172 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(optionsSection
|
||||
.position(CGPoint(x: availableWidth / 2.0, y: size.height + optionsSection.size.height / 2.0))
|
||||
context.add(perksSection
|
||||
.position(CGPoint(x: availableWidth / 2.0, y: size.height + perksSection.size.height / 2.0))
|
||||
.clipsToBounds(true)
|
||||
.cornerRadius(10.0)
|
||||
)
|
||||
size.height += optionsSection.size.height
|
||||
size.height += 26.0
|
||||
}
|
||||
|
||||
var i = 0
|
||||
var perksItems: [SectionGroupComponent.Item] = []
|
||||
for perk in state.configuration.perks {
|
||||
let iconBackgroundColors = gradientColors[i]
|
||||
perksItems.append(SectionGroupComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: perk.identifier,
|
||||
component: AnyComponent(
|
||||
PerkComponent(
|
||||
iconName: perk.iconName,
|
||||
iconBackgroundColors: [
|
||||
iconBackgroundColors
|
||||
],
|
||||
title: perk.title(strings: strings),
|
||||
titleColor: titleColor,
|
||||
subtitle: perk.subtitle(strings: strings),
|
||||
subtitleColor: subtitleColor,
|
||||
arrowColor: arrowColor
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { [weak state] in
|
||||
var demoSubject: PremiumDemoScreen.Subject
|
||||
switch perk {
|
||||
case .doubleLimits:
|
||||
let isPremium = state?.isPremium == true
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremimLimitsListScreen(context: accountContext, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "–").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium)
|
||||
controller.action = { [weak state] in
|
||||
dismissImpl?()
|
||||
if state?.isPremium == false {
|
||||
buy()
|
||||
}
|
||||
}
|
||||
controller.disposed = {
|
||||
updateIsFocused(false)
|
||||
}
|
||||
present(controller)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
updateIsFocused(true)
|
||||
return
|
||||
case .moreUpload:
|
||||
demoSubject = .moreUpload
|
||||
case .fasterDownload:
|
||||
demoSubject = .fasterDownload
|
||||
case .voiceToText:
|
||||
demoSubject = .voiceToText
|
||||
case .noAds:
|
||||
demoSubject = .noAds
|
||||
case .uniqueReactions:
|
||||
demoSubject = .uniqueReactions
|
||||
case .premiumStickers:
|
||||
demoSubject = .premiumStickers
|
||||
case .advancedChatManagement:
|
||||
demoSubject = .advancedChatManagement
|
||||
case .profileBadge:
|
||||
demoSubject = .profileBadge
|
||||
case .animatedUserpics:
|
||||
demoSubject = .animatedUserpics
|
||||
case .appIcons:
|
||||
demoSubject = .appIcons
|
||||
case .animatedEmoji:
|
||||
demoSubject = .animatedEmoji
|
||||
}
|
||||
|
||||
let controller = PremiumDemoScreen(
|
||||
context: accountContext,
|
||||
subject: demoSubject,
|
||||
source: .intro(state?.price),
|
||||
order: state?.configuration.perks,
|
||||
action: {
|
||||
if state?.isPremium == false {
|
||||
buy()
|
||||
}
|
||||
}
|
||||
)
|
||||
controller.disposed = {
|
||||
updateIsFocused(false)
|
||||
}
|
||||
present(controller)
|
||||
updateIsFocused(true)
|
||||
|
||||
addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier])
|
||||
size.height += perksSection.size.height
|
||||
|
||||
if case .emojiStatus = context.component.source {
|
||||
if state.isPremium == true {
|
||||
size.height -= 23.0
|
||||
} else {
|
||||
size.height += 23.0
|
||||
}
|
||||
))
|
||||
i += 1
|
||||
}
|
||||
|
||||
let perksSection = perksSection.update(
|
||||
component: SectionGroupComponent(
|
||||
items: perksItems,
|
||||
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
|
||||
selectionColor: environment.theme.list.itemHighlightedBackgroundColor,
|
||||
separatorColor: environment.theme.list.itemBlocksSeparatorColor
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(perksSection
|
||||
.position(CGPoint(x: availableWidth / 2.0, y: size.height + perksSection.size.height / 2.0))
|
||||
.clipsToBounds(true)
|
||||
.cornerRadius(10.0)
|
||||
)
|
||||
size.height += perksSection.size.height
|
||||
size.height += 23.0
|
||||
|
||||
let textSideInset: CGFloat = 16.0
|
||||
let textPadding: CGFloat = 13.0
|
||||
|
||||
let infoTitle = infoTitle.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(
|
||||
NSAttributedString(string: strings.Premium_AboutTitle.uppercased(), font: Font.regular(14.0), textColor: environment.theme.list.freeTextColor)
|
||||
),
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(infoTitle
|
||||
.position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + infoTitle.size.width / 2.0, y: size.height + infoTitle.size.height / 2.0))
|
||||
)
|
||||
size.height += infoTitle.size.height
|
||||
size.height += 3.0
|
||||
|
||||
let infoText = infoText.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(
|
||||
text: strings.Premium_AboutText,
|
||||
attributes: markdownAttributes
|
||||
),
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: availableWidth - sideInsets - textSideInset * 2.0, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let infoBackground = infoBackground.update(
|
||||
component: RoundedRectangle(
|
||||
color: environment.theme.list.itemBlocksBackgroundColor,
|
||||
cornerRadius: 10.0
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: availableWidth - sideInsets, height: infoText.size.height + textPadding * 2.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(infoBackground
|
||||
.position(CGPoint(x: size.width / 2.0, y: size.height + infoBackground.size.height / 2.0))
|
||||
)
|
||||
context.add(infoText
|
||||
.position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + infoText.size.width / 2.0, y: size.height + textPadding + infoText.size.height / 2.0))
|
||||
)
|
||||
size.height += infoBackground.size.height
|
||||
size.height += 6.0
|
||||
|
||||
let termsFont = Font.regular(13.0)
|
||||
let boldTermsFont = Font.semibold(13.0)
|
||||
let italicTermsFont = Font.italic(13.0)
|
||||
let boldItalicTermsFont = Font.semiboldItalic(13.0)
|
||||
let monospaceTermsFont = Font.monospace(13.0)
|
||||
let termsTextColor = environment.theme.list.freeTextColor
|
||||
let termsMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: termsFont, textColor: termsTextColor), bold: MarkdownAttributeSet(font: termsFont, textColor: termsTextColor), link: MarkdownAttributeSet(font: termsFont, textColor: environment.theme.list.itemAccentColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
var isGiftView = false
|
||||
if case let .gift(fromId, _, _) = context.component.source {
|
||||
if fromId == context.component.context.account.peerId {
|
||||
isGiftView = true
|
||||
} else {
|
||||
size.height += 23.0
|
||||
}
|
||||
}
|
||||
|
||||
let termsString: MultilineTextComponent.TextContent
|
||||
if isGiftView {
|
||||
termsString = .plain(NSAttributedString())
|
||||
} else if let promoConfiguration = context.state.promoConfiguration {
|
||||
let attributedString = stringWithAppliedEntities(promoConfiguration.status, entities: promoConfiguration.statusEntities, baseColor: termsTextColor, linkColor: environment.theme.list.itemAccentColor, baseFont: termsFont, linkFont: termsFont, boldFont: boldTermsFont, italicFont: italicTermsFont, boldItalicFont: boldItalicTermsFont, fixedFont: monospaceTermsFont, blockQuoteFont: termsFont, message: nil)
|
||||
termsString = .plain(attributedString)
|
||||
if case .emojiStatus = context.component.source {
|
||||
layoutPerks()
|
||||
layoutOptions()
|
||||
} else {
|
||||
termsString = .markdown(
|
||||
text: strings.Premium_Terms,
|
||||
attributes: termsMarkdownAttributes
|
||||
layoutOptions()
|
||||
layoutPerks()
|
||||
|
||||
let textSideInset: CGFloat = 16.0
|
||||
let textPadding: CGFloat = 13.0
|
||||
|
||||
let infoTitle = infoTitle.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(
|
||||
NSAttributedString(string: strings.Premium_AboutTitle.uppercased(), font: Font.regular(14.0), textColor: environment.theme.list.freeTextColor)
|
||||
),
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
}
|
||||
|
||||
let termsText = termsText.update(
|
||||
component: MultilineTextComponent(
|
||||
text: termsString,
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.0,
|
||||
highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.3),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { [weak environment] attributes, _ in
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String,
|
||||
let controller = environment?.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController {
|
||||
if url.hasPrefix("https://apps.apple.com/account/subscriptions") {
|
||||
controller.context.sharedContext.applicationBindings.openSubscriptions()
|
||||
} else if url.hasPrefix("https://") || url.hasPrefix("tg://") {
|
||||
controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: !url.hasPrefix("tg://"), presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {})
|
||||
context.add(infoTitle
|
||||
.position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + infoTitle.size.width / 2.0, y: size.height + infoTitle.size.height / 2.0))
|
||||
)
|
||||
size.height += infoTitle.size.height
|
||||
size.height += 3.0
|
||||
|
||||
let infoText = infoText.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(
|
||||
text: strings.Premium_AboutText,
|
||||
attributes: markdownAttributes
|
||||
),
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: availableWidth - sideInsets - textSideInset * 2.0, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let infoBackground = infoBackground.update(
|
||||
component: RoundedRectangle(
|
||||
color: environment.theme.list.itemBlocksBackgroundColor,
|
||||
cornerRadius: 10.0
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: availableWidth - sideInsets, height: infoText.size.height + textPadding * 2.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(infoBackground
|
||||
.position(CGPoint(x: size.width / 2.0, y: size.height + infoBackground.size.height / 2.0))
|
||||
)
|
||||
context.add(infoText
|
||||
.position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + infoText.size.width / 2.0, y: size.height + textPadding + infoText.size.height / 2.0))
|
||||
)
|
||||
size.height += infoBackground.size.height
|
||||
size.height += 6.0
|
||||
|
||||
let termsFont = Font.regular(13.0)
|
||||
let boldTermsFont = Font.semibold(13.0)
|
||||
let italicTermsFont = Font.italic(13.0)
|
||||
let boldItalicTermsFont = Font.semiboldItalic(13.0)
|
||||
let monospaceTermsFont = Font.monospace(13.0)
|
||||
let termsTextColor = environment.theme.list.freeTextColor
|
||||
let termsMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: termsFont, textColor: termsTextColor), bold: MarkdownAttributeSet(font: termsFont, textColor: termsTextColor), link: MarkdownAttributeSet(font: termsFont, textColor: environment.theme.list.itemAccentColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
var isGiftView = false
|
||||
if case let .gift(fromId, _, _) = context.component.source {
|
||||
if fromId == context.component.context.account.peerId {
|
||||
isGiftView = true
|
||||
}
|
||||
}
|
||||
|
||||
let termsString: MultilineTextComponent.TextContent
|
||||
if isGiftView {
|
||||
termsString = .plain(NSAttributedString())
|
||||
} else if let promoConfiguration = context.state.promoConfiguration {
|
||||
let attributedString = stringWithAppliedEntities(promoConfiguration.status, entities: promoConfiguration.statusEntities, baseColor: termsTextColor, linkColor: environment.theme.list.itemAccentColor, baseFont: termsFont, linkFont: termsFont, boldFont: boldTermsFont, italicFont: italicTermsFont, boldItalicFont: boldItalicTermsFont, fixedFont: monospaceTermsFont, blockQuoteFont: termsFont, message: nil)
|
||||
termsString = .plain(attributedString)
|
||||
} else {
|
||||
termsString = .markdown(
|
||||
text: strings.Premium_Terms,
|
||||
attributes: termsMarkdownAttributes
|
||||
)
|
||||
}
|
||||
|
||||
let termsText = termsText.update(
|
||||
component: MultilineTextComponent(
|
||||
text: termsString,
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.0,
|
||||
highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.3),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
let context = controller.context
|
||||
let signal: Signal<ResolvedUrl, NoError>?
|
||||
switch url {
|
||||
case "terms":
|
||||
signal = cachedTermsPage(context: context)
|
||||
case "privacy":
|
||||
signal = cachedPrivacyPage(context: context)
|
||||
default:
|
||||
signal = nil
|
||||
}
|
||||
if let signal = signal {
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).start(next: { resolvedUrl in
|
||||
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in
|
||||
}, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak controller] c, arguments in
|
||||
controller?.push(c)
|
||||
}, dismissInput: {}, contentContext: nil)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { [weak environment] attributes, _ in
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String,
|
||||
let controller = environment?.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController {
|
||||
if url.hasPrefix("https://apps.apple.com/account/subscriptions") {
|
||||
controller.context.sharedContext.applicationBindings.openSubscriptions()
|
||||
} else if url.hasPrefix("https://") || url.hasPrefix("tg://") {
|
||||
controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: !url.hasPrefix("tg://"), presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {})
|
||||
} else {
|
||||
let context = controller.context
|
||||
let signal: Signal<ResolvedUrl, NoError>?
|
||||
switch url {
|
||||
case "terms":
|
||||
signal = cachedTermsPage(context: context)
|
||||
case "privacy":
|
||||
signal = cachedPrivacyPage(context: context)
|
||||
default:
|
||||
signal = nil
|
||||
}
|
||||
if let signal = signal {
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).start(next: { resolvedUrl in
|
||||
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in
|
||||
}, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak controller] c, arguments in
|
||||
controller?.push(c)
|
||||
}, dismissInput: {}, contentContext: nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: availableWidth - sideInsets - textSideInset * 2.0, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(termsText
|
||||
.position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + termsText.size.width / 2.0, y: size.height + termsText.size.height / 2.0))
|
||||
)
|
||||
size.height += termsText.size.height
|
||||
size.height += 10.0
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: availableWidth - sideInsets - textSideInset * 2.0, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(termsText
|
||||
.position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + termsText.size.width / 2.0, y: size.height + termsText.size.height / 2.0))
|
||||
)
|
||||
size.height += termsText.size.height
|
||||
size.height += 10.0
|
||||
}
|
||||
|
||||
size.height += scrollEnvironment.insets.bottom
|
||||
|
||||
if context.component.source != .settings {
|
||||
@ -1713,10 +1801,11 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
otherPeerName
|
||||
).start(next: { [weak self] products, isPremium, otherPeerName in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.products == nil {
|
||||
strongSelf.selectedProductId = products.first?.id
|
||||
}
|
||||
let hadProducts = strongSelf.products != nil
|
||||
strongSelf.products = products.filter { $0.isSubscription }
|
||||
if !hadProducts {
|
||||
strongSelf.selectedProductId = strongSelf.products?.last?.id
|
||||
}
|
||||
strongSelf.isPremium = isPremium
|
||||
strongSelf.otherPeerName = otherPeerName
|
||||
strongSelf.updated(transition: .immediate)
|
||||
@ -1786,7 +1875,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
strongSelf.updateInProgress(false)
|
||||
|
||||
strongSelf.updated(transition: .immediate)
|
||||
strongSelf.completion()
|
||||
|
||||
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 {
|
||||
view.animateFrom = sourceView
|
||||
view.containerView = self.containerView
|
||||
view.animationColor = self.animationColor
|
||||
|
||||
Queue.mainQueue().after(0.1) {
|
||||
view.animateIn()
|
||||
}
|
||||
|
||||
self.sourceView = nil
|
||||
self.containerView = nil
|
||||
|
@ -5,6 +5,6 @@ import TelegramPresentationData
|
||||
|
||||
public extension SolidRoundedButtonTheme {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ swift_library(
|
||||
"//submodules/HexColor:HexColor",
|
||||
"//submodules/QrCode:QrCode",
|
||||
"//submodules/WallpaperResources:WallpaperResources",
|
||||
"//submodules/AuthorizationUtils:AuthorizationUtils",
|
||||
"//submodules/AuthorizationUI:AuthorizationUI",
|
||||
"//submodules/InstantPageUI:InstantPageUI",
|
||||
"//submodules/CheckNode:CheckNode",
|
||||
|
@ -11,7 +11,7 @@ import OverlayStatusController
|
||||
import AccountContext
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import AuthorizationUI
|
||||
import AuthorizationUtils
|
||||
import PhoneNumberFormat
|
||||
|
||||
private final class ChangePhoneNumberCodeControllerArguments {
|
||||
|
@ -10,7 +10,7 @@ import PresentationDataUtils
|
||||
import AccountContext
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import AuthorizationUI
|
||||
import AuthorizationUtils
|
||||
import PhoneNumberFormat
|
||||
|
||||
private final class ConfirmPhoneNumberCodeControllerArguments {
|
||||
|
@ -16,6 +16,7 @@ import AppBundle
|
||||
import PasswordSetupUI
|
||||
import UndoUI
|
||||
import PremiumUI
|
||||
import AuthorizationUI
|
||||
|
||||
private final class PrivacyAndSecurityControllerArguments {
|
||||
let account: Account
|
||||
@ -33,8 +34,9 @@ private final class PrivacyAndSecurityControllerArguments {
|
||||
let toggleArchiveAndMuteNonContacts: (Bool) -> Void
|
||||
let setupAccountAutoremove: () -> 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.openBlockedUsers = openBlockedUsers
|
||||
self.openLastSeenPrivacy = openLastSeenPrivacy
|
||||
@ -50,6 +52,7 @@ private final class PrivacyAndSecurityControllerArguments {
|
||||
self.toggleArchiveAndMuteNonContacts = toggleArchiveAndMuteNonContacts
|
||||
self.setupAccountAutoremove = setupAccountAutoremove
|
||||
self.openDataSettings = openDataSettings
|
||||
self.openEmailSettings = openEmailSettings
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,6 +90,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
case selectivePrivacyInfo(PresentationTheme, String)
|
||||
case passcode(PresentationTheme, String, Bool, String)
|
||||
case twoStepVerification(PresentationTheme, String, String, TwoStepVerificationAccessConfiguration?)
|
||||
case loginEmail(PresentationTheme, String, String?)
|
||||
case activeSessions(PresentationTheme, String, String)
|
||||
case autoArchiveHeader(String)
|
||||
case autoArchive(String, Bool)
|
||||
@ -99,7 +103,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .blockedPeers, .activeSessions, .passcode, .twoStepVerification:
|
||||
case .blockedPeers, .activeSessions, .passcode, .twoStepVerification, .loginEmail:
|
||||
return PrivacyAndSecuritySection.general.rawValue
|
||||
case .privacyHeader, .phoneNumberPrivacy, .lastSeenPrivacy, .profilePhotoPrivacy, .forwardPrivacy, .groupPrivacy, .voiceCallPrivacy, .voiceMessagePrivacy, .selectivePrivacyInfo:
|
||||
return PrivacyAndSecuritySection.privacy.rawValue
|
||||
@ -122,40 +126,42 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
return 3
|
||||
case .twoStepVerification:
|
||||
return 4
|
||||
case .privacyHeader:
|
||||
case .loginEmail:
|
||||
return 5
|
||||
case .phoneNumberPrivacy:
|
||||
case .privacyHeader:
|
||||
return 6
|
||||
case .lastSeenPrivacy:
|
||||
case .phoneNumberPrivacy:
|
||||
return 7
|
||||
case .profilePhotoPrivacy:
|
||||
case .lastSeenPrivacy:
|
||||
return 8
|
||||
case .voiceCallPrivacy:
|
||||
case .profilePhotoPrivacy:
|
||||
return 9
|
||||
case .voiceMessagePrivacy:
|
||||
case .voiceCallPrivacy:
|
||||
return 10
|
||||
case .forwardPrivacy:
|
||||
case .voiceMessagePrivacy:
|
||||
return 11
|
||||
case .groupPrivacy:
|
||||
case .forwardPrivacy:
|
||||
return 12
|
||||
case .selectivePrivacyInfo:
|
||||
case .groupPrivacy:
|
||||
return 13
|
||||
case .autoArchiveHeader:
|
||||
case .selectivePrivacyInfo:
|
||||
return 14
|
||||
case .autoArchive:
|
||||
case .autoArchiveHeader:
|
||||
return 15
|
||||
case .autoArchiveInfo:
|
||||
case .autoArchive:
|
||||
return 16
|
||||
case .accountHeader:
|
||||
case .autoArchiveInfo:
|
||||
return 17
|
||||
case .accountTimeout:
|
||||
case .accountHeader:
|
||||
return 18
|
||||
case .accountInfo:
|
||||
case .accountTimeout:
|
||||
return 19
|
||||
case .dataSettings:
|
||||
case .accountInfo:
|
||||
return 20
|
||||
case .dataSettingsInfo:
|
||||
case .dataSettings:
|
||||
return 21
|
||||
case .dataSettingsInfo:
|
||||
return 22
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,6 +239,12 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
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):
|
||||
if case let .activeSessions(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
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: {
|
||||
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):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Websites")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
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] = []
|
||||
|
||||
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(.loginEmail(presentationData.theme, presentationData.strings.PrivacySettings_LoginEmail, loginEmail))
|
||||
|
||||
entries.append(.privacyHeader(presentationData.theme, presentationData.strings.PrivacySettings_PrivacyTitle))
|
||||
if let privacySettings = privacySettings {
|
||||
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 stateValue = Atomic(value: PrivacyAndSecurityControllerState())
|
||||
let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in
|
||||
@ -568,6 +599,23 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
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 signal = context.engine.auth.twoStepVerificationConfiguration()
|
||||
|> map { value -> TwoStepVerificationAccessConfiguration? in
|
||||
@ -589,6 +637,8 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
}
|
||||
updateHasTwoStepAuth()
|
||||
|
||||
var setupEmailImpl: ((String?) -> Void)?
|
||||
|
||||
let arguments = PrivacyAndSecurityControllerArguments(account: context.account, openBlockedUsers: {
|
||||
pushControllerImpl?(blockedPeersController(context: context, blockedPeersContext: blockedPeersContext), true)
|
||||
}, openLastSeenPrivacy: {
|
||||
@ -926,6 +976,23 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
}))
|
||||
}, openDataSettings: {
|
||||
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())
|
||||
@ -953,9 +1020,10 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
context.sharedContext.accountManager.accessChallengeData(),
|
||||
combineLatest(twoStepAuth.get(), twoStepAuthDataValue.get()),
|
||||
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
|
||||
if let data = appConfiguration.data, let hasAutoarchive = data["autoarchive_setting_available"] as? Bool {
|
||||
canAutoarchive = hasAutoarchive
|
||||
@ -971,7 +1039,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
let isPremium = accountPeer?.isPremium ?? false
|
||||
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))
|
||||
}
|
||||
@ -997,5 +1065,60 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
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
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import AuthorizationUI
|
||||
import AuthorizationUtils
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
|
||||
|
@ -23,11 +23,15 @@ public final class SolidRoundedButtonTheme: Equatable {
|
||||
public let backgroundColor: UIColor
|
||||
public let backgroundColors: [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.backgroundColors = backgroundColors
|
||||
self.foregroundColor = foregroundColor
|
||||
self.disabledBackgroundColor = disabledBackgroundColor
|
||||
self.disabledForegroundColor = disabledForegroundColor
|
||||
}
|
||||
|
||||
public static func ==(lhs: SolidRoundedButtonTheme, rhs: SolidRoundedButtonTheme) -> Bool {
|
||||
@ -40,6 +44,12 @@ public final class SolidRoundedButtonTheme: Equatable {
|
||||
if lhs.foregroundColor != rhs.foregroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.disabledBackgroundColor != rhs.disabledBackgroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.disabledForegroundColor != rhs.disabledForegroundColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -86,6 +96,14 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
public var pressed: (() -> Void)?
|
||||
public var validLayout: CGFloat?
|
||||
|
||||
public var isEnabled: Bool = true {
|
||||
didSet {
|
||||
if self.isEnabled != oldValue {
|
||||
self.updateColors(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var title: String? {
|
||||
didSet {
|
||||
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.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if let strongSelf = self, strongSelf.isEnabled {
|
||||
if highlighted {
|
||||
strongSelf.buttonBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||
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
|
||||
|
||||
public func animateTitle(to title: String) {
|
||||
Queue.mainQueue().justDispatch {
|
||||
if let snapshotView = self.titleNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
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?.removeFromSuperview()
|
||||
})
|
||||
self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
|
||||
self.title = originalTitle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func animateTitle(to title: String) {
|
||||
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 = title
|
||||
self.buttonBackgroundNode.backgroundColor = self.theme.disabledBackgroundColor
|
||||
self.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -459,40 +461,60 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
|
||||
if theme.backgroundColors.count > 1 {
|
||||
self.buttonBackgroundNode.backgroundColor = nil
|
||||
|
||||
var locations: [CGFloat] = []
|
||||
let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1)
|
||||
for i in 0 ..< theme.backgroundColors.count {
|
||||
locations.append(delta * CGFloat(i))
|
||||
}
|
||||
self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: self.buttonHeight), colors: theme.backgroundColors, locations: locations, direction: .horizontal)
|
||||
|
||||
if self.buttonBackgroundAnimationView == nil {
|
||||
let buttonBackgroundAnimationView = UIImageView()
|
||||
self.buttonBackgroundAnimationView = buttonBackgroundAnimationView
|
||||
self.buttonBackgroundNode.view.addSubview(buttonBackgroundAnimationView)
|
||||
}
|
||||
|
||||
self.buttonBackgroundAnimationView?.image = self.buttonBackgroundNode.image
|
||||
} else {
|
||||
self.buttonBackgroundNode.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
|
||||
|
||||
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
|
||||
if theme.backgroundColors.count > 1 {
|
||||
self.buttonBackgroundNode.backgroundColor = nil
|
||||
|
||||
var locations: [CGFloat] = []
|
||||
let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1)
|
||||
for i in 0 ..< theme.backgroundColors.count {
|
||||
locations.append(delta * CGFloat(i))
|
||||
}
|
||||
self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: self.buttonHeight), colors: theme.backgroundColors, locations: locations, direction: .horizontal)
|
||||
|
||||
if self.buttonBackgroundAnimationView == nil {
|
||||
let buttonBackgroundAnimationView = UIImageView()
|
||||
self.buttonBackgroundAnimationView = buttonBackgroundAnimationView
|
||||
self.buttonBackgroundNode.view.addSubview(buttonBackgroundAnimationView)
|
||||
}
|
||||
|
||||
self.buttonBackgroundAnimationView?.image = self.buttonBackgroundNode.image
|
||||
} else {
|
||||
self.buttonBackgroundNode.image = nil
|
||||
self.buttonBackgroundAnimationView?.image = nil
|
||||
}
|
||||
}
|
||||
|
||||
if let width = self.validLayout {
|
||||
_ = self.updateLayout(width: width, transition: .immediate)
|
||||
}
|
||||
|
||||
self.updateShimmerParameters()
|
||||
}
|
||||
|
||||
public func sizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
@ -532,7 +554,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
|
||||
|
||||
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
|
||||
@ -595,7 +617,9 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.pressed?()
|
||||
if self.isEnabled {
|
||||
self.pressed?()
|
||||
}
|
||||
}
|
||||
|
||||
public func transitionToProgress() {
|
||||
|
@ -39,6 +39,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
"//submodules/StickerPeekUI:StickerPeekUI",
|
||||
"//submodules/Pasteboard:Pasteboard",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -14,6 +14,7 @@ import ShimmerEffect
|
||||
import EntityKeyboard
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import TextFormat
|
||||
|
||||
private let nativeItemSize = 36.0
|
||||
private let minItemsPerRow = 8
|
||||
@ -24,17 +25,14 @@ private let containerInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, ri
|
||||
class ItemLayout {
|
||||
let width: CGFloat
|
||||
let itemsCount: Int
|
||||
let hasTitle: Bool
|
||||
let itemsPerRow: Int
|
||||
let visibleItemSize: CGFloat
|
||||
let horizontalSpacing: CGFloat
|
||||
let itemTopOffset: CGFloat
|
||||
let height: CGFloat
|
||||
|
||||
init(width: CGFloat, itemsCount: Int, hasTitle: Bool) {
|
||||
init(width: CGFloat, itemsCount: Int) {
|
||||
self.width = width
|
||||
self.itemsCount = itemsCount
|
||||
self.hasTitle = hasTitle
|
||||
|
||||
let itemHorizontalSpace = width - containerInsets.left - containerInsets.right
|
||||
self.itemsPerRow = max(minItemsPerRow, Int((itemHorizontalSpace + minSpacing) / (nativeItemSize + minSpacing)))
|
||||
@ -45,8 +43,7 @@ class ItemLayout {
|
||||
|
||||
let numRowsInGroup = (itemsCount + (self.itemsPerRow - 1)) / self.itemsPerRow
|
||||
|
||||
self.itemTopOffset = hasTitle ? 61.0 : 0.0
|
||||
self.height = itemTopOffset + CGFloat(numRowsInGroup) * visibleItemSize + CGFloat(max(0, numRowsInGroup - 1)) * verticalSpacing
|
||||
self.height = CGFloat(numRowsInGroup) * visibleItemSize + CGFloat(max(0, numRowsInGroup - 1)) * verticalSpacing
|
||||
}
|
||||
|
||||
func frame(itemIndex: Int) -> CGRect {
|
||||
@ -56,7 +53,7 @@ class ItemLayout {
|
||||
return CGRect(
|
||||
origin: CGPoint(
|
||||
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(
|
||||
width: self.visibleItemSize,
|
||||
@ -96,8 +93,8 @@ final class StickerPackEmojisItem: GridItem {
|
||||
self.isEmpty = isEmpty
|
||||
|
||||
self.fillsRowWithDynamicHeight = { width in
|
||||
let layout = ItemLayout(width: width, itemsCount: items.count, hasTitle: title != nil)
|
||||
return layout.height
|
||||
let layout = ItemLayout(width: width, itemsCount: items.count)
|
||||
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 visibleItemPlaceholderViews: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemPlaceholderView] = [:]
|
||||
|
||||
private let containerNode: ASDisplayNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let subtitleNode: ImmediateTextNode
|
||||
private let buttonNode: HighlightableButtonNode
|
||||
|
||||
override init() {
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.subtitleNode = ImmediateTextNode()
|
||||
self.buttonNode = HighlightableButtonNode(pointerStyle: nil)
|
||||
@ -141,6 +140,7 @@ final class StickerPackEmojisItemNode: GridItemNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.subtitleNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
@ -191,6 +191,98 @@ final class StickerPackEmojisItemNode: GridItemNode {
|
||||
self?.standaloneShimmerEffect?.updateLayer()
|
||||
}
|
||||
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()
|
||||
@ -233,13 +325,15 @@ final class StickerPackEmojisItemNode: GridItemNode {
|
||||
var validIds = Set<EmojiPagerContentComponent.View.ItemLayer.Key>()
|
||||
|
||||
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
|
||||
} 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.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 {
|
||||
let item = items[index]
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -143,7 +143,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|
||||
|
||||
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.isOpaque = false
|
||||
|
@ -12,6 +12,7 @@ import TelegramAnimatedStickerNode
|
||||
import TelegramPresentationData
|
||||
import ShimmerEffect
|
||||
import StickerPeekUI
|
||||
import TextFormat
|
||||
|
||||
final class StickerPackPreviewInteraction {
|
||||
var previewedItem: StickerPreviewPeekItem?
|
||||
@ -19,11 +20,15 @@ final class StickerPackPreviewInteraction {
|
||||
|
||||
let addStickerPack: (StickerPackCollectionInfo, [StickerPackItem]) -> 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.addStickerPack = addStickerPack
|
||||
self.removeStickerPack = removeStickerPack
|
||||
self.emojiSelected = emojiSelected
|
||||
self.emojiLongPressed = emojiLongPressed
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ import PresentationDataUtils
|
||||
import StickerPeekUI
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import Pasteboard
|
||||
|
||||
private enum StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
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 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.context = context
|
||||
self.controller = controller
|
||||
@ -201,10 +216,16 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
|
||||
var addStickerPackImpl: ((StickerPackCollectionInfo, [StickerPackItem]) -> 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
|
||||
addStickerPackImpl?(info, items)
|
||||
}, removeStickerPack: { info in
|
||||
removeStickerPackImpl?(info)
|
||||
}, emojiSelected: { text, attribute in
|
||||
emojiSelectedImpl?(text, attribute)
|
||||
}, emojiLongPressed: { text, attribute, node, frame in
|
||||
emojiLongPressedImpl?(text, attribute, node, frame)
|
||||
})
|
||||
|
||||
super.init()
|
||||
@ -215,7 +236,6 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
self.addSubnode(self.actionAreaSeparatorNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
// self.addSubnode(self.titleBackgroundnode)
|
||||
self.titleContainer.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.titleContainer)
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
emojiSelectedImpl = { text, attribute in
|
||||
sendEmoji?(text, attribute)
|
||||
}
|
||||
|
||||
emojiLongPressedImpl = { text, attribute, node, frame in
|
||||
longPressEmoji?(text, attribute, node, frame)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -593,7 +621,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
|
||||
UIPasteboard.general.string = text
|
||||
|
||||
if let strongSelf = self {
|
||||
@ -1017,13 +1045,13 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
if !self.currentStickerPacks.isEmpty {
|
||||
var packsHeight = 0.0
|
||||
for stickerPack in currentStickerPacks {
|
||||
let layout = ItemLayout(width: fillingWidth, itemsCount: stickerPack.1.count, hasTitle: true)
|
||||
packsHeight += layout.height
|
||||
let layout = ItemLayout(width: fillingWidth, itemsCount: stickerPack.1.count)
|
||||
packsHeight += layout.height + 61.0
|
||||
}
|
||||
contentHeight = packsHeight + 8.0
|
||||
} else if let (info, items, _) = self.currentStickerPack {
|
||||
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
|
||||
} else {
|
||||
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 presentInGlobalOverlay: (ViewController, Any?) -> Void
|
||||
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 dimNode: ASDisplayNode
|
||||
@ -1207,7 +1237,19 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
var onReady: () -> 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.controller = controller
|
||||
self.presentationData = controller.presentationData
|
||||
@ -1217,6 +1259,8 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
self.dismissed = dismissed
|
||||
self.presentInGlobalOverlay = presentInGlobalOverlay
|
||||
self.sendSticker = sendSticker
|
||||
self.sendEmoji = sendEmoji
|
||||
self.longPressEmoji = longPressEmoji
|
||||
self.openMention = openMention
|
||||
|
||||
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
|
||||
self?.onReady()
|
||||
}
|
||||
@ -1518,6 +1562,7 @@ public final class StickerPackScreenImpl: ViewController {
|
||||
private let initialSelectedStickerPackIndex: Int
|
||||
fileprivate weak var parentNavigationController: NavigationController?
|
||||
private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
|
||||
private let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?
|
||||
|
||||
private var controllerNode: StickerPackScreenNode {
|
||||
return self.displayNode as! StickerPackScreenNode
|
||||
@ -1539,13 +1584,23 @@ public final class StickerPackScreenImpl: ViewController {
|
||||
let animationCache: AnimationCache
|
||||
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.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.stickerPacks = stickerPacks
|
||||
self.initialSelectedStickerPackIndex = selectedStickerPackIndex
|
||||
self.parentNavigationController = parentNavigationController
|
||||
self.sendSticker = sendSticker
|
||||
self.sendEmoji = sendEmoji
|
||||
self.actionPerformed = actionPerformed
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
@ -1605,6 +1660,46 @@ public final class StickerPackScreenImpl: ViewController {
|
||||
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
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -1718,16 +1813,29 @@ public enum StickerPackScreenPerformedAction {
|
||||
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 {
|
||||
//let stickerPacks = [mainStickerPack]
|
||||
let controller = StickerPackScreenImpl(context: context, stickerPacks: stickerPacks, selectedStickerPackIndex: stickerPacks.firstIndex(of: mainStickerPack) ?? 0, parentNavigationController: parentNavigationController, sendSticker: sendSticker, actionPerformed: actionPerformed)
|
||||
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,
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
@ -595,6 +595,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
|
||||
dict[512535275] = { return Api.PostAddress.parse_postAddress($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[1343122938] = { return Api.PrivacyKey.parse_privacyKeyChatInvite($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[-1600596305] = { return Api.help.PassportConfig.parse_passportConfig($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[-1728664459] = { return Api.help.PromoData.parse_promoDataEmpty($0) }
|
||||
dict[235081943] = { return Api.help.RecentMeUrls.parse_recentMeUrls($0) }
|
||||
@ -1497,6 +1498,8 @@ public extension Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PremiumGiftOption:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PremiumSubscriptionOption:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PrivacyKey:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PrivacyRule:
|
||||
|
@ -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 {
|
||||
enum PrivacyKey: TypeConstructorDescription {
|
||||
case privacyKeyAddedByPhone
|
||||
|
@ -414,13 +414,13 @@ public extension Api.help {
|
||||
}
|
||||
public extension Api.help {
|
||||
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) {
|
||||
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 {
|
||||
buffer.appendInt32(-1974518743)
|
||||
buffer.appendInt32(1395946908)
|
||||
}
|
||||
serializeString(statusText, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
@ -438,8 +438,11 @@ public extension Api.help {
|
||||
for item in videos {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
serializeString(currency, buffer: buffer, boxed: false)
|
||||
serializeInt64(monthlyAmount, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(periodOptions.count))
|
||||
for item in periodOptions {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
@ -451,8 +454,8 @@ public extension Api.help {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let currency, let monthlyAmount, 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))])
|
||||
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)), ("periodOptions", String(describing: periodOptions)), ("users", String(describing: users))])
|
||||
}
|
||||
}
|
||||
|
||||
@ -471,13 +474,13 @@ public extension Api.help {
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
|
||||
}
|
||||
var _5: String?
|
||||
_5 = parseString(reader)
|
||||
var _6: Int64?
|
||||
_6 = reader.readInt64()
|
||||
var _7: [Api.User]?
|
||||
var _5: [Api.PremiumSubscriptionOption]?
|
||||
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 _c2 = _2 != nil
|
||||
@ -485,9 +488,8 @@ public extension Api.help {
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||
return Api.help.PremiumPromo.premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, currency: _5!, monthlyAmount: _6!, users: _7!)
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.help.PremiumPromo.premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, periodOptions: _5!, users: _6!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -5383,13 +5383,13 @@ 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()
|
||||
buffer.appendInt32(1631726152)
|
||||
buffer.appendInt32(1063567478)
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
userId.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
|
||||
reactionPeer.serialize(buffer, true)
|
||||
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)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -5432,14 +5432,15 @@ 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()
|
||||
buffer.appendInt32(1790652275)
|
||||
buffer.appendInt32(698084494)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
bot.serialize(buffer, true)
|
||||
serializeString(url, buffer: buffer, boxed: false)
|
||||
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)
|
||||
var result: Api.SimpleWebViewResult?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -5469,18 +5470,19 @@ 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()
|
||||
buffer.appendInt32(-1850648527)
|
||||
buffer.appendInt32(-58219204)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
peer.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 << 3) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)}
|
||||
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 << 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)
|
||||
var result: Api.WebViewResult?
|
||||
if let signature = reader.readInt32() {
|
||||
|
@ -41,8 +41,7 @@ private func presentLiveLocationController(context: AccountContext, peerId: Peer
|
||||
}, callPeer: { _, _ in
|
||||
}, enqueueMessage: { message in
|
||||
let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start()
|
||||
}, sendSticker: nil,
|
||||
setupTemporaryHiddenMedia: { _, _, _ in
|
||||
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in
|
||||
}, chatAvatarHiddenMedia: { _, _ in
|
||||
}))
|
||||
}
|
||||
|
@ -322,6 +322,31 @@ public enum AuthorizationEmailVerificationError {
|
||||
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> {
|
||||
return account.postbox.transaction { transaction -> Signal<Never, AuthorizationSendEmailCodeError> in
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState {
|
||||
|
@ -19,7 +19,7 @@ func updatePremiumPromoConfigurationOnce(postbox: Postbox, network: Network) ->
|
||||
return .complete()
|
||||
}
|
||||
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) }
|
||||
updatePeers(transaction: transaction, peers: users, update: { current, updated -> Peer in
|
||||
if let updated = updated as? TelegramUser {
|
||||
@ -65,11 +65,10 @@ private func updatePremiumPromoConfiguration(transaction: Transaction, _ f: (Pre
|
||||
private extension PremiumPromoConfiguration {
|
||||
init(apiPremiumPromo: Api.help.PremiumPromo) {
|
||||
switch apiPremiumPromo {
|
||||
case let .premiumPromo(statusText, statusEntities, videoSections, videoFiles, currency, monthlyAmount, _):
|
||||
case let .premiumPromo(statusText, statusEntities, videoSections, videoFiles, options, _):
|
||||
self.status = statusText
|
||||
self.statusEntities = messageTextEntitiesFromApiEntities(statusEntities)
|
||||
self.currency = currency
|
||||
self.monthlyAmount = monthlyAmount
|
||||
|
||||
var videos: [String: TelegramMediaFile] = [:]
|
||||
for (key, document) in zip(videoSections, videoFiles) {
|
||||
if let file = telegramMediaFileFromApiDocument(document) {
|
||||
@ -77,6 +76,14 @@ private extension PremiumPromoConfiguration {
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,9 @@ import Postbox
|
||||
public struct PremiumPromoConfiguration: Codable, Equatable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case status
|
||||
case currency
|
||||
case monthlyAmount
|
||||
case statusEntities
|
||||
case videos
|
||||
case premiumProductOptions
|
||||
}
|
||||
|
||||
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 statusEntities: [MessageTextEntity]
|
||||
public var videos: [String: TelegramMediaFile]
|
||||
|
||||
public var currency: String
|
||||
public var monthlyAmount: Int64
|
||||
public var premiumProductOptions: [PremiumProductOption]
|
||||
|
||||
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.currency = currency
|
||||
self.monthlyAmount = monthlyAmount
|
||||
self.statusEntities = statusEntities
|
||||
self.videos = videos
|
||||
self.premiumProductOptions = premiumProductOptions
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
@ -66,9 +98,7 @@ public struct PremiumPromoConfiguration: Codable, Equatable {
|
||||
}
|
||||
self.videos = videos
|
||||
|
||||
self.currency = try container.decode(String.self, forKey: .currency)
|
||||
self.monthlyAmount = try container.decode(Int64.self, forKey: .monthlyAmount)
|
||||
|
||||
self.premiumProductOptions = try container.decode([PremiumProductOption].self, forKey: .premiumProductOptions)
|
||||
}
|
||||
|
||||
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.statusEntities, forKey: .statusEntities)
|
||||
try container.encode(self.currency, forKey: .currency)
|
||||
try container.encode(self.monthlyAmount, forKey: .monthlyAmount)
|
||||
|
||||
|
||||
var pairs: [DictionaryPair] = []
|
||||
for (key, file) in self.videos {
|
||||
pairs.append(DictionaryPair(key, value: file))
|
||||
}
|
||||
try container.encode(pairs, forKey: .videos)
|
||||
|
||||
try container.encode(self.premiumProductOptions, forKey: .premiumProductOptions)
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,12 @@ import SwiftSignalKit
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
#if os(macOS)
|
||||
private let botWebViewPlatform = "macos"
|
||||
#else
|
||||
private let botWebViewPlatform = "ios"
|
||||
#endif
|
||||
|
||||
public enum RequestSimpleWebViewError {
|
||||
case generic
|
||||
}
|
||||
@ -22,7 +28,7 @@ func _internal_requestSimpleWebView(postbox: Postbox, network: Network, botId: P
|
||||
if let _ = serializedThemeParams {
|
||||
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
|
||||
return .generic
|
||||
}
|
||||
@ -125,7 +131,7 @@ func _internal_requestWebView(postbox: Postbox, network: Network, stateManager:
|
||||
// if _ {
|
||||
// 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
|
||||
return .generic
|
||||
}
|
||||
|
@ -171,11 +171,11 @@ func _internal_reportPeerMessages(account: Account, messageIds: [MessageId], rea
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
guard let author = transaction.getPeer(authorId).flatMap(apiInputUser) else {
|
||||
guard let author = transaction.getPeer(authorId).flatMap(apiInputPeer) else {
|
||||
return nil
|
||||
}
|
||||
return (peer, author)
|
||||
@ -184,7 +184,7 @@ func _internal_reportPeerReaction(account: Account, authorId: PeerId, messageId:
|
||||
guard let (inputPeer, inputUser) = inputData else {
|
||||
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
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|
@ -163,6 +163,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
||||
case sharedMediaScrollingTooltip = 29
|
||||
case sharedMediaFastScrollingTooltip = 30
|
||||
case forcedPasswordSetup = 31
|
||||
case emojiTooltip = 32
|
||||
|
||||
var key: ValueBoxKey {
|
||||
let v = ValueBoxKey(length: 4)
|
||||
@ -339,6 +340,10 @@ private struct ApplicationSpecificNoticeKeys {
|
||||
static func sharedMediaFastScrollingTooltip() -> NoticeEntryKey {
|
||||
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 {
|
||||
@ -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> {
|
||||
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedTrendingStickerPacks())
|
||||
|> map { view -> [Int64]? in
|
||||
|
@ -2160,7 +2160,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
case featured
|
||||
}
|
||||
|
||||
let item: Item
|
||||
public let item: Item
|
||||
|
||||
private let content: ItemContent
|
||||
private let placeholderColor: UIColor
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Settings/Menu/LoginEmail.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/Menu/LoginEmail.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "LoginEmail.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
122
submodules/TelegramUI/Images.xcassets/Settings/Menu/LoginEmail.imageset/LoginEmail.pdf
vendored
Normal file
122
submodules/TelegramUI/Images.xcassets/Settings/Menu/LoginEmail.imageset/LoginEmail.pdf
vendored
Normal 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
|
@ -25,6 +25,7 @@ import AppLock
|
||||
import AccountUtils
|
||||
import ContextUI
|
||||
import TelegramCallsUI
|
||||
import AuthorizationUI
|
||||
|
||||
final class UnauthorizedApplicationContext {
|
||||
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: {
|
||||
authorizationCompleted?()
|
||||
})
|
||||
(self.rootController as NavigationController).statusBarHost = sharedContext.mainWindow?.statusBarHost
|
||||
|
||||
authorizationCompleted = { [weak self] in
|
||||
self?.authorizationCompleted = true
|
||||
|
@ -407,6 +407,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private var raiseToListen: RaiseToListenManager?
|
||||
private var voicePlaylistDidEndTimestamp: Double = 0.0
|
||||
|
||||
private weak var emojiTooltipController: TooltipController?
|
||||
private weak var sendingOptionsTooltipController: TooltipController?
|
||||
private weak var searchResultsTooltipController: TooltipController?
|
||||
private weak var messageTooltipController: TooltipController?
|
||||
@ -828,6 +829,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self?.sendMessages([message])
|
||||
}, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference, sourceNode, sourceRect in
|
||||
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
|
||||
if let strongSelf = self {
|
||||
strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { entry in
|
||||
@ -1949,6 +1952,29 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.sendMessages(transformedMessages)
|
||||
}
|
||||
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
|
||||
if let strongSelf = self {
|
||||
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() {
|
||||
self.checksTooltipController?.dismiss()
|
||||
|
||||
@ -15993,6 +16039,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
private func dismissAllTooltips() {
|
||||
self.emojiTooltipController?.dismiss()
|
||||
self.sendingOptionsTooltipController?.dismiss()
|
||||
self.searchResultsTooltipController?.dismiss()
|
||||
self.messageTooltipController?.dismiss()
|
||||
@ -16750,7 +16797,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.chatDisplayNode.dismissTextInput()
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import ChatInterfaceState
|
||||
import UndoUI
|
||||
import TelegramPresentationData
|
||||
import ChatPresentationInterfaceState
|
||||
import TextFormat
|
||||
|
||||
struct ChatInterfaceHighlightedState: Equatable {
|
||||
let messageStableId: UInt32
|
||||
@ -74,6 +75,7 @@ public final class ChatControllerInteraction {
|
||||
let sendCurrentMessage: (Bool) -> Void
|
||||
let sendMessage: (String) -> Void
|
||||
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 sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool) -> Bool
|
||||
let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void
|
||||
@ -179,6 +181,7 @@ public final class ChatControllerInteraction {
|
||||
sendCurrentMessage: @escaping (Bool) -> Void,
|
||||
sendMessage: @escaping (String) -> Void,
|
||||
sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Bool,
|
||||
sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute) -> Void,
|
||||
sendGif: @escaping (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool,
|
||||
sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool) -> Bool,
|
||||
requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void,
|
||||
@ -267,6 +270,7 @@ public final class ChatControllerInteraction {
|
||||
self.sendCurrentMessage = sendCurrentMessage
|
||||
self.sendMessage = sendMessage
|
||||
self.sendSticker = sendSticker
|
||||
self.sendEmoji = sendEmoji
|
||||
self.sendGif = sendGif
|
||||
self.sendBotContextResultAsGif = sendBotContextResultAsGif
|
||||
self.requestMessageActionCallback = requestMessageActionCallback
|
||||
|
@ -2563,6 +2563,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
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 {
|
||||
return self.inputPanelNode is ChatTextInputPanelNode
|
||||
}
|
||||
|
@ -225,7 +225,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, callPeer: { peerId, isVideo in
|
||||
self?.controllerInteraction?.callPeer(peerId, isVideo)
|
||||
}, enqueueMessage: { _ in
|
||||
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { signal, media in
|
||||
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { signal, media in
|
||||
if let strongSelf = self {
|
||||
strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { messageId in
|
||||
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
|
||||
@ -261,7 +261,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, activateMessagePinch: { _ in
|
||||
}, openMessageContextActions: { _, _, _, _ 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)
|
||||
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
|
||||
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
|
||||
|
@ -3467,6 +3467,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
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? {
|
||||
guard let backgroundImage = self.transparentTextInputBackgroundImage else {
|
||||
|
@ -112,7 +112,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
return false }, openPeer: { _, _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ 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
|
||||
}, presentControllerInCurrent: { _, _ in
|
||||
}, navigationController: {
|
||||
|
@ -78,7 +78,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
params.navigationController?.pushViewController(controller)
|
||||
return true
|
||||
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 }
|
||||
|
||||
if actions.count > 1, let first = actions.first {
|
||||
|
@ -82,6 +82,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, sendMessage: { _ in
|
||||
}, sendSticker: { _, _, _, _, _, _, _, _ in
|
||||
return false
|
||||
}, sendEmoji: { _, _ in
|
||||
}, sendGif: { _, _, _, _, _ in
|
||||
return false
|
||||
}, sendBotContextResultAsGif: { _, _, _, _, _ in
|
||||
@ -282,7 +283,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, _, loadMore) = location {
|
||||
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
|
||||
}
|
||||
|
@ -1759,8 +1759,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
private let archivedPacks = Promise<[ArchivedStickerPackItem]?>()
|
||||
private let blockedPeers = Promise<BlockedPeersContext?>(nil)
|
||||
private let hasTwoStepAuth = Promise<Bool?>(nil)
|
||||
private let twoStepAuthData = Promise<TwoStepVerificationAccessConfiguration?>(nil)
|
||||
private let hasPassport = Promise<Bool>(false)
|
||||
private let twoStepAccessConfiguration = Promise<TwoStepVerificationAccessConfiguration?>(nil)
|
||||
private let twoStepAuthData = Promise<TwoStepAuthData?>(nil)
|
||||
private let supportPeerDisposable = MetaDisposable()
|
||||
private let tipsPeerDisposable = MetaDisposable()
|
||||
private let cachedFaq = Promise<ResolvedUrl?>(nil)
|
||||
@ -2271,6 +2271,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}, sendMessage: { _ in
|
||||
}, sendSticker: { _, _, _, _, _, _, _, _ in
|
||||
return false
|
||||
}, sendEmoji: { _, _ in
|
||||
}, sendGif: { _, _, _, _, _ in
|
||||
return false
|
||||
}, 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.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
|
||||
return TwoStepVerificationAccessConfiguration(configuration: value, password: nil)
|
||||
}))
|
||||
self.hasPassport.set(.single(false) |> then(context.engine.auth.twoStepAuthData()
|
||||
|> map { value -> Bool in
|
||||
return value.hasSecretValues
|
||||
|
||||
self.twoStepAuthData.set(.single(nil)
|
||||
|> 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)))
|
||||
|
||||
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
|
||||
@ -3404,7 +3412,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}, callPeer: { peerId, isVideo in
|
||||
//self?.controllerInteraction?.callPeer(peerId)
|
||||
}, 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 {
|
||||
strongSelf.openUrl(url: url, concealed: false, external: false)
|
||||
}
|
||||
@ -6369,13 +6377,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] blockedPeersContext, hasTwoStepAuth in
|
||||
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
|
||||
self?.privacySettings.set(.single(settings))
|
||||
}, updatedBlockedPeers: { [weak self] blockedPeersContext in
|
||||
self?.blockedPeers.set(.single(blockedPeersContext))
|
||||
}, updatedHasTwoStepAuth: { [weak self] hasTwoStepAuthValue in
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
@ -1281,7 +1281,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
tapMessage?(message)
|
||||
}, 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
|
||||
}, 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
|
||||
@ -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 {
|
||||
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? {
|
||||
|
Loading…
x
Reference in New Issue
Block a user