Various fixes

This commit is contained in:
Ilya Laktyushin 2022-08-29 05:25:29 +02:00
parent b4e057f444
commit e15dca9bcc
13 changed files with 438 additions and 183 deletions

View File

@ -7962,6 +7962,7 @@ Sorry for the inconvenience.";
"Emoji.FrequentlyUsed" = "Recently Used";
"Premium.Annual" = "Annual";
"Premium.Semiannual" = "Semiannual";
"Premium.Monthly" = "Monthly";
"Login.PhoneNumberConfirmation" = "Is this the correct number?";

View File

@ -11,6 +11,8 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
return self.displayNode as! AuthorizationSequenceCodeEntryControllerNode
}
private var validLayout: ContainerViewLayout?
private let strings: PresentationStrings
private let theme: PresentationTheme
private let openUrl: (String) -> Void
@ -30,12 +32,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
public var inProgress: Bool = false {
didSet {
// if self.inProgress {
// let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.rootController.navigationBar.accentTextColor))
// self.navigationItem.rightBarButtonItem = item
// } else {
// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
// }
self.updateNavigationItems()
self.controllerNode.inProgress = self.inProgress
}
}
@ -53,8 +50,6 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
self.statusBar.statusBarStyle = theme.intro.statusBarStyle.style
// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
self.attemptNavigation = { _ in
return false
}
@ -124,6 +119,19 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
self.controllerNode.animateError(text: text)
}
func updateNavigationItems() {
guard let layout = self.validLayout, layout.size.width < 360.0 else {
return
}
if self.inProgress {
let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.rootController.navigationBar.accentTextColor))
self.navigationItem.rightBarButtonItem = item
} else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
}
}
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 != email || self.data?.2 != codeType || self.data?.3 != nextType || self.data?.4 != timeout {
@ -144,6 +152,13 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
let hadLayout = self.validLayout != nil
self.validLayout = layout
if !hadLayout {
self.updateNavigationItems()
}
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
}

View File

@ -249,7 +249,9 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
controller.present(strongSelf.sharedContext.makeProxySettingsController(sharedContext: strongSelf.sharedContext, account: strongSelf.account), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}))
}
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: actions), in: .window(.root))
(controller.navigationController as? NavigationController)?.presentOverlay(controller: standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: actions), inGlobal: true, blockInteraction: true)
controller.dismissConfirmation()
}
}))
}
@ -645,7 +647,6 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
guard let strongSelf = self else {
return
}
// lastController?.inProgress = false
switch result {
case let .signUp(data):
let _ = beginSignUp(account: strongSelf.account, data: data).start()
@ -655,8 +656,6 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
}, error: { [weak self, weak lastController] error in
Queue.mainQueue().async {
if let strongSelf = self, let lastController = lastController {
// controller.inProgress = false
let text: String
switch error {
case .limitExceeded:

View File

@ -17,6 +17,8 @@ public final class AuthorizationSequenceEmailEntryController: ViewController {
return self.displayNode as! AuthorizationSequenceEmailEntryControllerNode
}
private var validLayout: ContainerViewLayout?
private let presentationData: PresentationData
public var proceedWithEmail: ((String) -> Void)?
@ -28,12 +30,7 @@ public final class AuthorizationSequenceEmailEntryController: ViewController {
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))
// }
self.updateNavigationItems()
self.controllerNode.inProgress = self.inProgress
}
}
@ -56,8 +53,6 @@ public final class AuthorizationSequenceEmailEntryController: ViewController {
self.navigationBar?.backPressed = {
back()
}
// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
}
required init(coder aDecoder: NSCoder) {
@ -87,6 +82,19 @@ public final class AuthorizationSequenceEmailEntryController: ViewController {
self.controllerNode.activateInput()
}
func updateNavigationItems() {
guard let layout = self.validLayout, layout.size.width < 360.0 else {
return
}
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))
}
}
public func updateData(appleSignInAllowed: Bool) {
var appleSignInAllowed = appleSignInAllowed
if #available(iOS 13.0, *) {
@ -104,6 +112,13 @@ public final class AuthorizationSequenceEmailEntryController: ViewController {
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
let hadLayout = self.validLayout != nil
self.validLayout = layout
if !hadLayout {
self.updateNavigationItems()
}
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
}

View File

@ -87,7 +87,6 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
self.animationNode = DefaultAnimatedStickerNodeImpl()
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "IntroMail"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animationNode.visibility = true
self.titleNode = ASTextNode()
self.titleNode.isUserInteractionEnabled = false
@ -189,11 +188,12 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
var insets = layout.insets(options: [])
insets.top = layout.statusBarHeight ?? 20.0
if let inputHeight = layout.inputHeight {
insets.bottom = max(inputHeight, insets.bottom)
}
let titleInset: CGFloat = layout.size.width > 320.0 ? 18.0 : 0.0
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)
@ -204,15 +204,25 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
let proceedSize = CGSize(width: layout.size.width - 48.0, height: proceedHeight)
var items: [AuthorizationLayoutItem] = []
items.append(AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
self.animationNode.updateLayout(size: animationSize)
items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: titleInset, maxValue: titleInset), 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: 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)))
if layout.size.width > 320.0 {
items.insert(AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), at: 0)
self.animationNode.updateLayout(size: animationSize)
self.proceedNode.isHidden = false
self.animationNode.isHidden = false
self.animationNode.visibility = true
} else {
insets.top = navigationBarHeight
self.proceedNode.isHidden = true
self.animationNode.isHidden = true
}
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)

View File

@ -17,6 +17,8 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
return self.displayNode as! AuthorizationSequencePhoneEntryControllerNode
}
private var validLayout: ContainerViewLayout?
private let sharedContext: SharedAccountContext
private var account: UnauthorizedAccount
private let isTestingEnvironment: Bool
@ -43,12 +45,7 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
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))
// }
self.updateNavigationItems()
self.controllerNode.inProgress = self.inProgress
self.confirmationController?.inProgress = self.inProgress
}
@ -89,7 +86,6 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
if !otherAccountPhoneNumbers.1.isEmpty {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
}
// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
}
required init(coder aDecoder: NSCoder) {
@ -104,6 +100,19 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
self.back()
}
func updateNavigationItems() {
guard let layout = self.validLayout, layout.size.width < 360.0 else {
return
}
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))
}
}
func updateData(countryCode: Int32, countryName: String?, number: String) {
self.currentData = (countryCode, countryName, number)
if self.isNodeLoaded {
@ -207,6 +216,13 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
let hadLayout = self.validLayout != nil
self.validLayout = layout
if !hadLayout {
self.updateNavigationItems()
}
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
if self.shouldAnimateIn, let inputHeight = layout.inputHeight, inputHeight > 0.0 {
@ -217,6 +233,11 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
}
}
func dismissConfirmation() {
self.confirmationController?.dismissAnimated()
self.confirmationController = nil
}
@objc func nextPressed() {
let (_, _, number) = self.controllerNode.codeAndNumber
if !number.isEmpty {
@ -239,6 +260,7 @@ 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 {
if let validLayout = self.validLayout, validLayout.size.width > 320.0 {
let (code, formattedNumber) = self.controllerNode.formattedCodeAndNumber
let confirmationController = PhoneConfirmationController(theme: self.presentationData.theme, strings: self.presentationData.strings, code: code, number: formattedNumber, sourceController: self)
@ -249,6 +271,16 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
}
(self.navigationController as? NavigationController)?.presentOverlay(controller: confirmationController, inGlobal: true, blockInteraction: true)
self.confirmationController = confirmationController
} 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
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))
}
}
} else {
self.hapticFeedback.error()

View File

@ -227,7 +227,7 @@ private final class PhoneAndCountryNode: ASDisplayNode {
self.phoneBackground.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - 57.0), size: CGSize(width: size.width - inset, height: 57.0))
let countryCodeFrame = CGRect(origin: CGPoint(x: 18.0, y: size.height - 58.0), size: CGSize(width: 71.0, height: 57.0))
let numberFrame = CGRect(origin: CGPoint(x: 107.0, y: size.height - 58.0), size: CGSize(width: size.width - 96.0 - 8.0, height: 57.0))
let numberFrame = CGRect(origin: CGPoint(x: 107.0, y: size.height - 58.0), size: CGSize(width: size.width - 96.0 - 8.0 - 24.0, height: 57.0))
let placeholderFrame = numberFrame.offsetBy(dx: 0.0, dy: 17.0 - UIScreenPixel)
let phoneInputFrame = countryCodeFrame.union(numberFrame)
@ -360,7 +360,6 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.animationNode = DefaultAnimatedStickerNodeImpl()
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
@ -480,8 +479,6 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
func animateIn(buttonFrame: CGRect, buttonTitle: String, animationSnapshot: UIView, textSnapshot: UIView) {
self.proceedNode.animateTitle(to: self.strings.Login_Continue)
let duration: Double = 0.3
self.animationSnapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in
self?.animationSnapshotView?.removeFromSuperview()
self?.animationSnapshotView = nil
@ -507,7 +504,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
for node in nodes {
node.alpha = 1.0
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
}
@ -518,11 +515,13 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
var insets = layout.insets(options: [])
insets.top = layout.statusBarHeight ?? 20.0
if let inputHeight = layout.inputHeight, !inputHeight.isZero {
insets.bottom = max(inputHeight, insets.bottom)
}
let titleInset: CGFloat = layout.size.width > 320.0 ? 18.0 : 0.0
let additionalBottomInset: CGFloat = layout.size.width > 320.0 ? 80.0 : 10.0
self.titleNode.attributedText = NSAttributedString(string: strings.Login_PhoneTitle, font: Font.bold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
let inset: CGFloat = 24.0
@ -534,11 +533,23 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
let proceedSize = CGSize(width: layout.size.width - inset * 2.0, height: proceedHeight)
var items: [AuthorizationLayoutItem] = [
AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)),
AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)),
AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: titleInset, maxValue: titleInset), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)),
AuthorizationLayoutItem(node: self.noticeNode, size: noticeSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)),
AuthorizationLayoutItem(node: self.phoneAndCountryNode, size: CGSize(width: layout.size.width, height: 115.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 30.0, maxValue: 30.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)),
]
if layout.size.width > 320.0 {
items.insert(AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), at: 0)
self.proceedNode.isHidden = false
self.animationNode.isHidden = false
self.animationNode.visibility = true
} else {
insets.top = navigationBarHeight
self.proceedNode.isHidden = true
self.animationNode.isHidden = true
self.managedAnimationNode.isHidden = true
}
let contactSyncSize = self.contactSyncNode.updateLayout(width: layout.size.width)
if self.hasOtherAccounts {
self.contactSyncNode.isHidden = false
@ -558,7 +569,7 @@ 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)
let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - additionalBottomInset)), items: items, transition: transition, failIfDoesNotFit: false)
transition.updateFrame(node: self.managedAnimationNode, frame: self.animationNode.frame)
}
@ -697,6 +708,11 @@ final class PhoneConfirmationController: ViewController {
var proceed: () -> Void = {}
class Node: ASDisplayNode {
private let theme: PresentationTheme
private let code: String
private let number: String
private let dimNode: ASDisplayNode
private let backgroundNode: ASDisplayNode
@ -714,7 +730,14 @@ final class PhoneConfirmationController: ViewController {
var proceed: () -> Void = {}
var cancel: () -> Void = {}
private var validLayout: ContainerViewLayout?
init(theme: PresentationTheme, strings: PresentationStrings, code: String, number: String) {
self.theme = theme
self.code = code
self.number = number
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
@ -753,7 +776,6 @@ final class PhoneConfirmationController: ViewController {
self.codeTargetNode.displaysAsynchronously = false
self.codeTargetNode.attributedText = NSAttributedString(string: code, font: largeFont, textColor: theme.list.itemPrimaryTextColor)
self.phoneTargetNode = ImmediateTextNode()
self.phoneTargetNode.displaysAsynchronously = false
@ -801,6 +823,9 @@ final class PhoneConfirmationController: ViewController {
}
func animateIn(codeNode: ASDisplayNode, numberNode: ASDisplayNode, buttonNode: ASDisplayNode) {
guard let layout = self.validLayout else {
return
}
let codeFrame = codeNode.convert(codeNode.bounds, to: nil)
let numberFrame = numberNode.convert(numberNode.bounds, to: nil)
let buttonFrame = buttonNode.convert(buttonNode.bounds, to: nil)
@ -811,43 +836,45 @@ final class PhoneConfirmationController: ViewController {
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
let codeSize = self.codeSourceNode.updateLayout(self.frame.size)
let duration: Double = 0.25
let codeSize = self.codeSourceNode.updateLayout(layout.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)
let numberSize = self.phoneSourceNode.updateLayout(layout.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.codeSourceNode.layer.animateScale(from: 1.0, to: sourceScale, duration: duration)
self.codeSourceNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration)
self.codeSourceNode.layer.animatePosition(from: self.codeSourceNode.position, to: self.codeTargetNode.position, duration: duration)
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.phoneSourceNode.layer.animateScale(from: 1.0, to: sourceScale, duration: duration)
self.phoneSourceNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration)
self.phoneSourceNode.layer.animatePosition(from: self.phoneSourceNode.position, to: self.phoneTargetNode.position, duration: duration)
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.codeTargetNode.layer.animateScale(from: targetScale, to: 1.0, duration: duration)
self.codeTargetNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
self.codeTargetNode.layer.animatePosition(from: self.codeSourceNode.position, to: self.codeTargetNode.position, duration: duration)
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.phoneTargetNode.layer.animateScale(from: targetScale, to: 1.0, duration: duration)
self.phoneTargetNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
self.phoneTargetNode.layer.animatePosition(from: self.phoneSourceNode.position, to: self.phoneTargetNode.position, duration: duration)
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.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: duration)
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.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
self.textNode.layer.animateScale(from: 0.5, to: 1.0, duration: duration)
self.textNode.layer.animatePosition(from: CGPoint(x: -100.0, y: -45.0), to: CGPoint(), duration: duration, 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.cancelButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
self.cancelButton.layer.animateScale(from: 0.5, to: 1.0, duration: duration)
self.cancelButton.layer.animatePosition(from: CGPoint(x: -100.0, y: -70.0), to: CGPoint(), duration: duration, additive: true)
self.proceedNode.layer.animatePosition(from: buttonFrame.center, to: self.proceedNode.position, duration: 0.3)
self.proceedNode.layer.animatePosition(from: buttonFrame.center, to: self.proceedNode.position, duration: duration)
}
func animateOut(codeNode: ASDisplayNode, numberNode: ASDisplayNode, buttonNode: ASDisplayNode, completion: @escaping () -> Void) {
@ -857,6 +884,8 @@ final class PhoneConfirmationController: ViewController {
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
let duration: Double = 0.25
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)
@ -866,45 +895,48 @@ final class PhoneConfirmationController: ViewController {
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.codeSourceNode.layer.animateScale(from: sourceScale, to: 1.0, duration: duration)
self.codeSourceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
self.codeSourceNode.layer.animatePosition(from: self.codeTargetNode.position, to: self.codeSourceNode.position, duration: duration)
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.phoneSourceNode.layer.animateScale(from: sourceScale, to: 1.0, duration: duration)
self.phoneSourceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
self.phoneSourceNode.layer.animatePosition(from: self.phoneTargetNode.position, to: self.phoneSourceNode.position, duration: duration)
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)
self.codeTargetNode.layer.animateScale(from: 1.0, to: targetScale, duration: duration)
self.codeTargetNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
self.codeTargetNode.layer.animatePosition(from: self.codeTargetNode.position, to: self.codeSourceNode.position, duration: duration)
Queue.mainQueue().after(0.25) {
Queue.mainQueue().after(0.23) {
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
self.phoneTargetNode.layer.animateScale(from: 1.0, to: targetScale, duration: duration)
self.phoneTargetNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false, completion: { _ in
completion()
})
self.phoneTargetNode.layer.animatePosition(from: self.phoneTargetNode.position, to: self.phoneSourceNode.position, duration: 0.3)
self.phoneTargetNode.layer.animatePosition(from: self.phoneTargetNode.position, to: self.phoneSourceNode.position, duration: duration)
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.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: duration)
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.textNode.layer.animateScale(from: 1.0, to: 0.5, duration: duration, removeOnCompletion: false)
self.textNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -100.0, y: -45.0), duration: duration, 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.cancelButton.layer.animateScale(from: 1.0, to: 0.5, duration: duration, removeOnCompletion: false)
self.cancelButton.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -100.0, y: -70.0), duration: duration, removeOnCompletion: false, additive: true)
self.proceedNode.layer.animatePosition(from: self.proceedNode.position, to: buttonFrame.center, duration: 0.3, removeOnCompletion: false)
self.proceedNode.layer.animatePosition(from: self.proceedNode.position, to: buttonFrame.center, duration: duration, removeOnCompletion: false)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
let hadLayout = self.validLayout != nil
self.validLayout = layout
let sideInset: CGFloat = 8.0
let innerInset: CGFloat = 18.0
@ -914,15 +946,32 @@ final class PhoneConfirmationController: ViewController {
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 maxWidth = layout.size.width - 20.0
if !hadLayout {
var fontSize = 34.0
if layout.size.width < 375.0 {
fontSize = 30.0
}
let totalWidth = codeSize.width + numberSize.width + 10.0
let largeFont = Font.with(size: fontSize, design: .regular, weight: .bold, traits: [.monospacedNumbers])
self.codeTargetNode.attributedText = NSAttributedString(string: self.code, font: largeFont, textColor: self.theme.list.itemPrimaryTextColor)
let targetString = NSMutableAttributedString(string: self.number, font: largeFont, textColor: self.theme.list.itemPrimaryTextColor)
targetString.addAttribute(NSAttributedString.Key.kern, value: 1.6, range: NSRange(location: 0, length: targetString.length))
self.phoneTargetNode.attributedText = targetString
}
let spacing: CGFloat = 10.0
let codeSize = self.codeTargetNode.updateLayout(CGSize(width: maxWidth, height: .greatestFiniteMagnitude))
let numberSize = self.phoneTargetNode.updateLayout(CGSize(width: maxWidth - codeSize.width - spacing, height: .greatestFiniteMagnitude))
let totalWidth = codeSize.width + numberSize.width + spacing
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)
let numberFrame = CGRect(origin: CGPoint(x: codeFrame.maxX + spacing, 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)
@ -972,6 +1021,10 @@ final class PhoneConfirmationController: ViewController {
}
}
func dismissAnimated() {
self.controllerNode.cancel()
}
func transitionOut() {
self.controllerNode.cancel()

View File

@ -2136,21 +2136,21 @@ open class TextView: UIView {
}
}
if !line.strikethroughs.isEmpty {
for strikethrough in line.strikethroughs {
var textColor: UIColor?
layout.attributedString?.enumerateAttributes(in: NSMakeRange(line.range.location, line.range.length), options: []) { attributes, range, _ in
if range == strikethrough.range, let color = attributes[NSAttributedString.Key.foregroundColor] as? UIColor {
textColor = color
}
}
if let textColor = textColor {
context.setFillColor(textColor.cgColor)
}
let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
context.fill(CGRect(x: frame.minX, y: frame.minY - 5.0, width: frame.width, height: 1.0))
}
}
// if !line.strikethroughs.isEmpty {
// for strikethrough in line.strikethroughs {
// var textColor: UIColor?
// layout.attributedString?.enumerateAttributes(in: NSMakeRange(line.range.location, line.range.length), options: []) { attributes, range, _ in
// if range == strikethrough.range, let color = attributes[NSAttributedString.Key.foregroundColor] as? UIColor {
// textColor = color
// }
// }
// if let textColor = textColor {
// context.setFillColor(textColor.cgColor)
// }
// let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
// context.fill(CGRect(x: frame.minX, y: frame.minY - 5.0, width: frame.width, height: 1.0))
// }
// }
if !line.spoilers.isEmpty {
if layout.displaySpoilers {

View File

@ -10,6 +10,7 @@ import PersistentStringHash
private let productIdentifiers = [
"org.telegram.telegramPremium.annual",
"org.telegram.telegramPremium.semiannual",
"org.telegram.telegramPremium.monthly",
"org.telegram.telegramPremium.twelveMonths",
"org.telegram.telegramPremium.sixMonths",
@ -17,7 +18,7 @@ private let productIdentifiers = [
]
private func isSubscriptionProductId(_ id: String) -> Bool {
return id.hasSuffix(".monthly") || id.hasSuffix(".annual")
return id.hasSuffix(".monthly") || id.hasSuffix(".annual") || id.hasSuffix(".semiannual")
}
private extension NSDecimalNumber {
@ -30,6 +31,22 @@ private extension NSDecimalNumber {
raiseOnUnderflow: false,
raiseOnDivideByZero: false))
}
func prettyPrice() -> NSDecimalNumber {
return self.multiplying(by: NSDecimalNumber(value: 2))
.rounding(accordingToBehavior:
NSDecimalNumberHandler(
roundingMode: .plain,
scale: Int16(0),
raiseOnExactness: false,
raiseOnOverflow: false,
raiseOnUnderflow: false,
raiseOnDivideByZero: false
)
)
.dividing(by: NSDecimalNumber(value: 2))
.subtracting(NSDecimalNumber(value: 0.01))
}
}
public final class InAppPurchaseManager: NSObject {
@ -57,7 +74,7 @@ public final class InAppPurchaseManager: NSObject {
} else if #available(iOS 11.2, *) {
return self.skProduct.subscriptionPeriod != nil
} else {
return self.id.hasSuffix(".monthly") || self.id.hasSuffix(".annual")
return self.id.hasSuffix(".monthly") || self.id.hasSuffix(".annual") || self.id.hasSuffix(".semiannual")
}
}
@ -66,13 +83,27 @@ public final class InAppPurchaseManager: NSObject {
}
public func pricePerMonth(_ monthsCount: Int) -> String {
let price = self.skProduct.price.dividing(by: NSDecimalNumber(value: monthsCount)).round(2)
let price = self.skProduct.price.dividing(by: NSDecimalNumber(value: monthsCount)).prettyPrice().round(2)
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) ?? ""
let prettierPrice = price
.multiplying(by: NSDecimalNumber(value: 2))
.rounding(accordingToBehavior:
NSDecimalNumberHandler(
roundingMode: .up,
scale: Int16(0),
raiseOnExactness: false,
raiseOnOverflow: false,
raiseOnUnderflow: false,
raiseOnDivideByZero: false
)
)
.dividing(by: NSDecimalNumber(value: 2))
.subtracting(NSDecimalNumber(value: 0.01))
return self.numberFormatter.string(from: prettierPrice) ?? ""
}
public var priceValue: NSDecimalNumber {

View File

@ -548,7 +548,6 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
).start(next: { [weak self] products, peer in
if let strongSelf = self {
var gifts: [PremiumGiftProduct] = []
for option in strongSelf.options {
if let product = products.first(where: { $0.id == option.storeProductId }), !product.isSubscription {

View File

@ -305,6 +305,27 @@ struct PremiumIntroConfiguration {
}
}
private struct PremiumProduct: Equatable {
let option: PremiumPromoConfiguration.PremiumProductOption
let storeProduct: InAppPurchaseManager.Product
var id: String {
return self.storeProduct.id
}
var months: Int32 {
return self.option.months
}
var price: String {
return self.storeProduct.price
}
var pricePerMonth: String {
return self.storeProduct.pricePerMonth(Int(self.months))
}
}
final class PremiumOptionComponent: CombinedComponent {
let title: String
let subtitle: String
@ -419,7 +440,7 @@ final class PremiumOptionComponent: CombinedComponent {
)
var spacing: CGFloat = 0.0
var subtitleHeight: CGFloat = 0.0
var subtitleSize = CGSize()
if !component.subtitle.isEmpty {
spacing = 2.0
@ -447,7 +468,7 @@ final class PremiumOptionComponent: CombinedComponent {
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
subtitleSize = subtitle.size
insets.top -= 2.0
insets.bottom -= 2.0
@ -512,10 +533,19 @@ 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 + spacing + subtitleHeight + insets.bottom)
let size = CGSize(width: context.availableSize.width, height: insets.top + title.size.height + spacing + subtitleSize.height + insets.bottom)
let distance = context.availableSize.width - insets.left - insets.right - label.size.width - subtitleSize.width
let labelY: CGFloat
if distance > 8.0 {
labelY = size.height / 2.0
} else {
labelY = insets.top + title.size.height / 2.0
}
context.add(label
.position(CGPoint(x: context.availableSize.width - insets.right - label.size.width / 2.0, y: size.height / 2.0))
.position(CGPoint(x: context.availableSize.width - insets.right - label.size.width / 2.0, y: labelY))
)
context.add(check
@ -970,20 +1000,22 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let source: PremiumSource
let isPremium: Bool?
let otherPeerName: String?
let products: [InAppPurchaseManager.Product]?
let products: [PremiumProduct]?
let selectedProductId: String?
let promoConfiguration: PremiumPromoConfiguration?
let present: (ViewController) -> Void
let selectProduct: (String) -> Void
let buy: () -> Void
let updateIsFocused: (Bool) -> Void
init(context: AccountContext, source: PremiumSource, isPremium: Bool?, otherPeerName: String?, products: [InAppPurchaseManager.Product]?, selectedProductId: String?, present: @escaping (ViewController) -> Void, selectProduct: @escaping (String) -> Void, buy: @escaping () -> Void, updateIsFocused: @escaping (Bool) -> Void) {
init(context: AccountContext, source: PremiumSource, isPremium: Bool?, otherPeerName: String?, products: [PremiumProduct]?, selectedProductId: String?, promoConfiguration: PremiumPromoConfiguration?, present: @escaping (ViewController) -> Void, selectProduct: @escaping (String) -> Void, buy: @escaping () -> Void, updateIsFocused: @escaping (Bool) -> Void) {
self.context = context
self.source = source
self.isPremium = isPremium
self.otherPeerName = otherPeerName
self.products = products
self.selectedProductId = selectedProductId
self.promoConfiguration = promoConfiguration
self.present = present
self.selectProduct = selectProduct
self.buy = buy
@ -1009,6 +1041,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
if lhs.selectedProductId != rhs.selectedProductId {
return false
}
if lhs.promoConfiguration != rhs.promoConfiguration {
return false
}
return true
}
@ -1016,14 +1051,13 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
final class State: ComponentState {
private let context: AccountContext
var products: [InAppPurchaseManager.Product]?
var products: [PremiumProduct]?
var selectedProductId: String?
var isPremium: Bool?
private var disposable: Disposable?
private(set) var configuration = PremiumIntroConfiguration.defaultValue
private(set) var promoConfiguration: PremiumPromoConfiguration?
private var stickersDisposable: Disposable?
private var preloadDisposableSet = DisposableSet()
@ -1042,13 +1076,11 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
super.init()
self.disposable = (context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Configuration.App(),
TelegramEngine.EngineData.Item.Configuration.PremiumPromo()
TelegramEngine.EngineData.Item.Configuration.App()
)
|> deliverOnMainQueue).start(next: { [weak self] appConfiguration, promoConfiguration in
|> deliverOnMainQueue).start(next: { [weak self] appConfiguration in
if let strongSelf = self {
strongSelf.configuration = PremiumIntroConfiguration.with(appConfiguration: appConfiguration)
strongSelf.promoConfiguration = promoConfiguration
strongSelf.updated(transition: .immediate)
if let identifier = source.identifier {
@ -1070,10 +1102,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_show", data: json)
}
}
for (_, video) in promoConfiguration.videos {
strongSelf.preloadDisposableSet.add(preloadVideoResource(postbox: context.account.postbox, resourceReference: .standalone(resource: video.resource), duration: 3.0).start())
}
}
})
@ -1253,7 +1281,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let shortestOptionPrice: (Int64, NSDecimalNumber)
if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) {
shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
shortestOptionPrice = (Int64(Float(product.storeProduct.priceCurrencyAndAmount.amount)), product.storeProduct.priceValue)
} else {
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
}
@ -1261,16 +1289,17 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
var i = 0
for product in products {
let giftTitle: String
let months: Float
let months = product.months
if product.id.hasSuffix(".monthly") {
giftTitle = strings.Premium_Monthly
months = 1
} else if product.id.hasSuffix(".semiannual") {
giftTitle = strings.Premium_Semiannual
} else {
giftTitle = strings.Premium_Annual
months = 12
}
let discountValue = Int((1.0 - Float(product.priceCurrencyAndAmount.amount) / months / Float(shortestOptionPrice.0)) * 100.0)
let discountValue = Int((1.0 - Float(product.storeProduct.priceCurrencyAndAmount.amount) / Float(months) / Float(shortestOptionPrice.0)) * 100.0)
let discount: String
if discountValue > 0 {
discount = "-\(discountValue)%"
@ -1278,12 +1307,12 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
discount = ""
}
let defaultPrice = product.defaultPrice(shortestOptionPrice.1, monthsCount: Int(months))
let defaultPrice = product.storeProduct.defaultPrice(shortestOptionPrice.1, monthsCount: Int(months))
var subtitle = ""
var pricePerMonth = product.price
if months > 1 {
pricePerMonth = product.pricePerMonth(Int(months))
pricePerMonth = product.storeProduct.pricePerMonth(Int(months))
if discountValue > 0 {
subtitle = "**\(defaultPrice)** \(product.price)"
@ -1552,7 +1581,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let termsString: MultilineTextComponent.TextContent
if isGiftView {
termsString = .plain(NSAttributedString())
} else if let promoConfiguration = context.state.promoConfiguration {
} else if let promoConfiguration = context.component.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 {
@ -1723,8 +1752,10 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
var inProgress = false
var products: [InAppPurchaseManager.Product]?
var selectedProductId: String?
private(set) var promoConfiguration: PremiumPromoConfiguration?
private(set) var products: [PremiumProduct]?
private(set) var selectedProductId: String?
var isPremium: Bool?
var otherPeerName: String?
@ -1740,6 +1771,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
private var disposable: Disposable?
private var paymentDisposable = MetaDisposable()
private var activationDisposable = MetaDisposable()
private var preloadDisposableSet = DisposableSet()
var price: String? {
return self.products?.first(where: { $0.id == self.selectedProductId })?.price
@ -1792,20 +1824,37 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
self.disposable = combineLatest(
queue: Queue.mainQueue(),
availableProducts,
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.PremiumPromo()),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in
return peer?.isPremium ?? false
},
otherPeerName
).start(next: { [weak self] products, isPremium, otherPeerName in
).start(next: { [weak self] availableProducts, promoConfiguration, isPremium, otherPeerName in
if let strongSelf = self {
strongSelf.promoConfiguration = promoConfiguration
let hadProducts = strongSelf.products != nil
strongSelf.products = products.filter { $0.isSubscription }
if !hadProducts {
strongSelf.selectedProductId = strongSelf.products?.last?.id
var products: [PremiumProduct] = []
for option in promoConfiguration.premiumProductOptions {
if let product = availableProducts.first(where: { $0.id == option.storeProductId }), product.isSubscription {
products.append(PremiumProduct(option: option, storeProduct: product))
}
}
strongSelf.products = products
strongSelf.isPremium = isPremium
strongSelf.otherPeerName = otherPeerName
if !hadProducts {
strongSelf.selectedProductId = strongSelf.products?.last?.id
for (_, video) in promoConfiguration.videos {
strongSelf.preloadDisposableSet.add(preloadVideoResource(postbox: context.account.postbox, resourceReference: .standalone(resource: video.resource), duration: 3.0).start())
}
}
strongSelf.updated(transition: .immediate)
}
})
@ -1833,6 +1882,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
self.paymentDisposable.dispose()
self.activationDisposable.dispose()
self.emojiFileDisposable?.dispose()
self.preloadDisposableSet.dispose()
}
func buy() {
@ -1851,7 +1901,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|> deliverOnMainQueue).start(next: { [weak self] available in
if let strongSelf = self {
if available {
strongSelf.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct)
strongSelf.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct.storeProduct)
|> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self, case .purchased = status {
strongSelf.activationDisposable.set((strongSelf.context.account.postbox.peerView(id: strongSelf.context.account.peerId)
@ -2153,6 +2203,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
otherPeerName: state.otherPeerName,
products: state.products,
selectedProductId: state.selectedProductId,
promoConfiguration: state.promoConfiguration,
present: context.component.present,
selectProduct: { [weak state] productId in
state?.selectProduct(productId)

View File

@ -539,8 +539,18 @@ private func privacyAndSecurityControllerEntries(
return entries
}
class PrivacyAndSecurityControllerImpl: ItemListController {
class PrivacyAndSecurityControllerImpl: ItemListController, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
var authorizationCompletion: (ASAuthorizationCredential) -> Void = { _ in }
@available(iOS 13.0, *)
public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
self.authorizationCompletion(authorization)
}
@available(iOS 13.0, *)
public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return self.view.window!
}
}
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 {
@ -1083,6 +1093,27 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
let codeController = AuthorizationSequenceCodeEntryController(presentationData: presentationData, openUrl: { _ in }, back: {
dismissCodeControllerImpl?()
})
let emailChangeCompletion = { [weak codeController] in
codeController?.animateSuccess()
Queue.mainQueue().after(0.75) {
if let navigationController = getNavigationControllerImpl?() {
let controllers = navigationController.viewControllers.filter { controller in
if controller is AuthorizationSequenceEmailEntryController || controller is AuthorizationSequenceCodeEntryController {
return false
} else {
return true
}
}
navigationController.setViewControllers(controllers, animated: true)
navigationController.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .emoji(name: "IntroLetter", text: presentationData.strings.Login_EmailChanged), elevatedLayout: false, animateInAsReplacement: false, action: { _ in
return false
}))
}
}
}
codeController.loginWithCode = { [weak codeController] code in
actionsDisposable.add((verifyLoginEmailChange(account: context.account, code: .emailCode(code))
|> deliverOnMainQueue).start(error: { error in
@ -1120,39 +1151,57 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
}
}
}, completed: { [weak codeController] in
codeController?.animateSuccess()
Queue.mainQueue().after(0.75) {
if let navigationController = getNavigationControllerImpl?() {
let controllers = navigationController.viewControllers.filter { controller in
if controller is AuthorizationSequenceEmailEntryController || controller is AuthorizationSequenceCodeEntryController {
return false
} else {
return true
}, completed: {
emailChangeCompletion()
}))
}
}
navigationController.setViewControllers(controllers, animated: true)
codeController.signInWithApple = { [weak controller, weak codeController] in
if #available(iOS 13.0, *) {
let appleIdProvider = ASAuthorizationAppleIDProvider()
let passwordProvider = ASAuthorizationPasswordProvider()
let request = appleIdProvider.createRequest()
navigationController.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .emoji(name: "IntroLetter", text: presentationData.strings.Login_EmailChanged), elevatedLayout: false, animateInAsReplacement: false, action: { _ in
return false
let passwordRequest = passwordProvider.createRequest()
let authorizationController = ASAuthorizationController(authorizationRequests: [request, passwordRequest])
authorizationController.delegate = controller
authorizationController.presentationContextProvider = controller
authorizationController.performRequests()
controller.authorizationCompletion = { [weak controller, weak codeController] credentials in
switch authorization.credential {
case let appleIdCredential as ASAuthorizationAppleIDCredential:
guard let tokenData = appleIdCredential.identityToken, let token = String(data: tokenData, encoding: .utf8) else {
codeController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return
}
actionsDisposable.add((verifyLoginEmailChange(account: context.account, code: .appleToken(token))
|> deliverOnMainQueue).start(error: { error in
let text: String
switch error {
case .limitExceeded:
text = presentationData.strings.Login_CodeFloodError
case .generic, .codeExpired:
text = presentationData.strings.Login_UnknownError
case .invalidCode:
text = presentationData.strings.Login_InvalidCodeError
case .timeout:
text = presentationData.strings.Login_NetworkError
case .invalidEmailToken:
text = presentationData.strings.Login_InvalidEmailTokenError
case .emailNotAllowed:
text = presentationData.strings.Login_EmailNotAllowedError
}
codeController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, completed: { [weak controller] in
controller?.authorizationCompletion = nil
emailChangeCompletion()
}))
default:
break
}
}
}))
}
codeController.signInWithApple = {
// if #available(iOS 13.0, *) {
// let appleIdProvider = ASAuthorizationAppleIDProvider()
// let passwordProvider = ASAuthorizationPasswordProvider()
// let request = appleIdProvider.createRequest()
//
// let passwordRequest = passwordProvider.createRequest()
//
// let authorizationController = ASAuthorizationController(authorizationRequests: [request, passwordRequest])
// authorizationController.delegate = strongSelf
// authorizationController.presentationContextProvider = strongSelf
// authorizationController.performRequests()
// }
}
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)

View File

@ -432,7 +432,7 @@ public func verifyLoginEmailChange(account: Account, code: AuthorizationCode.Ema
}
}
|> mapToSignal { _ -> Signal<Never, AuthorizationEmailVerificationError> in
return .never()
return .complete()
}
}