diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index a9b4783420..d5f9f3b2d6 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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?"; diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift index 4bc44291d0..db80bf494f 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift @@ -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 } } @@ -52,9 +49,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { self.hasActiveInput = true 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) } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift index 80af48a4ef..1759355110 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift @@ -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() } })) } @@ -549,6 +551,8 @@ public final class AuthorizationSequenceController: NavigationController, MFMail controller?.inProgress = true + strongSelf.currentEmail = email + strongSelf.actionDisposable.set((sendLoginEmailCode(account: strongSelf.account, email: email) |> deliverOnMainQueue).start(error: { error in if let strongSelf = self, let controller = controller { @@ -570,12 +574,8 @@ 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 + }, completed: { controller?.inProgress = false - - if let strongSelf = self { - strongSelf.currentEmail = email - } })) } controller.signInWithApple = { [weak self] in @@ -645,7 +645,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 +654,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: diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryController.swift index fe838c9e05..9a763b828d 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryController.swift @@ -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) } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift index e9b005f837..a88c675073 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift @@ -77,6 +77,14 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText var inProgress: Bool = false { didSet { self.codeField.alpha = self.inProgress ? 0.6 : 1.0 + + if self.inProgress != oldValue { + if self.inProgress { + self.proceedNode.transitionToProgress() + } else { + self.proceedNode.transitionFromProgress() + } + } } } @@ -87,7 +95,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 +196,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 +212,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) diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift index 6427aa57fe..4a0b573690 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift @@ -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,16 +260,27 @@ 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 { - let (code, formattedNumber) = self.controllerNode.formattedCodeAndNumber + 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) - confirmationController.proceed = { [weak self] in - if let strongSelf = self { - strongSelf.loginWithNumber?(strongSelf.controllerNode.currentNumber, strongSelf.controllerNode.syncContacts) + 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.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)) } - (self.navigationController as? NavigationController)?.presentOverlay(controller: confirmationController, inGlobal: true, blockInteraction: true) - self.confirmationController = confirmationController } } else { self.hapticFeedback.error() diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift index c1798516aa..fe7a92f9b0 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift @@ -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 @@ -479,9 +478,7 @@ 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) } @@ -696,7 +707,12 @@ final class PhoneConfirmationController: ViewController { var proceed: () -> Void = {} - class Node: ASDisplayNode { + 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 @@ -913,16 +945,33 @@ final class PhoneConfirmationController: ViewController { 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 maxWidth = layout.size.width - 20.0 + if !hadLayout { + var fontSize = 34.0 + if layout.size.width < 375.0 { + fontSize = 30.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 codeSize = self.codeTargetNode.updateLayout(backgroundSize) - let numberSize = self.phoneTargetNode.updateLayout(backgroundSize) + let spacing: CGFloat = 10.0 - let totalWidth = codeSize.width + numberSize.width + 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() diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift index e321beda94..cf0dded3dd 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift @@ -15,6 +15,8 @@ final class AuthorizationSequenceSignUpController: ViewController { return self.displayNode as! AuthorizationSequenceSignUpControllerNode } + private var validLayout: ContainerViewLayout? + private let presentationData: PresentationData private let back: () -> Void @@ -30,12 +32,7 @@ final class AuthorizationSequenceSignUpController: 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 } } @@ -81,6 +78,19 @@ final class AuthorizationSequenceSignUpController: ViewController { })]), in: .window(.root)) } + 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)) + } + } + override public func loadDisplayNode() { let currentAvatarMixin = Atomic(value: nil) @@ -138,6 +148,13 @@ final class AuthorizationSequenceSignUpController: 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) } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift index 3b08f55124..0e2ff4d4ec 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift @@ -67,6 +67,14 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel didSet { self.firstNameField.alpha = self.inProgress ? 0.6 : 1.0 self.lastNameField.alpha = self.inProgress ? 0.6 : 1.0 + + if self.inProgress != oldValue { + if self.inProgress { + self.proceedNode.transitionToProgress() + } else { + self.proceedNode.transitionFromProgress() + } + } } } @@ -206,88 +214,19 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel if let inputHeight = layout.inputHeight { insets.bottom += max(inputHeight, layout.standardInputHeight) } + + let additionalBottomInset: CGFloat = layout.size.width > 320.0 ? 80.0 : 10.0 self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_InfoTitle, font: Font.semibold(28.0), textColor: self.theme.list.itemPrimaryTextColor) let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude)) -// let additionalTitleSpacing: CGFloat -// if titleSize.width > layout.size.width - 160.0 { -// additionalTitleSpacing = 44.0 -// } else { -// additionalTitleSpacing = 0.0 -// } - let fieldHeight: CGFloat = 54.0 let sideInset: CGFloat = 24.0 let innerInset: CGFloat = 16.0 -// let minimalNoticeSpacing: CGFloat = 11.0 -// let maxNoticeSpacing: CGFloat = 35.0 let noticeSize = self.currentOptionNode.measure(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude)) let termsSize = self.termsNode.updateLayout(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude)) -// -// let noticeHeight: CGFloat = noticeSize.height + (self.termsNode.isHidden ? 0.0 : (termsSize.height + 4.0)) -// -// let minimalTermsOfServiceSpacing: CGFloat = 6.0 -// let maxTermsOfServiceSpacing: CGFloat = 20.0 -// let minTrailingSpacing: CGFloat = 10.0 -// -// let inputHeight = inputFieldsHeight -// let essentialHeight = additionalTitleSpacing + titleSize.height + minimalTitleSpacing + inputHeight + minimalNoticeSpacing + noticeHeight -// let additionalHeight = minimalTermsOfServiceSpacing + minTrailingSpacing -// -// let navigationHeight: CGFloat -// if essentialHeight + additionalHeight > availableHeight || availableHeight * 0.66 - inputHeight < additionalHeight { -// navigationHeight = min(floor(availableHeight * 0.3), availableHeight - inputFieldsHeight) -// } else { -// navigationHeight = floor(availableHeight * 0.3) -// } -// -// let titleOffset: CGFloat -// if navigationHeight * 0.5 < titleSize.height + minimalTitleSpacing { -// titleOffset = max(navigationBarHeight, floor((navigationHeight - titleSize.height) / 2.0)) -// } else { -// titleOffset = max(navigationBarHeight, max(navigationHeight * 0.5, navigationHeight - maxTitleSpacing - titleSize.height)) -// } -// transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleSize.width) / 2.0), y: titleOffset), size: titleSize)) -// -// let avatarSize: CGSize = CGSize(width: 110.0, height: 110.0) -// let addPhotoButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - avatarSize.width) / 2.0), y: navigationHeight + 10.0), size: avatarSize) -// transition.updateFrame(node: self.addPhotoButton, frame: addPhotoButtonFrame) -// self.currentPhotoNode.frame = CGRect(origin: CGPoint(), size: addPhotoButtonFrame.size) -// -// -// -// let firstFieldFrame = CGRect(origin: CGPoint(x: sideInset + innerInset, y: navigationHeight + 3.0), size: CGSize(width: layout.size.width - (sideInset + innerInset) * 2.0, height: fieldHeight)) -// transition.updateFrame(node: self.firstNameField, frame: firstFieldFrame) -// -// let lastFieldFrame = CGRect(origin: CGPoint(x: firstFieldFrame.minX, y: firstFieldFrame.maxY), size: CGSize(width: firstFieldFrame.size.width, height: fieldHeight)) -// transition.updateFrame(node: self.lastNameField, frame: lastFieldFrame) -// -// transition.updateFrame(node: self.firstSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: firstFieldFrame.maxY), size: CGSize(width: layout.size.width - sideInset * 2.0, height: UIScreenPixel))) -// transition.updateFrame(node: self.lastSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: lastFieldFrame.maxY), size: CGSize(width: layout.size.width - sideInset * 2.0, height: UIScreenPixel))) -// -// let additionalAvailableHeight = max(1.0, availableHeight - lastFieldFrame.maxY) -// let additionalAvailableSpacing = max(1.0, additionalAvailableHeight - noticeHeight) -// let noticeSpacingFactor = maxNoticeSpacing / (maxNoticeSpacing + maxTermsOfServiceSpacing + minTrailingSpacing) -// let termsOfServiceSpacingFactor = maxTermsOfServiceSpacing / (maxNoticeSpacing + maxTermsOfServiceSpacing + minTrailingSpacing) -// -// let noticeSpacing: CGFloat -// let termsOfServiceSpacing: CGFloat -// if additionalAvailableHeight <= maxNoticeSpacing + noticeHeight + maxTermsOfServiceSpacing + minTrailingSpacing { -// termsOfServiceSpacing = min(floor(termsOfServiceSpacingFactor * additionalAvailableSpacing), maxTermsOfServiceSpacing) -// noticeSpacing = floor((additionalAvailableHeight - termsOfServiceSpacing - noticeHeight) / 2.0) -// } else { -// noticeSpacing = min(floor(noticeSpacingFactor * additionalAvailableSpacing), maxNoticeSpacing) -// termsOfServiceSpacing = min(floor(termsOfServiceSpacingFactor * additionalAvailableSpacing), maxTermsOfServiceSpacing) -// } -// -// let currentOptionFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - noticeSize.width) / 2.0), y: lastFieldFrame.maxY + max(0.0, noticeSpacing)), size: noticeSize) -// transition.updateFrame(node: self.currentOptionNode, frame: currentOptionFrame) -// let termsFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - termsSize.width) / 2.0), y: layout.size.height - insets.bottom - termsSize.height - 4.0), size: termsSize) -// transition.updateFrame(node: self.termsNode, frame: termsFrame) -// let avatarSize: CGSize = CGSize(width: 110.0, height: 110.0) var items: [AuthorizationLayoutItem] = [] @@ -305,11 +244,19 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel items.append(AuthorizationLayoutItem(node: self.termsNode, size: termsSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 48.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) - let proceedHeight = self.proceedNode.updateLayout(width: layout.size.width - 48.0, transition: transition) - let proceedSize = CGSize(width: layout.size.width - 48.0, height: proceedHeight) - items.append(AuthorizationLayoutItem(node: self.proceedNode, size: proceedSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 20.0, maxValue: 20.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 - 20.0)), items: items, transition: transition, failIfDoesNotFit: false) + if layout.size.width > 320.0 { + self.proceedNode.isHidden = false + + let inset: CGFloat = 24.0 + let proceedHeight = self.proceedNode.updateLayout(width: layout.size.width - 48.0, transition: transition) + let proceedSize = CGSize(width: layout.size.width - 48.0, height: proceedHeight) + transition.updateFrame(node: self.proceedNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - proceedSize.width) / 2.0), y: layout.size.height - insets.bottom - proceedSize.height - inset), size: proceedSize)) + } else { + insets.top = navigationBarHeight + self.proceedNode.isHidden = true + } + + 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) } func activateInput() { diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index 1e318d6804..75d5733895 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -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 { diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift index 234029b427..07830dcede 100644 --- a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -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 { diff --git a/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift b/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift index 1eb5619f24..1856b15dc5 100644 --- a/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift +++ b/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift @@ -75,9 +75,10 @@ class EmojiHeaderComponent: Component { override init(frame: CGRect) { self.statusView = ComponentHostView() - + super.init(frame: frame) - + + self.statusView.isHidden = true self.addSubview(self.statusView) self.disablesInteractiveModalDismiss = true @@ -87,13 +88,29 @@ class EmojiHeaderComponent: Component { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + var scheduledAnimateIn = false + override func didMoveToWindow() { + super.didMoveToWindow() + + if self.scheduledAnimateIn { + self.animateIn() + self.scheduledAnimateIn = false + } + } func animateIn() { guard let animateFrom = self.animateFrom, var containerView = self.containerView else { return } + + guard let _ = self.window else { + self.scheduledAnimateIn = true + return + } - containerView = containerView.subviews[2].subviews[1] + self.statusView.isHidden = false + containerView = containerView.subviews[1].subviews[1] let initialPosition = self.statusView.center let targetPosition = self.statusView.superview!.convert(self.statusView.center, to: containerView) diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index 780dcc5039..c5e48d9060 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -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 { diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 405d534f73..2fc6e2d2d7 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -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,15 +1051,14 @@ 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) @@ -2022,7 +2072,9 @@ private final class PremiumIntroScreenComponent: CombinedComponent { ) let titleString: String - if case .giftTerms = context.component.source { + if case .emojiStatus = context.component.source { + titleString = environment.strings.Premium_Title + } else if case .giftTerms = context.component.source { titleString = environment.strings.Premium_Title } else if case .gift = context.component.source { titleString = environment.strings.Premium_GiftedTitle @@ -2154,6 +2206,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) @@ -2454,9 +2507,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { view.animateFrom = sourceView view.containerView = self.containerView - Queue.mainQueue().after(0.1) { - view.animateIn() - } + view.animateIn() self.sourceView = nil self.containerView = nil diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index 08a458e78b..e19d407c91 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -539,8 +539,18 @@ private func privacyAndSecurityControllerEntries( return entries } -class PrivacyAndSecurityControllerImpl: ItemListController { - +class PrivacyAndSecurityControllerImpl: ItemListController, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { + var authorizationCompletion: ((Any) -> Void)? + + @available(iOS 13.0, *) + public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + self.authorizationCompletion?(authorization.credential) + } + + @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? = nil) -> ViewController { @@ -1068,6 +1078,28 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting updateHasTwoStepAuth() } + let emailChangeCompletion: (AuthorizationSequenceCodeEntryController?) -> Void = { codeController in + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + 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 + })) + } + } + } + setupEmailImpl = { emailPattern in let presentationData = context.sharedContext.currentPresentationData.with { $0 } var dismissEmailControllerImpl: (() -> Void)? @@ -1083,6 +1115,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting let codeController = AuthorizationSequenceCodeEntryController(presentationData: presentationData, openUrl: { _ in }, back: { dismissCodeControllerImpl?() }) + codeController.loginWithCode = { [weak codeController] code in actionsDisposable.add((verifyLoginEmailChange(account: context.account, code: .emailCode(code)) |> deliverOnMainQueue).start(error: { error in @@ -1121,39 +1154,9 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting } } }, 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 - } - } - 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 - })) - } - } + emailChangeCompletion(codeController) })) } - 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) dismissCodeControllerImpl = { [weak codeController] in @@ -1181,8 +1184,56 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting emailController?.inProgress = false })) } - emailController.signInWithApple = { - + emailController.signInWithApple = { [weak controller, weak emailController] in + 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 = controller + authorizationController.presentationContextProvider = controller + authorizationController.performRequests() + + controller?.authorizationCompletion = { [weak controller, weak emailController] credential in + guard let credential = credential as? ASAuthorizationCredential else { + return + } + switch credential { + case let appleIdCredential as ASAuthorizationAppleIDCredential: + guard let tokenData = appleIdCredential.identityToken, let token = String(data: tokenData, encoding: .utf8) else { + emailController?.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 + } + emailController?.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(nil) + })) + default: + break + } + } + } } emailController.updateData(appleSignInAllowed: true) pushControllerImpl?(emailController, true) diff --git a/submodules/TelegramCore/Sources/Authorization.swift b/submodules/TelegramCore/Sources/Authorization.swift index 9f8d40caa1..6253c8107e 100644 --- a/submodules/TelegramCore/Sources/Authorization.swift +++ b/submodules/TelegramCore/Sources/Authorization.swift @@ -432,7 +432,7 @@ public func verifyLoginEmailChange(account: Account, code: AuthorizationCode.Ema } } |> mapToSignal { _ -> Signal in - return .never() + return .complete() } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 94af790fcf..9757f33ab1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -2023,7 +2023,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { var displayAvatarContextMenu: ((ASDisplayNode, ContextGesture?) -> Void)? var displayCopyContextMenu: ((ASDisplayNode, Bool, Bool) -> Void)? - var displayPremiumIntro: ((UIView, PeerEmojiStatus?, TelegramMediaFile?, String?, Bool) -> Void)? + var displayPremiumIntro: ((UIView, PeerEmojiStatus?, Signal<(TelegramMediaFile, String)?, NoError>, Bool) -> Void)? var navigationTransition: PeerInfoHeaderNavigationTransition? @@ -2034,8 +2034,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let animationRenderer: MultiAnimationRenderer var emojiStatusPackDisposable = MetaDisposable() - var emojiStatusFile: TelegramMediaFile? - var emojiStatusPackTitle: String? + var emojiStatusFileAndPackTitle = Promise<(TelegramMediaFile, String)?>() init(context: AccountContext, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool) { self.context = context @@ -2214,7 +2213,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { } func invokeDisplayPremiumIntro() { - self.displayPremiumIntro?(self.isAvatarExpanded ? self.titleExpandedCredibilityIconView : self.titleCredibilityIconView, nil, nil, nil, self.isAvatarExpanded) + self.displayPremiumIntro?(self.isAvatarExpanded ? self.titleExpandedCredibilityIconView : self.titleCredibilityIconView, nil, .never(), self.isAvatarExpanded) } func initiateAvatarExpansion(gallery: Bool, first: Bool) { @@ -2372,7 +2371,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { guard let strongSelf = self else { return } - strongSelf.displayPremiumIntro?(strongSelf.titleCredibilityIconView, currentEmojiStatus, strongSelf.emojiStatusFile, strongSelf.emojiStatusPackTitle, false) + strongSelf.displayPremiumIntro?(strongSelf.titleCredibilityIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), false) }, longTapAction: { [weak self] in guard let strongSelf = self else { @@ -2385,7 +2384,8 @@ final class PeerInfoHeaderNode: ASDisplayNode { } if let emojiFile = emojiFile { - strongSelf.emojiStatusFile = nil + strongSelf.emojiStatusFileAndPackTitle.set(.never()) + for attribute in emojiFile.attributes { if case let .CustomEmoji(_, _, packReference) = attribute, let packReference = packReference { strongSelf.emojiStatusPackDisposable.set((strongSelf.context.engine.stickers.loadedStickerPack(reference: packReference, forceActualized: false) @@ -2406,15 +2406,13 @@ final class PeerInfoHeaderNode: ASDisplayNode { guard let strongSelf = self else { return } - strongSelf.emojiStatusFile = fileAndPackTitle?.0 - strongSelf.emojiStatusPackTitle = fileAndPackTitle?.1 + strongSelf.emojiStatusFileAndPackTitle.set(.single(fileAndPackTitle)) })) break } } } else { - strongSelf.emojiStatusFile = nil - strongSelf.emojiStatusPackDisposable.set(nil) + strongSelf.emojiStatusFileAndPackTitle.set(.never()) } } )), @@ -2433,7 +2431,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { guard let strongSelf = self else { return } - strongSelf.displayPremiumIntro?(strongSelf.titleExpandedCredibilityIconView, currentEmojiStatus, strongSelf.emojiStatusFile, strongSelf.emojiStatusPackTitle, true) + strongSelf.displayPremiumIntro?(strongSelf.titleExpandedCredibilityIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), true) }, longTapAction: { [weak self] in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index d02e8e19d6..d7ccde14a8 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -3096,7 +3096,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate })) } - self.headerNode.displayPremiumIntro = { [weak self] sourceView, _, _, _, _ in + self.headerNode.displayPremiumIntro = { [weak self] sourceView, _, _, _ in guard let strongSelf = self else { return } @@ -3131,7 +3131,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } else { screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext) - self.headerNode.displayPremiumIntro = { [weak self] sourceView, peerStatus, emojiStatusFile, emojiPackTitle, white in + self.headerNode.displayPremiumIntro = { [weak self] sourceView, peerStatus, emojiStatusFileAndPackTitle, white in guard let strongSelf = self else { return } @@ -3146,18 +3146,31 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let strongSelf = self else { return } - let source: PremiumSource - if let peerStatus = peerStatus, let emojiStatusFile = emojiStatusFile { - source = .emojiStatus(strongSelf.peerId, peerStatus.fileId, emojiStatusFile, emojiPackTitle) + let source: Signal + if let peerStatus = peerStatus { + source = emojiStatusFileAndPackTitle + |> take(1) + |> mapToSignal { emojiStatusFileAndPackTitle -> Signal in + if let (file, packTitle) = emojiStatusFileAndPackTitle { + return .single(.emojiStatus(strongSelf.peerId, peerStatus.fileId, file, packTitle)) + } else { + return .complete() + } + } } else { - source = .profile(strongSelf.peerId) + source = .single(.profile(strongSelf.peerId)) } - let controller = PremiumIntroScreen(context: strongSelf.context, source: source) - controller.sourceView = sourceView - controller.containerView = strongSelf.controller?.navigationController?.view - controller.animationColor = white ? .white : strongSelf.presentationData.theme.list.itemAccentColor - strongSelf.controller?.push(controller) + let _ = source.start(next: { [weak self] source in + guard let strongSelf = self else { + return + } + let controller = PremiumIntroScreen(context: strongSelf.context, source: source) + controller.sourceView = sourceView + controller.containerView = strongSelf.controller?.navigationController?.view + controller.animationColor = white ? .white : strongSelf.presentationData.theme.list.itemAccentColor + strongSelf.controller?.push(controller) + }) }) } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 4baca26a92..aba787fef7 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -802,6 +802,10 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String for basePath in baseTelegramMePaths { for scheme in schemes { let basePrefix = scheme + basePath + "/" + var url = url + if (url.lowercased().hasPrefix(scheme) && url.lowercased().hasSuffix(".\(basePath)")) { + url = basePrefix + String(url[scheme.endIndex...]).replacingOccurrences(of: ".\(basePath)", with: "") + } if url.lowercased().hasPrefix(basePrefix) { if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])) { return resolveInternalUrl(context: context, url: internalUrl)