diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 74850a488d..4ffce82f61 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -321,13 +321,18 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch case .limitExceeded: f(.default) - let limitScreen = PremiumLimitScreen(context: context, subject: .pins, action: { + var dismissImpl: (() -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .pins, action: { + dismissImpl?() let premiumScreen = PremiumIntroScreen(context: context, action: { }) chatListController?.push(premiumScreen) }) - chatListController?.push(limitScreen) + chatListController?.push(controller) + dismissImpl = { [weak controller] in + controller?.dismiss() + } } }) }))) diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index c10053e0d4..909e7ae853 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -10,6 +10,7 @@ import ItemListUI import AccountContext import ItemListPeerActionItem import ChatListFilterSettingsHeaderItem +import PremiumUI private final class ChatListFilterPresetListControllerArguments { let context: AccountContext @@ -246,6 +247,17 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch var pushControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController) -> Void)? + let filtersWithCountsSignal = context.engine.peers.updatedChatListFilters() + |> distinctUntilChanged + |> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in + return .single(filters.map { filter -> (ChatListFilter, Int) in + return (filter, 0) + }) + } + + let filtersWithCounts = Promise<[(ChatListFilter, Int)]>() + filtersWithCounts.set(filtersWithCountsSignal) + let arguments = ChatListFilterPresetListControllerArguments(context: context, addSuggestedPresed: { title, data in let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in @@ -259,7 +271,42 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch }, openPreset: { preset in pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: { _ in })) }, addNew: { - pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: { _ in })) + let _ = combineLatest( + queue: Queue.mainQueue(), + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) + ), + filtersWithCounts.get() |> take(1) + ).start(next: { result, filters in + let (accountPeer, limits, premiumLimits) = result + let limit = limits.maxFoldersCount + let premiumLimit = premiumLimits.maxFoldersCount + if let accountPeer = accountPeer, accountPeer.isPremium { + if filters.count >= premiumLimit { + //printPremiumError + return + } + } else { + if filters.count >= limit { + var dismissImpl: (() -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .folders, action: { + dismissImpl?() + let controller = PremiumIntroScreen(context: context, action: { + + }) + pushControllerImpl?(controller) + }) + pushControllerImpl?(controller) + dismissImpl = { [weak controller] in + controller?.dismiss() + } + return + } + } + pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: { _ in })) + }) }, setItemWithRevealedOptions: { preset, fromPreset in updateState { state in var state = state @@ -296,15 +343,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch ]) presentControllerImpl?(actionSheet) }) - - let filtersWithCountsSignal = context.engine.peers.updatedChatListFilters() - |> distinctUntilChanged - |> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in - return .single(filters.map { filter -> (ChatListFilter, Int) in - return (filter, 0) - }) - } - + let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState]) |> map { preferences -> [ChatListFeaturedFilter] in guard let state = preferences.values[PreferencesKeys.chatListFiltersFeaturedState]?.get(ChatListFiltersFeaturedState.self) else { @@ -313,10 +352,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch return state.filters } |> distinctUntilChanged - - let filtersWithCounts = Promise<[(ChatListFilter, Int)]>() - filtersWithCounts.set(filtersWithCountsSignal) - + let updatedFilterOrder = Promise<[Int32]?>(nil) let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings]) diff --git a/submodules/PremiumUI/Resources/star.scn b/submodules/PremiumUI/Resources/star.scn index ad5f05949a..f0f760eb38 100644 Binary files a/submodules/PremiumUI/Resources/star.scn and b/submodules/PremiumUI/Resources/star.scn differ diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index da17b54bd5..bf60593631 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -14,6 +14,14 @@ import SolidRoundedButtonComponent import Markdown import SceneKit +private func deg2rad(_ number: Float) -> Float { + return number * .pi / 180 +} + +private func rad2deg(_ number: Float) -> Float { + return number * 180.0 / .pi +} + private class StarComponent: Component { static func ==(lhs: StarComponent, rhs: StarComponent) -> Bool { return true @@ -41,18 +49,97 @@ private class StarComponent: Component { self.sceneView = SCNView(frame: frame) self.sceneView.backgroundColor = .clear self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) + self.sceneView.isUserInteractionEnabled = false super.init(frame: frame) self.addSubview(self.sceneView) self.setup() + + let panGestureRecoginzer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:))) + self.addGestureRecognizer(panGestureRecoginzer) + + let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:))) + self.addGestureRecognizer(tapGestureRecoginzer) + + self.disablesInteractiveModalDismiss = true + self.disablesInteractiveTransitionGestureRecognizer = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + @objc private func handleTap(_ gesture: UITapGestureRecognizer) { + guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else { + return + } + + var left = true + if let view = gesture.view { + let point = gesture.location(in: view) + if point.x > view.frame.width / 2.0 { + left = false + } + } + + let initial = node.rotation + let target = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: left ? -0.6 : 0.6) + + let animation = CABasicAnimation(keyPath: "rotation") + animation.fromValue = NSValue(scnVector4: initial) + animation.toValue = NSValue(scnVector4: target) + animation.duration = 0.25 + animation.timingFunction = CAMediaTimingFunction(name: .easeOut) + animation.fillMode = .forwards + node.addAnimation(animation, forKey: "rotate") + + node.rotation = target + + Queue.mainQueue().after(0.25) { + node.rotation = initial + let springAnimation = CASpringAnimation(keyPath: "rotation") + springAnimation.fromValue = NSValue(scnVector4: target) + springAnimation.toValue = NSValue(scnVector4: SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: 0.0)) + springAnimation.mass = 1.0 + springAnimation.stiffness = 21.0 + springAnimation.damping = 5.8 + springAnimation.duration = springAnimation.settlingDuration * 0.8 + node.addAnimation(springAnimation, forKey: "rotate") + } + } + + private var previousAngle: Float = 0.0 + @objc private func handlePan(_ gesture: UIPanGestureRecognizer) { + guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else { + return + } + + switch gesture.state { + case .began: + self.previousAngle = 0.0 + case .changed: + let translation = gesture.translation(in: gesture.view) + let anglePan = deg2rad(Float(translation.x)) + + self.previousAngle = anglePan + node.rotation = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: self.previousAngle) + case .ended: + let velocity = gesture.velocity(in: gesture.view) + + var small = false + if (self.previousAngle < .pi / 2 && self.previousAngle > -.pi / 2) && abs(velocity.x) < 200 { + small = true + } + + self.playAppearanceAnimation(velocity: velocity.x, small: small) + node.rotation = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: 0.0) + default: + break + } + } + private func setup() { guard let scene = SCNScene(named: "star.scn") else { return @@ -77,11 +164,11 @@ private class StarComponent: Component { self.setupGradientAnimation() self.setupShineAnimation() - self.playAppearanceAnimation() + self.playAppearanceAnimation(boom: true) } private func setupGradientAnimation() { - guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: true) else { + guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else { return } guard let initial = node.geometry?.materials.first?.diffuse.contentsTransform else { @@ -124,21 +211,37 @@ private class StarComponent: Component { node.geometry?.materials.first?.emission.addAnimation(group, forKey: "shimmer") } - private func playAppearanceAnimation() { - guard let scene = self.sceneView.scene, let starNode = scene.rootNode.childNode(withName: "star", recursively: false) else { + private func playAppearanceAnimation(velocity: CGFloat? = nil, small: Bool = false, boom: Bool = false) { + guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else { return } + + if boom, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particles = scene.rootNode.childNode(withName: "particles", recursively: false) { + node.physicsField?.isActive = true + Queue.mainQueue().after(1.0) { + node.physicsField?.isActive = false + particles.particleSystems?.first?.birthRate = 0.8 + } + } + let from = node.rotation + var toValue: Float = small ? 0.0 : .pi * 2.0 + if let velocity = velocity, !small && abs(velocity) > 200 && velocity < 0.0 { + toValue *= -1 + } + let to = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: toValue) + let distance = rad2deg(to.w - from.w) + let springAnimation = CASpringAnimation(keyPath: "rotation") - springAnimation.fromValue = NSValue(scnVector4: SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: 0.0)) - springAnimation.toValue = NSValue(scnVector4: SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: .pi * 2.0)) + springAnimation.fromValue = NSValue(scnVector4: from) + springAnimation.toValue = NSValue(scnVector4: to) springAnimation.mass = 1.0 springAnimation.stiffness = 21.0 springAnimation.damping = 5.8 - springAnimation.duration = 1.5 - springAnimation.initialVelocity = 1.7 + springAnimation.duration = springAnimation.settlingDuration * 0.75 + springAnimation.initialVelocity = velocity.flatMap { abs($0 / CGFloat(distance)) } ?? 1.7 - starNode.addAnimation(springAnimation, forKey: "rotate") + node.addAnimation(springAnimation, forKey: "rotate") } func update(component: StarComponent, availableSize: CGSize, transition: Transition) -> CGSize { @@ -631,7 +734,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { .position(CGPoint(x: fade.size.width / 2.0, y: fade.size.height / 2.0)) ) - size.height += 183.0 + size.height += 183.0 + 10.0 let textColor = theme.list.itemPrimaryTextColor let titleColor = theme.list.itemPrimaryTextColor @@ -1053,7 +1156,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { let topPanelAlpha: CGFloat let titleOffset: CGFloat let titleScale: CGFloat - let titleOffsetDelta = 150.0 - environment.navigationHeight / 2.0 + let titleOffsetDelta = 160.0 - environment.navigationHeight / 2.0 if let topContentOffset = state.topContentOffset { topPanelAlpha = min(30.0, max(0.0, topContentOffset - 64.0)) / 30.0 @@ -1066,7 +1169,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } context.add(star - .position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0 - 18.0 - titleOffset * titleScale)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0 - 10.0 - titleOffset * titleScale)) .scale(titleScale) ) @@ -1080,7 +1183,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { ) context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: max(150.0 - titleOffset, environment.navigationHeight / 2.0))) + .position(CGPoint(x: context.availableSize.width / 2.0, y: max(160.0 - titleOffset, environment.navigationHeight / 2.0))) .scale(titleScale) ) diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index 318907fbc2..1a8083233d 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -16,6 +16,410 @@ import BundleIconComponent import SolidRoundedButtonComponent import Markdown +private class PremiumLimitAnimationComponent: Component { + private let iconName: String + private let inactiveColor: UIColor + private let activeColors: [UIColor] + private let textColor: UIColor + + init( + iconName: String, + inactiveColor: UIColor, + activeColors: [UIColor], + textColor: UIColor + ) { + self.iconName = iconName + self.inactiveColor = inactiveColor + self.activeColors = activeColors + self.textColor = textColor + } + + static func ==(lhs: PremiumLimitAnimationComponent, rhs: PremiumLimitAnimationComponent) -> Bool { + if lhs.iconName != rhs.iconName { + return false + } + if lhs.inactiveColor != rhs.inactiveColor { + return false + } + if lhs.activeColors != rhs.activeColors { + return false + } + if lhs.textColor != rhs.textColor { + return false + } + return true + } + + final class View: UIView { + private let container: SimpleLayer + private let inactiveBackground: SimpleLayer + + private let activeContainer: SimpleLayer + private let activeBackground: SimpleLayer + + private let badgeView: UIView + private let badgeMaskView: UIView + private let badgeMaskBackgroundView: UIView + private let badgeMaskArrowView: UIImageView + private let badgeForeground: SimpleLayer + private let badgeIcon: UIImageView + private let badgeCountLabel: RollingLabel + + override init(frame: CGRect) { + self.container = SimpleLayer() + self.container.masksToBounds = true + self.container.cornerRadius = 6.0 + + self.inactiveBackground = SimpleLayer() + + self.activeContainer = SimpleLayer() + self.activeContainer.masksToBounds = true + + self.activeBackground = SimpleLayer() + + self.badgeView = UIView() + self.badgeView.layer.anchorPoint = CGPoint(x: 0.5, y: 1.0) + + self.badgeMaskBackgroundView = UIView() + self.badgeMaskBackgroundView.backgroundColor = .white + self.badgeMaskBackgroundView.layer.cornerRadius = 24.0 + + self.badgeMaskArrowView = UIImageView() + self.badgeMaskArrowView.image = generateImage(CGSize(width: 44.0, height: 12.0), rotatedContext: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + context.setFillColor(UIColor.white.cgColor) + context.scaleBy(x: 3.76, y: 3.76) + context.translateBy(x: -9.3, y: -12.7) + try? drawSvgPath(context, path: "M6.4,0.0 C2.9,0.0 0.0,2.84 0.0,6.35 C0.0,9.86 2.9,12.7 6.4,12.7 H9.302 H11.3 C11.7,12.7 12.1,12.87 12.4,13.17 L14.4,15.13 C14.8,15.54 15.5,15.54 15.9,15.13 L17.8,13.17 C18.1,12.87 18.5,12.7 18.9,12.7 H20.9 H23.6 C27.1,12.7 29.9,9.86 29.9,6.35 C29.9,2.84 27.1,0.0 23.6,0.0 Z ") + }) + + self.badgeMaskView = UIView() + self.badgeMaskView.addSubview(self.badgeMaskBackgroundView) + self.badgeMaskView.addSubview(self.badgeMaskArrowView) + self.badgeView.mask = self.badgeMaskView + + self.badgeForeground = SimpleLayer() + + self.badgeIcon = UIImageView() + self.badgeIcon.contentMode = .center + + self.badgeCountLabel = RollingLabel() + self.badgeCountLabel.font = Font.with(size: 24.0, design: .round, weight: .semibold, traits: []) + self.badgeCountLabel.textColor = .white + self.badgeCountLabel.text(num: 0) + + super.init(frame: frame) + + self.layer.addSublayer(self.container) + self.container.addSublayer(self.inactiveBackground) + self.container.addSublayer(self.activeContainer) + self.activeContainer.addSublayer(self.activeBackground) + + self.addSubview(self.badgeView) + self.badgeView.layer.addSublayer(self.badgeForeground) + self.badgeView.addSubview(self.badgeIcon) + self.badgeView.addSubview(self.badgeCountLabel) + + self.isUserInteractionEnabled = false + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var didPlayAppearanceAnimation = false + func playAppearanceAnimation(availableSize: CGSize) { + self.badgeView.layer.animateScale(from: 0.1, to: 1.0, duration: 0.4, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) + + let now = self.badgeView.layer.convertTime(CACurrentMediaTime(), from: nil) + + let positionAnimation = CABasicAnimation(keyPath: "position.x") + positionAnimation.fromValue = NSValue(cgPoint: CGPoint(x: -availableSize.width / 2.0, y: 0.0)) + positionAnimation.toValue = NSValue(cgPoint: CGPoint()) + positionAnimation.isAdditive = true + positionAnimation.duration = 0.5 + positionAnimation.fillMode = .forwards + positionAnimation.beginTime = now + + let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + rotateAnimation.fromValue = 0.0 as NSNumber + rotateAnimation.toValue = 0.2 as NSNumber + rotateAnimation.isAdditive = true + rotateAnimation.duration = 0.2 + rotateAnimation.beginTime = now + 0.5 + rotateAnimation.fillMode = .forwards + rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) + + let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + returnAnimation.fromValue = 0.2 as NSNumber + returnAnimation.toValue = 0.0 as NSNumber + returnAnimation.isAdditive = true + returnAnimation.duration = 0.18 + returnAnimation.beginTime = now + 0.5 + 0.2 + returnAnimation.fillMode = .forwards + returnAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) + + self.badgeView.layer.add(positionAnimation, forKey: "appearance1") + self.badgeView.layer.add(rotateAnimation, forKey: "appearance2") + self.badgeView.layer.add(returnAnimation, forKey: "appearance3") + + self.badgeCountLabel.text(num: 4) + } + + var previousAvailableSize: CGSize? + func update(component: PremiumLimitAnimationComponent, availableSize: CGSize, transition: Transition) -> CGSize { + self.inactiveBackground.backgroundColor = component.inactiveColor.cgColor + self.activeBackground.backgroundColor = component.activeColors.last?.cgColor + + self.badgeIcon.image = UIImage(bundleImageName: component.iconName)?.withRenderingMode(.alwaysTemplate) + self.badgeIcon.tintColor = component.textColor + + let lineHeight: CGFloat = 30.0 + let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - lineHeight), size: CGSize(width: availableSize.width, height: lineHeight)) + self.container.frame = containerFrame + + self.inactiveBackground.frame = CGRect(origin: .zero, size: CGSize(width: containerFrame.width / 2.0 - 1.0, height: lineHeight)) + self.activeContainer.frame = CGRect(origin: CGPoint(x: containerFrame.width / 2.0 + 1.0, y: 0.0), size: CGSize(width: containerFrame.width / 2.0 - 1.0, height: lineHeight)) + + self.activeBackground.bounds = CGRect(origin: .zero, size: CGSize(width: containerFrame.width * 3.0 / 2.0, height: lineHeight)) + if self.activeBackground.animation(forKey: "movement") == nil { + self.activeBackground.position = CGPoint(x: containerFrame.width * 3.0 / 4.0, y: lineHeight / 2.0) + } + + let badgeSize = CGSize(width: 82.0, height: 48.0 + 12.0) + self.badgeMaskView.frame = CGRect(origin: .zero, size: badgeSize) + self.badgeMaskBackgroundView.frame = CGRect(origin: .zero, size: CGSize(width: badgeSize.width, height: 48.0)) + self.badgeMaskArrowView.frame = CGRect(origin: CGPoint(x: (badgeSize.width - 44.0) / 2.0, y: badgeSize.height - 12.0), size: CGSize(width: 44.0, height: 12.0)) + + self.badgeView.bounds = CGRect(origin: .zero, size: badgeSize) + self.badgeView.center = CGPoint(x: availableSize.width / 2.0, y: 82.0) + if self.badgeForeground.animation(forKey: "movement") == nil { + self.badgeForeground.position = CGPoint(x: badgeSize.width * 3.0 / 2.0, y: badgeSize.height / 2.0) + } + self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: badgeSize.width * 3.0, height: badgeSize.height)) + + self.badgeIcon.frame = CGRect(x: 15.0, y: 9.0, width: 30.0, height: 30.0) + + self.badgeCountLabel.frame = CGRect(x: badgeSize.width - 36.0, y: 10.0, width: 30.0, height: 48.0) + + if !self.didPlayAppearanceAnimation { + self.didPlayAppearanceAnimation = true + self.playAppearanceAnimation(availableSize: availableSize) + } + + if self.previousAvailableSize != availableSize { + self.previousAvailableSize = availableSize + + var locations: [CGFloat] = [] + let delta = 1.0 / CGFloat(component.activeColors.count - 1) + for i in 0 ..< component.activeColors.count { + locations.append(delta * CGFloat(i)) + } + + let gradient = generateGradientImage(size: CGSize(width: 200.0, height: 60.0), colors: component.activeColors, locations: locations, direction: .horizontal) + self.badgeForeground.contentsGravity = .resizeAspectFill + self.badgeForeground.contents = gradient?.cgImage + + self.activeBackground.contentsGravity = .resizeAspectFill + self.activeBackground.contents = gradient?.cgImage + + self.setupGradientAnimations() + } + + return availableSize + } + + private func setupGradientAnimations() { + if let _ = self.badgeForeground.animation(forKey: "movement") { + } else { + CATransaction.begin() + + let badgeOffset = (self.badgeForeground.frame.width - self.badgeView.bounds.width) / 2.0 + let badgePreviousValue = self.badgeForeground.position.x + var badgeNewValue: CGFloat = badgeOffset + if badgeOffset - badgePreviousValue < self.badgeForeground.frame.width * 0.25 { + badgeNewValue -= self.badgeForeground.frame.width * 0.35 + } + self.badgeForeground.position = CGPoint(x: badgeNewValue, y: self.badgeForeground.bounds.size.height / 2.0) + + let lineOffset = (self.activeBackground.frame.width - self.activeContainer.bounds.width) / 2.0 + let linePreviousValue = self.activeBackground.position.x + var lineNewValue: CGFloat = lineOffset + if lineOffset - linePreviousValue < self.activeBackground.frame.width * 0.25 { + lineNewValue -= self.activeBackground.frame.width * 0.35 + } + self.activeBackground.position = CGPoint(x: lineNewValue, y: self.activeBackground.bounds.size.height / 2.0) + + let badgeAnimation = CABasicAnimation(keyPath: "position.x") + badgeAnimation.duration = 4.5 + badgeAnimation.fromValue = badgePreviousValue + badgeAnimation.toValue = badgeNewValue + badgeAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + + CATransaction.setCompletionBlock { [weak self] in + self?.setupGradientAnimations() + } + self.badgeForeground.add(badgeAnimation, forKey: "movement") + + let lineAnimation = CABasicAnimation(keyPath: "position.x") + lineAnimation.duration = 4.5 + lineAnimation.fromValue = linePreviousValue + lineAnimation.toValue = lineNewValue + lineAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + self.activeBackground.add(lineAnimation, forKey: "movement") + + CATransaction.commit() + } + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} + +public final class PremiumLimitDisplayComponent: CombinedComponent { + public let inactiveColor: UIColor + public let activeColors: [UIColor] + public let inactiveTitle: String + public let inactiveTitleColor: UIColor + public let activeTitle: String + public let activeValue: String + public let activeTitleColor: UIColor + public let badgeIconName: String + public let badgeValue: String + + public init( + inactiveColor: UIColor, + activeColors: [UIColor], + inactiveTitle: String, + inactiveTitleColor: UIColor, + activeTitle: String, + activeValue: String, + activeTitleColor: UIColor, + badgeIconName: String, + badgeValue: String + ) { + self.inactiveColor = inactiveColor + self.activeColors = activeColors + self.inactiveTitle = inactiveTitle + self.inactiveTitleColor = inactiveTitleColor + self.activeTitle = activeTitle + self.activeValue = activeValue + self.activeTitleColor = activeTitleColor + self.badgeIconName = badgeIconName + self.badgeValue = badgeValue + } + + public static func ==(lhs: PremiumLimitDisplayComponent, rhs: PremiumLimitDisplayComponent) -> Bool { + if lhs.inactiveColor != rhs.inactiveColor { + return false + } + if lhs.activeColors != rhs.activeColors { + return false + } + if lhs.inactiveTitle != rhs.inactiveTitle { + return false + } + if lhs.inactiveTitleColor != rhs.inactiveTitleColor { + return false + } + if lhs.activeTitle != rhs.activeTitle { + return false + } + if lhs.activeValue != rhs.activeValue { + return false + } + if lhs.activeTitleColor != rhs.activeTitleColor { + return false + } + if lhs.badgeIconName != rhs.badgeIconName { + return false + } + if lhs.badgeValue != rhs.badgeValue { + return false + } + return true + } + + public static var body: Body { + let inactiveTitle = Child(Text.self) + let activeTitle = Child(Text.self) + let activeValue = Child(Text.self) + let animation = Child(PremiumLimitAnimationComponent.self) + + return { context in + let component = context.component + + let height: CGFloat = 120.0 + let lineHeight: CGFloat = 30.0 + + let inactiveTitle = inactiveTitle.update( + component: Text( + text: component.inactiveTitle, + font: Font.semibold(15.0), + color: component.inactiveTitleColor + ), + availableSize: context.availableSize, + transition: context.transition + ) + + let activeTitle = activeTitle.update( + component: Text( + text: component.activeTitle, + font: Font.semibold(15.0), + color: component.activeTitleColor + ), + availableSize: context.availableSize, + transition: context.transition + ) + + let activeValue = activeValue.update( + component: Text( + text: component.activeValue, + font: Font.semibold(15.0), + color: component.activeTitleColor + ), + availableSize: context.availableSize, + transition: context.transition + ) + + let animation = animation.update( + component: PremiumLimitAnimationComponent( + iconName: component.badgeIconName, + inactiveColor: component.inactiveColor, + activeColors: component.activeColors, + textColor: component.activeTitleColor + ), + availableSize: CGSize(width: context.availableSize.width, height: height), + transition: context.transition + ) + + context.add(animation + .position(CGPoint(x: context.availableSize.width / 2.0, y: height / 2.0)) + ) + + context.add(inactiveTitle + .position(CGPoint(x: inactiveTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0)) + ) + + context.add(activeTitle + .position(CGPoint(x: context.availableSize.width / 2.0 + 1.0 + activeTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0)) + ) + + context.add(activeValue + .position(CGPoint(x: context.availableSize.width - activeValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0)) + ) + + return CGSize(width: context.availableSize.width, height: height) + } + } +} + private final class LimitSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -78,13 +482,9 @@ private final class LimitSheetContent: CombinedComponent { } static var body: Body { - let badgeBackground = Child(RoundedRectangle.self) - let badgeIcon = Child(BundleIconComponent.self) - let badgeText = Child(MultilineTextComponent.self) - let title = Child(MultilineTextComponent.self) let text = Child(MultilineTextComponent.self) - + let limit = Child(PremiumLimitDisplayComponent.self) let button = Child(SolidRoundedButtonComponent.self) return { context in @@ -100,68 +500,40 @@ private final class LimitSheetContent: CombinedComponent { let textSideInset: CGFloat = 24.0 + environment.safeInsets.left let iconName: String - let badgeString: String + let badgeValue: String let string: String + let premiumValue: String switch subject { case .folders: let limit = state.limits.maxFoldersCount let premiumLimit = state.premiumLimits.maxFoldersCount iconName = "Premium/Folder" - badgeString = "\(limit)" + badgeValue = "\(limit)" string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string + premiumValue = "\(premiumLimit)" case .chatsInFolder: let limit = state.limits.maxFolderChatsCount let premiumLimit = state.premiumLimits.maxFolderChatsCount iconName = "Premium/Chat" - badgeString = "\(limit)" + badgeValue = "\(limit)" string = strings.Premium_MaxChatsInFolderCountText("\(limit)", "\(premiumLimit)").string + premiumValue = "\(premiumLimit)" case .pins: - let limit = state.limits.maxPinnedChatCount - let premiumLimit = state.premiumLimits.maxPinnedChatCount + let limit = 4//state.limits.maxPinnedChatCount + let premiumLimit = 6//state.premiumLimits.maxPinnedChatCount iconName = "Premium/Pin" - badgeString = "\(limit)" + badgeValue = "\(limit)" string = strings.Premium_MaxPinsText("\(limit)", "\(premiumLimit)").string + premiumValue = "\(premiumLimit)" case .files: let limit = 2048 * 1024 * 1024 //state.limits.maxPinnedChatCount let premiumLimit = 4096 * 1024 * 1024 //state.premiumLimits.maxPinnedChatCount iconName = "Premium/File" - badgeString = dataSizeString(limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) + badgeValue = dataSizeString(limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) string = strings.Premium_MaxFileSizeText(dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))).string + premiumValue = dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) } - let badgeIcon = badgeIcon.update( - component: BundleIconComponent( - name: iconName, - tintColor: .white - ), - availableSize: context.availableSize, - transition: .immediate - ) - - let badgeText = badgeText.update( - component: MultilineTextComponent( - text: .plain(NSAttributedString( - string: badgeString, - font: Font.with(size: 24.0, design: .round, weight: .semibold, traits: []), - textColor: .white, - paragraphAlignment: .center - )), - horizontalAlignment: .center, - maximumNumberOfLines: 1 - ), - availableSize: context.availableSize, - transition: .immediate - ) - - let badgeBackground = badgeBackground.update( - component: RoundedRectangle( - colors: [UIColor(rgb: 0xa34fcf), UIColor(rgb: 0xc8498a), UIColor(rgb: 0xff7a23)], - cornerRadius: 23.5 - ), - availableSize: CGSize(width: badgeText.size.width + 67.0, height: 47.0), - transition: .immediate - ) - let title = title.update( component: MultilineTextComponent( text: .plain(NSAttributedString( @@ -195,19 +567,45 @@ private final class LimitSheetContent: CombinedComponent { transition: .immediate ) + let limit = limit.update( + component: PremiumLimitDisplayComponent( + inactiveColor: UIColor(rgb: 0xE9E9EA), + activeColors: [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ], + inactiveTitle: "Free", + inactiveTitleColor: .black, + activeTitle: "Premium", + activeValue: premiumValue, + activeTitleColor: .white, + badgeIconName: iconName, + badgeValue: badgeValue + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + let button = button.update( component: SolidRoundedButtonComponent( title: strings.Premium_IncreaseLimit, theme: SolidRoundedButtonComponent.Theme( backgroundColor: .black, - backgroundColors: [UIColor(rgb: 0x407af0), UIColor(rgb: 0x9551e8), UIColor(rgb: 0xbf499a), UIColor(rgb: 0xf17b30)], + backgroundColors: [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ], foregroundColor: .white ), font: .bold, fontSize: 17.0, height: 50.0, cornerRadius: 10.0, - gloss: false, + gloss: true, iconName: "Premium/X2", iconPosition: .right, action: { [weak component] in @@ -224,23 +622,12 @@ private final class LimitSheetContent: CombinedComponent { let width = context.availableSize.width - let badgeFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - badgeBackground.size.width) / 2.0), y: 33.0), size: badgeBackground.size) - context.add(badgeBackground - .position(CGPoint(x: badgeFrame.midX, y: badgeFrame.midY)) + context.add(limit + .position(CGPoint(x: width / 2.0, y: limit.size.height / 2.0 + 44.0)) ) - let badgeIconFrame = CGRect(origin: CGPoint(x: badgeFrame.minX + 18.0, y: badgeFrame.minY + floor((badgeFrame.height - badgeIcon.size.height) / 2.0)), size: badgeIcon.size) - context.add(badgeIcon - .position(CGPoint(x: badgeIconFrame.midX, y: badgeIconFrame.midY)) - ) - - let badgeTextFrame = CGRect(origin: CGPoint(x: badgeFrame.maxX - badgeText.size.width - 15.0, y: badgeFrame.minY + floor((badgeFrame.height - badgeText.size.height) / 2.0)), size: badgeText.size) - context.add(badgeText - .position(CGPoint(x: badgeTextFrame.midX, y: badgeTextFrame.midY)) - ) - context.add(title - .position(CGPoint(x: width / 2.0, y: 28.0)) + .position(CGPoint(x: width / 2.0, y: 28.0)) ) context.add(text .position(CGPoint(x: width / 2.0, y: 228.0)) diff --git a/submodules/PremiumUI/Sources/RollingCountLabel.swift b/submodules/PremiumUI/Sources/RollingCountLabel.swift new file mode 100644 index 0000000000..521e3ca275 --- /dev/null +++ b/submodules/PremiumUI/Sources/RollingCountLabel.swift @@ -0,0 +1,197 @@ +import UIKit + +private extension UILabel { + func textWidth() -> CGFloat { + return UILabel.textWidth(label: self) + } + + class func textWidth(label: UILabel) -> CGFloat { + return textWidth(label: label, text: label.text!) + } + + class func textWidth(label: UILabel, text: String) -> CGFloat { + return textWidth(font: label.font, text: text) + } + + class func textWidth(font: UIFont, text: String) -> CGFloat { + let myText = text as NSString + + let rect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) + let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil) + return ceil(labelSize.width) + } +} + +open class RollingLabel: UILabel { + private var fullText = "" + + private var suffix: String = "" + open var showSymbol = false + private var scrollLayers: [CAScrollLayer] = [] + private var scrollLabels: [UILabel] = [] + private let duration = 1.12 + private let durationOffset = 0.2 + private let textsNotAnimated = [","] + + public func text(num: Int) { + self.configure(with: num) + self.text = " " + self.animate() + } + + public func setPrefix(prefix: String) { + self.suffix = prefix + } + + private func configure(with number: Int) { + fullText = String(number) + + clean() + setupSubviews() + } + + private func animate(ascending: Bool = true) { + createAnimations(ascending: ascending) + } + + private func clean() { + self.text = nil + self.subviews.forEach { $0.removeFromSuperview() } + self.layer.sublayers?.forEach { $0.removeFromSuperlayer() } + scrollLayers.removeAll() + scrollLabels.removeAll() + } + + private func setupSubviews() { + let stringArray = fullText.map { String($0) } + var x: CGFloat = 0 + let y: CGFloat = 0 + if self.textAlignment == .center { + if showSymbol { + self.text = "\(fullText) \(suffix)" + } else { + self.text = fullText + } + let w = UILabel.textWidth(font: self.font, text: self.text ?? "") + self.text = "" // 초기화 + x = -(w / 2) + } else if self.textAlignment == .right { + if showSymbol { + self.text = "\(fullText) \(suffix) " + } else { + self.text = fullText + } + let w = UILabel.textWidth(font: self.font, text: self.text ?? "") + self.text = "" // 초기화 + x = -w + } + + if showSymbol { + let wLabel = UILabel() + wLabel.frame.origin = CGPoint(x: x, y: y) + wLabel.textColor = textColor + wLabel.font = font + wLabel.text = "\(suffix) " + wLabel.textAlignment = .center + wLabel.sizeToFit() + self.addSubview(wLabel) + x += wLabel.bounds.width + } + + stringArray.enumerated().forEach { index, text in + if textsNotAnimated.contains(text) { + let label = UILabel() + label.frame.origin = CGPoint(x: x, y: y) + label.textColor = textColor + label.font = font + label.text = text + label.textAlignment = .center + label.sizeToFit() + self.addSubview(label) + + x += label.bounds.width + } else { + let label = UILabel() + label.frame.origin = CGPoint(x: x, y: y) + label.textColor = textColor + label.font = font + label.text = "0" + label.textAlignment = .center + label.sizeToFit() + createScrollLayer(to: label, text: text) + + x += label.bounds.width + } + } + } + + private func createScrollLayer(to label: UILabel, text: String) { + let scrollLayer = CAScrollLayer() + scrollLayer.frame = label.frame + scrollLayers.append(scrollLayer) + self.layer.addSublayer(scrollLayer) + + createContentForLayer(scrollLayer: scrollLayer, text: text) + } + + private func createContentForLayer(scrollLayer: CAScrollLayer, text: String) { + var textsForScroll: [String] = [] + + let max: Int + var found = false + if let val = Int(text) { + max = val + found = true + } else { + max = 9 + } + + for i in 0...max { + let str = String(i) + textsForScroll.append(str) + } + if !found { + textsForScroll.append(text) + } + + var height: CGFloat = 0 + for text in textsForScroll { + let label = UILabel() + label.text = text + label.textColor = textColor + label.font = font + label.textAlignment = .center + label.frame = CGRect(x: 0, y: height, width: scrollLayer.frame.width, height: scrollLayer.frame.height) + scrollLayer.addSublayer(label.layer) + scrollLabels.append(label) + + height = label.frame.maxY + } + } + + private func createAnimations(ascending: Bool) { + var offset: CFTimeInterval = 0.0 + + for scrollLayer in scrollLayers { + let maxY = scrollLayer.sublayers?.last?.frame.origin.y ?? 0.0 + + let animation = CABasicAnimation(keyPath: "sublayerTransform.translation.y") + animation.duration = duration + offset + animation.timingFunction = CAMediaTimingFunction(name: .easeOut) + + if ascending { + animation.fromValue = maxY + animation.toValue = 0 + } else { + animation.fromValue = 0 + animation.toValue = maxY + } + + scrollLayer.scrollMode = .vertically + scrollLayer.add(animation, forKey: nil) + scrollLayer.scroll(to: CGPoint(x: 0, y: maxY)) + + offset += self.durationOffset + } + } +} diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index fa164d897a..895699be86 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -645,9 +645,7 @@ public final class SolidRoundedButtonView: UIView { let previousValue = buttonBackgroundAnimationView.center.x var newValue: CGFloat = offset if offset - previousValue < buttonBackgroundAnimationView.frame.width * 0.25 { - newValue -= CGFloat.random(in: buttonBackgroundAnimationView.frame.width * 0.3 ..< buttonBackgroundAnimationView.frame.width * 0.4) - } else { -// newValue -= CGFloat.random(in: 0.0 ..< buttonBackgroundAnimationView.frame.width * 0.1) + newValue -= buttonBackgroundAnimationView.frame.width * 0.35 } buttonBackgroundAnimationView.center = CGPoint(x: newValue, y: buttonBackgroundAnimationView.bounds.size.height / 2.0) @@ -794,7 +792,10 @@ public final class SolidRoundedButtonView: UIView { } if let buttonBackgroundAnimationView = self.buttonBackgroundAnimationView { - transition.updateFrame(view: buttonBackgroundAnimationView, frame: CGRect(origin: CGPoint(), size: CGSize(width: buttonSize.width * 2.4, height: buttonSize.height))) + if buttonBackgroundAnimationView.layer.animation(forKey: "movement") == nil { + buttonBackgroundAnimationView.center = CGPoint(x: buttonSize.width * 2.4 / 2.0, y: buttonSize.height / 2.0) + } + buttonBackgroundAnimationView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: buttonSize.width * 2.4, height: buttonSize.height)) self.setupGradientAnimations() } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift index 7ded3bf636..3a2b8e5a24 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift @@ -128,6 +128,37 @@ public extension TelegramEngine { ) } } + + public func subscribe< + T0: TelegramEngineDataItem, + T1: TelegramEngineDataItem, + T2: TelegramEngineDataItem + >( + _ t0: T0, + _ t1: T1, + _ t2: T2 + ) -> Signal< + ( + T0.Result, + T1.Result, + T2.Result + ), + NoError> { + return self._subscribe(items: [ + t0 as! AnyPostboxViewDataItem, + t1 as! AnyPostboxViewDataItem, + t2 as! AnyPostboxViewDataItem + ]) + |> map { results -> (T0.Result, T1.Result, T2.Result) in + return ( + results[0] as! T0.Result, + results[1] as! T1.Result, + results[2] as! T2.Result + ) + } + } + + public func get< T0: TelegramEngineDataItem, T1: TelegramEngineDataItem @@ -142,5 +173,23 @@ public extension TelegramEngine { NoError> { return self.subscribe(t0, t1) |> take(1) } + + public func get< + T0: TelegramEngineDataItem, + T1: TelegramEngineDataItem, + T2: TelegramEngineDataItem + >( + _ t0: T0, + _ t1: T1, + _ t2: T2 + ) -> Signal< + ( + T0.Result, + T1.Result, + T2.Result + ), + NoError> { + return self.subscribe(t0, t1, t2) |> take(1) + } } } diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Contents.json deleted file mode 100644 index 96acb63cc3..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Tmp.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Tmp.png b/submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Tmp.png deleted file mode 100644 index 460cf4ce24..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Tmp.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Tmp2.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Tmp2.imageset/Contents.json deleted file mode 100644 index b538d05a51..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Premium/Tmp2.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Tmp2.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Tmp2.imageset/Tmp2.png b/submodules/TelegramUI/Images.xcassets/Premium/Tmp2.imageset/Tmp2.png deleted file mode 100644 index bbf29bcd92..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Premium/Tmp2.imageset/Tmp2.png and /dev/null differ diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 1652870db1..19ec98a606 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -131,11 +131,11 @@ public final class TelegramRootController: NavigationController { self.rootTabController = tabBarController self.pushViewController(tabBarController, animated: false) - Queue.mainQueue().after(1.0) { -// let screen = PremiumLimitScreen(context: self.context, subject: .pins, action: {}) - let screen = PremiumIntroScreen(context: self.context, action: {}) - self.chatListController?.push(screen) - } +// Queue.mainQueue().after(1.0) { +//// let screen = PremiumLimitScreen(context: self.context, subject: .pins, action: {}) +// let screen = PremiumIntroScreen(context: self.context, action: {}) +// self.chatListController?.push(screen) +// } } public func updateRootControllers(showCallsTab: Bool) {