From 04ca83108156e4fd92b1b062865fafa37cf33338 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 5 Sep 2023 15:46:03 +0400 Subject: [PATCH] Fix build --- .../Sources/PremiumLimitScreen.swift | 1195 ++++++++++------- .../Sources/SharedAccountContext.swift | 2 + 2 files changed, 677 insertions(+), 520 deletions(-) diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index d9d72d00fa..4863d05c4c 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -37,428 +37,22 @@ func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor }) } -private class PremiumLimitAnimationComponent: Component { - private let iconName: String? +public class PremiumLimitDisplayComponent: Component { private let inactiveColor: UIColor private let activeColors: [UIColor] - private let textColor: UIColor + private let inactiveTitle: String + private let inactiveValue: String + private let inactiveTitleColor: UIColor + private let activeTitle: String + private let activeValue: String + private let activeTitleColor: UIColor + private let badgeIconName: String? private let badgeText: String? private let badgePosition: CGFloat private let badgeGraphPosition: CGFloat private let invertProgress: Bool private let isPremiumDisabled: Bool - init( - iconName: String?, - inactiveColor: UIColor, - activeColors: [UIColor], - textColor: UIColor, - badgeText: String?, - badgePosition: CGFloat, - badgeGraphPosition: CGFloat, - invertProgress: Bool, - isPremiumDisabled: Bool - ) { - self.iconName = iconName - self.inactiveColor = inactiveColor - self.activeColors = activeColors - self.textColor = textColor - self.badgeText = badgeText - self.badgePosition = badgePosition - self.badgeGraphPosition = badgeGraphPosition - self.invertProgress = invertProgress - self.isPremiumDisabled = isPremiumDisabled - } - - 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 - } - if lhs.badgeText != rhs.badgeText { - return false - } - if lhs.badgePosition != rhs.badgePosition { - return false - } - if lhs.badgeGraphPosition != rhs.badgeGraphPosition { - return false - } - if lhs.invertProgress != rhs.invertProgress { - return false - } - if lhs.isPremiumDisabled != rhs.isPremiumDisabled { - return false - } - return true - } - - final class View: UIView { - private var component: PremiumLimitAnimationComponent? - - 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 badgeMaskTailView: UIImageView - private let badgeForeground: SimpleLayer - private let badgeIcon: UIImageView - private let badgeCountLabel: RollingLabel - - private let hapticFeedback = HapticFeedback() - - 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.activeBackground.anchorPoint = CGPoint() - - self.badgeView = UIView() - self.badgeView.alpha = 0.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.badgeMaskTailView = UIImageView() - self.badgeMaskTailView.isHidden = true - - - let img = generateImage(CGSize(width: 44.0, height: 36.0), rotatedContext: { size, context in - context.clear(CGRect(origin: .zero, size: size)) - context.setFillColor(UIColor.white.cgColor) - context.fill(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 44.0, height: 24.0))) - context.translateBy(x: 22.0, y: 24.0) - try? drawSvgPath(context, path: "M0.0,0.0 H22.0 V4.75736 C22.0,7.43007 18.7686,8.76857 16.8787,6.87868 L11.7574,1.75736 C10.6321,0.632141 9.10602,0.0 7.51472,0.0 H0.0 Z ") - }) - self.badgeMaskTailView.image = img - - self.badgeMaskView = UIView() - self.badgeMaskView.addSubview(self.badgeMaskBackgroundView) - self.badgeMaskView.addSubview(self.badgeMaskArrowView) - self.badgeMaskView.addSubview(self.badgeMaskTailView) - self.badgeMaskView.layer.rasterizationScale = UIScreenScale - self.badgeMaskView.layer.shouldRasterize = true - 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.configure(with: "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(component: PremiumLimitAnimationComponent, availableSize: CGSize) { - self.badgeView.layer.animateScale(from: 0.1, to: 1.0, duration: 0.4, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) - - let positionAnimation = CABasicAnimation(keyPath: "position.x") - positionAnimation.fromValue = NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0)) - positionAnimation.toValue = NSValue(cgPoint: self.badgeView.center) - positionAnimation.duration = 0.5 - positionAnimation.fillMode = .forwards - self.badgeView.layer.add(positionAnimation, forKey: "appearance1") - - - Queue.mainQueue().after(0.5, { - let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - rotateAnimation.fromValue = 0.0 as NSNumber - rotateAnimation.toValue = 0.2 as NSNumber - rotateAnimation.duration = 0.2 - rotateAnimation.fillMode = .forwards - rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) - rotateAnimation.isRemovedOnCompletion = false - self.badgeView.layer.add(rotateAnimation, forKey: "appearance2") - - if !self.badgeView.isHidden { - self.hapticFeedback.impact(.light) - } - - Queue.mainQueue().after(0.2) { - let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - returnAnimation.fromValue = 0.2 as NSNumber - returnAnimation.toValue = 0.0 as NSNumber - returnAnimation.duration = 0.18 - returnAnimation.fillMode = .forwards - returnAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) - self.badgeView.layer.add(returnAnimation, forKey: "appearance3") - self.badgeView.layer.removeAnimation(forKey: "appearance2") - } - }) - - self.badgeView.alpha = 1.0 - self.badgeView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) - - if let badgeText = component.badgeText { - self.badgeCountLabel.configure(with: badgeText) - } - } - - var previousAvailableSize: CGSize? - func update(component: PremiumLimitAnimationComponent, availableSize: CGSize, transition: Transition) -> CGSize { - self.component = component - self.inactiveBackground.backgroundColor = component.inactiveColor.cgColor - self.activeBackground.backgroundColor = component.activeColors.last?.cgColor - - self.badgeIcon.image = component.iconName.flatMap { UIImage(bundleImageName: $0)?.withRenderingMode(.alwaysTemplate) } - self.badgeIcon.tintColor = component.textColor - self.badgeView.isHidden = self.badgeIcon.image == nil - - 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 - - let activityPosition: CGFloat = floor(containerFrame.width * component.badgeGraphPosition) - let activeWidth: CGFloat = containerFrame.width - activityPosition - - if !component.isPremiumDisabled { - if component.invertProgress { - self.inactiveBackground.frame = CGRect(origin: CGPoint(x: activityPosition, y: 0.0), size: CGSize(width: availableSize.width - activityPosition, height: lineHeight)) - self.activeContainer.frame = CGRect(origin: .zero, size: CGSize(width: activityPosition, height: lineHeight)) - } else { - self.inactiveBackground.frame = CGRect(origin: .zero, size: CGSize(width: activityPosition, height: lineHeight)) - self.activeContainer.frame = CGRect(origin: CGPoint(x: activityPosition, y: 0.0), size: CGSize(width: activeWidth, height: lineHeight)) - } - self.activeBackground.frame = CGRect(origin: .zero, size: CGSize(width: activeWidth * (1.0 + 0.35), height: lineHeight)) - if self.activeBackground.animation(forKey: "movement") == nil { - self.activeBackground.position = CGPoint(x: -self.activeContainer.frame.width * 0.35, y: lineHeight / 2.0) - } - } - - let countWidth: CGFloat - if let badgeText = component.badgeText { - switch badgeText.count { - case 1: - countWidth = 20.0 - case 2: - countWidth = 35.0 - case 3: - countWidth = 51.0 - case 4: - countWidth = 60.0 - default: - countWidth = 51.0 - } - } else { - countWidth = 51.0 - } - let badgeWidth: CGFloat = countWidth + 62.0 - - let badgeSize = CGSize(width: badgeWidth, 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.badgeMaskTailView.frame = CGRect(origin: CGPoint(x: badgeSize.width - 44.0, y: badgeSize.height - 36.0), size: CGSize(width: 44.0, height: 36.0)) - - self.badgeView.bounds = CGRect(origin: .zero, size: badgeSize) - - var badgePosition = component.badgePosition - if component.isPremiumDisabled { - badgePosition = 0.5 - } - if badgePosition > 1.0 - .ulpOfOne { - self.badgeView.layer.anchorPoint = CGPoint(x: 1.0, y: 1.0) - - self.badgeMaskTailView.isHidden = false - self.badgeMaskArrowView.isHidden = true - - if let _ = self.badgeView.layer.animation(forKey: "appearance1") { - - } else { - self.badgeView.center = CGPoint(x: 3.0 + (availableSize.width - 6.0) * badgePosition + 3.0, y: 82.0) - } - } else { - self.badgeView.layer.anchorPoint = CGPoint(x: 0.5, y: 1.0) - - self.badgeMaskTailView.isHidden = true - self.badgeMaskArrowView.isHidden = component.isPremiumDisabled - - if let _ = self.badgeView.layer.animation(forKey: "appearance1") { - - } else { - self.badgeView.center = CGPoint(x: availableSize.width * badgePosition, y: 82.0) - } - - if self.badgeView.frame.maxX > availableSize.width { - let delta = self.badgeView.frame.maxX - availableSize.width - 6.0 - if let _ = self.badgeView.layer.animation(forKey: "appearance1") { - - } else { - self.badgeView.center = self.badgeView.center.offsetBy(dx: -delta, dy: 0.0) - } - } - } - self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: badgeSize.width * 3.0, height: badgeSize.height)) - if self.badgeForeground.animation(forKey: "movement") == nil { - self.badgeForeground.position = CGPoint(x: badgeSize.width * 3.0 / 2.0 - self.badgeForeground.frame.width * 0.35, y: badgeSize.height / 2.0) - } - - self.badgeIcon.frame = CGRect(x: 15.0, y: 9.0, width: 30.0, height: 30.0) - self.badgeCountLabel.frame = CGRect(x: badgeSize.width - countWidth - 11.0, y: 10.0, width: countWidth, height: 48.0) - - if component.isPremiumDisabled { - if !self.didPlayAppearanceAnimation { - self.didPlayAppearanceAnimation = true - - self.badgeView.alpha = 1.0 - if let badgeText = component.badgeText { - self.badgeCountLabel.configure(with: badgeText, duration: 0.3) - } - } - } else if !self.didPlayAppearanceAnimation { - self.didPlayAppearanceAnimation = true - self.playAppearanceAnimation(component: component, 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() { - guard let _ = self.component else { - return - } - 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 = 0.0 - let linePreviousValue = self.activeBackground.position.x - var lineNewValue: CGFloat = lineOffset - if linePreviousValue < 0.0 { - lineNewValue = 0.0 - } else { - lineNewValue = -self.activeContainer.bounds.width * 0.35 - } - self.activeBackground.position = CGPoint(x: lineNewValue, y: 0.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 { - let inactiveColor: UIColor - let activeColors: [UIColor] - let inactiveTitle: String - let inactiveValue: String - let inactiveTitleColor: UIColor - let activeTitle: String - let activeValue: String - let activeTitleColor: UIColor - let badgeIconName: String? - let badgeText: String? - let badgePosition: CGFloat - let badgeGraphPosition: CGFloat - let invertProgress: Bool - let isPremiumDisabled: Bool - public init( inactiveColor: UIColor, activeColors: [UIColor], @@ -537,18 +131,170 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { return true } - public static var body: Body { - let inactiveTitle = Child(MultilineTextComponent.self) - let inactiveValue = Child(MultilineTextComponent.self) - let activeTitle = Child(MultilineTextComponent.self) - let activeValue = Child(MultilineTextComponent.self) - let animation = Child(PremiumLimitAnimationComponent.self) - - return { context in - let component = context.component + public final class View: UIView { + private var component: PremiumLimitDisplayComponent? + + private let container: SimpleLayer + private let inactiveBackground: SimpleLayer + + private let inactiveTitleLabel = ComponentView() + private let inactiveValueLabel = ComponentView() + + private let inactiveRightTitleLabel = ComponentView() + private let inactiveRightValueLabel = ComponentView() + + private let activeContainer: SimpleLayer + private let activeBackground: SimpleLayer + + private let activeTitleLabel = ComponentView() + private let activeValueLabel = ComponentView() + + private let badgeView: UIView + private let badgeMaskView: UIView + private let badgeMaskBackgroundView: UIView + private let badgeMaskArrowView: UIImageView + private let badgeMaskArrowFillerView: UIView + private let badgeForeground: SimpleLayer + private let badgeIcon: UIImageView + private let badgeCountLabel: RollingLabel + + private let hapticFeedback = HapticFeedback() + + 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.activeBackground.anchorPoint = CGPoint() + + self.badgeView = UIView() + self.badgeView.alpha = 0.0 + + self.badgeMaskBackgroundView = UIView() + self.badgeMaskBackgroundView.backgroundColor = .white + self.badgeMaskBackgroundView.layer.cornerRadius = 24.0 + if #available(iOS 13.0, *) { + self.badgeMaskBackgroundView.layer.cornerCurve = .continuous + } + + 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.badgeMaskArrowFillerView = UIView() + self.badgeMaskArrowFillerView.backgroundColor = .white + + self.badgeMaskView = UIView() + self.badgeMaskView.addSubview(self.badgeMaskBackgroundView) + self.badgeMaskView.addSubview(self.badgeMaskArrowView) + self.badgeMaskArrowView.addSubview(self.badgeMaskArrowFillerView) + self.badgeMaskView.layer.rasterizationScale = UIScreenScale + self.badgeMaskView.layer.shouldRasterize = true + 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.configure(with: "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(component: PremiumLimitDisplayComponent, availableSize: CGSize, from: CGFloat? = nil) { + self.badgeView.layer.animateScale(from: 0.1, to: 1.0, duration: 0.4, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) + + let positionAnimation = CABasicAnimation(keyPath: "position.x") + positionAnimation.fromValue = NSValue(cgPoint: CGPoint(x: from ?? 0.0, y: 0.0)) + positionAnimation.toValue = NSValue(cgPoint: self.badgeView.center) + positionAnimation.duration = 0.5 + positionAnimation.fillMode = .forwards + self.badgeView.layer.add(positionAnimation, forKey: "appearance1") + + Queue.mainQueue().after(0.5, { + let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + rotateAnimation.fromValue = 0.0 as NSNumber + rotateAnimation.toValue = 0.2 as NSNumber + rotateAnimation.duration = 0.2 + rotateAnimation.fillMode = .forwards + rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) + rotateAnimation.isRemovedOnCompletion = false + self.badgeView.layer.add(rotateAnimation, forKey: "appearance2") + + if !self.badgeView.isHidden { + self.hapticFeedback.impact(.light) + } + + Queue.mainQueue().after(0.2) { + let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + returnAnimation.fromValue = 0.2 as NSNumber + returnAnimation.toValue = 0.0 as NSNumber + returnAnimation.duration = 0.18 + returnAnimation.fillMode = .forwards + returnAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) + self.badgeView.layer.add(returnAnimation, forKey: "appearance3") + self.badgeView.layer.removeAnimation(forKey: "appearance2") + } + }) + + self.badgeView.alpha = 1.0 + self.badgeView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + + if let badgeText = component.badgeText { + self.badgeCountLabel.configure(with: badgeText) + } + } + + var previousAvailableSize: CGSize? + func update(component: PremiumLimitDisplayComponent, availableSize: CGSize, transition: Transition) -> CGSize { + self.component = component + self.inactiveBackground.backgroundColor = component.inactiveColor.cgColor + self.activeBackground.backgroundColor = component.activeColors.last?.cgColor + + let size = CGSize(width: availableSize.width, height: 120.0) + + self.badgeIcon.image = component.badgeIconName.flatMap { UIImage(bundleImageName: $0)?.withRenderingMode(.alwaysTemplate) } + self.badgeIcon.tintColor = component.activeTitleColor + self.badgeView.isHidden = self.badgeIcon.image == nil - let height: CGFloat = 120.0 let lineHeight: CGFloat = 30.0 + let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - lineHeight), size: CGSize(width: size.width, height: lineHeight)) + self.container.frame = containerFrame + + let activityPosition: CGFloat = floor(containerFrame.width * component.badgeGraphPosition) + let activeWidth: CGFloat = containerFrame.width - activityPosition let leftTextColor: UIColor let rightTextColor: UIColor @@ -560,119 +306,528 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { rightTextColor = component.activeTitleColor } - let animation = animation.update( - component: PremiumLimitAnimationComponent( - iconName: component.badgeIconName, - inactiveColor: component.inactiveColor, - activeColors: component.activeColors, - textColor: component.activeTitleColor, - badgeText: component.badgeText, - badgePosition: component.badgePosition, - badgeGraphPosition: component.badgeGraphPosition, - invertProgress: component.invertProgress, - isPremiumDisabled: component.isPremiumDisabled - ), - 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)) - ) - if !component.isPremiumDisabled { - let inactiveTitle = inactiveTitle.update( - component: MultilineTextComponent( - text: .plain( - NSAttributedString( - string: component.inactiveTitle, - font: Font.semibold(15.0), - textColor: leftTextColor + let inactiveTitleSize = self.inactiveTitleLabel.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.inactiveTitle, + font: Font.semibold(15.0), + textColor: leftTextColor + ) ) ) ), - availableSize: context.availableSize, - transition: context.transition + environment: {}, + containerSize: availableSize ) - - let inactiveValue = inactiveValue.update( - component: MultilineTextComponent( - text: .plain( - NSAttributedString( - string: component.inactiveValue, - font: Font.semibold(15.0), - textColor: leftTextColor - ) - ) - ), - availableSize: context.availableSize, - transition: context.transition - ) - - let activeTitle = activeTitle.update( - component: MultilineTextComponent( - text: .plain( - NSAttributedString( - string: component.activeTitle, - font: Font.semibold(15.0), - textColor: rightTextColor - ) - ) - ), - availableSize: context.availableSize, - transition: context.transition - ) - - let activeValue = activeValue.update( - component: MultilineTextComponent( - text: .plain( - NSAttributedString( - string: component.activeValue, - font: Font.semibold(15.0), - textColor: rightTextColor - ) - ) - ), - availableSize: context.availableSize, - transition: context.transition - ) - - let activityPosition = floor(context.availableSize.width * component.badgeGraphPosition) - - var inactiveTitleOpacity: CGFloat = 1.0 - var inactiveValueOpacity: CGFloat = 1.0 - - if 12.0 + inactiveValue.size.width + 4.0 + inactiveTitle.size.width + 12.0 >= activityPosition - 8.0 { - inactiveTitleOpacity = 0.0 - if 12.0 + inactiveValue.size.width + 12.0 >= activityPosition - 8.0 { - inactiveValueOpacity = 0.0 + if let view = self.inactiveTitleLabel.view { + if view.superview == nil { + self.addSubview(view) } + view.frame = CGRect(origin: CGPoint(x: 12.0, y: containerFrame.minY + floorToScreenPixels((lineHeight - inactiveTitleSize.height) / 2.0)), size: inactiveTitleSize) } - context.add(inactiveTitle - .position(CGPoint(x: inactiveTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0)) - .opacity(inactiveTitleOpacity) + let inactiveValueSize = self.inactiveValueLabel.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.inactiveValue, + font: Font.semibold(15.0), + textColor: leftTextColor + ) + ) + ) + ), + environment: {}, + containerSize: availableSize ) + if let view = self.inactiveValueLabel.view { + if view.superview == nil { + self.addSubview(view) + } + view.frame = CGRect(origin: CGPoint(x: activityPosition - 12.0 - inactiveValueSize.width, y: containerFrame.minY + floorToScreenPixels((lineHeight - inactiveValueSize.height) / 2.0)), size: inactiveValueSize) + } - context.add(inactiveValue - .position(CGPoint(x: activityPosition - inactiveValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0)) - .opacity(inactiveValueOpacity) + let activeTitleSize = self.activeTitleLabel.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.activeTitle, + font: Font.semibold(15.0), + textColor: rightTextColor + ) + ) + ) + ), + environment: {}, + containerSize: availableSize ) + if let view = self.activeTitleLabel.view { + if view.superview == nil { + self.addSubview(view) + } + view.frame = CGRect(origin: CGPoint(x: activityPosition + 12.0, y: containerFrame.minY + floorToScreenPixels((lineHeight - activeTitleSize.height) / 2.0)), size: activeTitleSize) + } - context.add(activeTitle - .position(CGPoint(x: activityPosition + 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)) + let activeValueSize = self.activeValueLabel.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.activeValue, + font: Font.semibold(15.0), + textColor: rightTextColor + ) + ) + ) + ), + environment: {}, + containerSize: availableSize ) + if let view = self.activeValueLabel.view { + if view.superview == nil { + self.addSubview(view) + } + view.frame = CGRect(origin: CGPoint(x: containerFrame.maxX - 12.0 - activeValueSize.width, y: containerFrame.minY + floorToScreenPixels((lineHeight - activeValueSize.height) / 2.0)), size: activeValueSize) + } + } + + // var inactiveTitleOpacity: CGFloat = 1.0 + // var inactiveValueOpacity: CGFloat = 1.0 + // + // if 12.0 + inactiveValue.size.width + 4.0 + inactiveTitle.size.width + 12.0 >= activityPosition - 8.0 { + // inactiveTitleOpacity = 0.0 + // if 12.0 + inactiveValue.size.width + 12.0 >= activityPosition - 8.0 { + // inactiveValueOpacity = 0.0 + // } + // } + + + if !component.isPremiumDisabled { + if component.invertProgress { + self.inactiveBackground.frame = CGRect(origin: CGPoint(x: activityPosition, y: 0.0), size: CGSize(width: size.width - activityPosition, height: lineHeight)) + self.activeContainer.frame = CGRect(origin: .zero, size: CGSize(width: activityPosition, height: lineHeight)) + self.activeBackground.frame = CGRect(origin: .zero, size: CGSize(width: containerFrame.width * 1.35, height: lineHeight)) + } else { + self.inactiveBackground.frame = CGRect(origin: .zero, size: CGSize(width: activityPosition, height: lineHeight)) + self.activeContainer.frame = CGRect(origin: CGPoint(x: activityPosition, y: 0.0), size: CGSize(width: activeWidth, height: lineHeight)) + self.activeBackground.frame = CGRect(origin: CGPoint(x: -activityPosition, y: 0.0), size: CGSize(width: containerFrame.width * 1.35, height: lineHeight)) + } + if self.activeBackground.animation(forKey: "movement") == nil { + self.activeBackground.position = CGPoint(x: -self.activeContainer.frame.width * 0.35, y: lineHeight / 2.0) + } + } + + let countWidth: CGFloat + if let badgeText = component.badgeText { + switch badgeText.count { + case 1: + countWidth = 20.0 + case 2: + countWidth = 35.0 + case 3: + countWidth = 51.0 + case 4: + countWidth = 60.0 + default: + countWidth = 51.0 + } + } else { + countWidth = 51.0 + } + let badgeWidth: CGFloat = countWidth + 62.0 + + let badgeSize = CGSize(width: badgeWidth, 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.badgeView.bounds = CGRect(origin: .zero, size: badgeSize) + + var badgePosition = component.badgePosition + if component.isPremiumDisabled { + badgePosition = 0.5 + } + if badgePosition > 1.0 - .ulpOfOne { + self.badgeMaskArrowView.frame = CGRect(origin: CGPoint(x: badgeSize.width - 24.0, y: badgeSize.height - 15.0), size: CGSize(width: 44.0, height: 12.0)) + self.badgeView.layer.anchorPoint = CGPoint(x: 1.0, y: 1.0) + self.badgeMaskArrowFillerView.frame = CGRect(x: -7.0, y: -21.0, width: 31.0, height: 24.0) + + self.badgeMaskArrowView.isHidden = component.isPremiumDisabled + + if let _ = self.badgeView.layer.animation(forKey: "appearance1") { + + } else { + self.badgeView.center = CGPoint(x: 3.0 + (size.width - 6.0) * badgePosition + 3.0, y: 82.0) + } + } else { + self.badgeMaskArrowView.frame = CGRect(origin: CGPoint(x: (badgeSize.width - 44.0) / 2.0, y: badgeSize.height - 12.0 - UIScreenPixel), size: CGSize(width: 44.0, height: 12.0)) + self.badgeView.layer.anchorPoint = CGPoint(x: 0.5, y: 1.0) + self.badgeMaskArrowFillerView.frame = CGRect(x: 14.0, y: -21.0, width: 8.0, height: 24.0) + + self.badgeMaskArrowView.isHidden = component.isPremiumDisabled + + if let _ = self.badgeView.layer.animation(forKey: "appearance1") { + + } else { + self.badgeView.center = CGPoint(x: size.width * badgePosition, y: 82.0) + } + + if self.badgeView.frame.maxX > size.width { + let delta = self.badgeView.frame.maxX - size.width - 6.0 + if let _ = self.badgeView.layer.animation(forKey: "appearance1") { + + } else { + self.badgeView.center = self.badgeView.center.offsetBy(dx: -delta, dy: 0.0) + } + } + } + self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: badgeSize.width * 3.0, height: badgeSize.height)) + if self.badgeForeground.animation(forKey: "movement") == nil { + self.badgeForeground.position = CGPoint(x: badgeSize.width * 3.0 / 2.0 - self.badgeForeground.frame.width * 0.35, y: badgeSize.height / 2.0) + } + + self.badgeIcon.frame = CGRect(x: 15.0, y: 9.0, width: 30.0, height: 30.0) + self.badgeCountLabel.frame = CGRect(x: badgeSize.width - countWidth - 11.0, y: 10.0, width: countWidth, height: 48.0) + + if component.isPremiumDisabled { + if !self.didPlayAppearanceAnimation { + self.didPlayAppearanceAnimation = true + + self.badgeView.alpha = 1.0 + if let badgeText = component.badgeText { + self.badgeCountLabel.configure(with: badgeText, duration: 0.3) + } + } + } else if !self.didPlayAppearanceAnimation { + self.didPlayAppearanceAnimation = true + self.playAppearanceAnimation(component: component, availableSize: size, from: 0.0) + } + + 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 size + } + + private func setupGradientAnimations() { + guard let _ = self.component else { + return + } + 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 = 0.0 + let linePreviousValue = self.activeBackground.position.x + var lineNewValue: CGFloat = lineOffset + if linePreviousValue < 0.0 { + lineNewValue = 0.0 + } else { + lineNewValue = -self.activeContainer.bounds.width * 0.35 + } + lineNewValue -= self.activeContainer.frame.minX + self.activeBackground.position = CGPoint(x: lineNewValue, y: 0.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() } - - return CGSize(width: context.availableSize.width, height: height) } } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public 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 { +// let inactiveColor: UIColor +// let activeColors: [UIColor] +// let inactiveTitle: String +// let inactiveValue: String +// let inactiveTitleColor: UIColor +// let activeTitle: String +// let activeValue: String +// let activeTitleColor: UIColor +// let badgeIconName: String? +// let badgeText: String? +// let badgePosition: CGFloat +// let badgeGraphPosition: CGFloat +// let invertProgress: Bool +// let isPremiumDisabled: Bool +// +// public init( +// inactiveColor: UIColor, +// activeColors: [UIColor], +// inactiveTitle: String, +// inactiveValue: String, +// inactiveTitleColor: UIColor, +// activeTitle: String, +// activeValue: String, +// activeTitleColor: UIColor, +// badgeIconName: String?, +// badgeText: String?, +// badgePosition: CGFloat, +// badgeGraphPosition: CGFloat, +// invertProgress: Bool = false, +// isPremiumDisabled: Bool +// ) { +// self.inactiveColor = inactiveColor +// self.activeColors = activeColors +// self.inactiveTitle = inactiveTitle +// self.inactiveValue = inactiveValue +// self.inactiveTitleColor = inactiveTitleColor +// self.activeTitle = activeTitle +// self.activeValue = activeValue +// self.activeTitleColor = activeTitleColor +// self.badgeIconName = badgeIconName +// self.badgeText = badgeText +// self.badgePosition = badgePosition +// self.badgeGraphPosition = badgeGraphPosition +// self.invertProgress = invertProgress +// self.isPremiumDisabled = isPremiumDisabled +// } +// +// 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.inactiveValue != rhs.inactiveValue { +// 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.badgeText != rhs.badgeText { +// return false +// } +// if lhs.badgePosition != rhs.badgePosition { +// return false +// } +// if lhs.badgeGraphPosition != rhs.badgeGraphPosition { +// return false +// } +// if lhs.invertProgress != rhs.invertProgress { +// return false +// } +// if lhs.isPremiumDisabled != rhs.isPremiumDisabled { +// return false +// } +// return true +// } +// +// public static var body: Body { +// let inactiveTitle = Child(MultilineTextComponent.self) +// let inactiveValue = Child(MultilineTextComponent.self) +// let activeTitle = Child(MultilineTextComponent.self) +// let activeValue = Child(MultilineTextComponent.self) +// let animation = Child(PremiumLimitAnimationComponent.self) +// +// return { context in +// let component = context.component +// +// let height: CGFloat = 120.0 +// let lineHeight: CGFloat = 30.0 +// +// let leftTextColor: UIColor +// let rightTextColor: UIColor +// if component.invertProgress { +// leftTextColor = component.activeTitleColor +// rightTextColor = component.inactiveTitleColor +// } else { +// leftTextColor = component.inactiveTitleColor +// rightTextColor = component.activeTitleColor +// } +// +// let animation = animation.update( +// component: PremiumLimitAnimationComponent( +// iconName: component.badgeIconName, +// inactiveColor: component.inactiveColor, +// activeColors: component.activeColors, +// inactiveTextColor: component.inactiveTitleColor, +// inactiveTitle: component.inactiveTitle, +// inactiveValue: component.inactiveValue, +// activeTextColor: component.activeTitleColor, +// activeTitle: component.activeTitle, +// activeValue: component.activeValue, +// badgeTextColor: component.activeTitleColor, +// badgeText: component.badgeText, +// badgePosition: component.badgePosition, +// badgeGraphPosition: component.badgeGraphPosition, +// invertProgress: component.invertProgress, +// isPremiumDisabled: component.isPremiumDisabled +// ), +// 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)) +// ) +// +// if !component.isPremiumDisabled { +// let inactiveTitle = inactiveTitle.update( +// component: MultilineTextComponent( +// text: .plain( +// NSAttributedString( +// string: component.inactiveTitle, +// font: Font.semibold(15.0), +// textColor: leftTextColor +// ) +// ) +// ), +// availableSize: context.availableSize, +// transition: context.transition +// ) +// +// let inactiveValue = inactiveValue.update( +// component: MultilineTextComponent( +// text: .plain( +// NSAttributedString( +// string: component.inactiveValue, +// font: Font.semibold(15.0), +// textColor: leftTextColor +// ) +// ) +// ), +// availableSize: context.availableSize, +// transition: context.transition +// ) +// +// let activeTitle = activeTitle.update( +// component: MultilineTextComponent( +// text: .plain( +// NSAttributedString( +// string: component.activeTitle, +// font: Font.semibold(15.0), +// textColor: rightTextColor +// ) +// ) +// ), +// availableSize: context.availableSize, +// transition: context.transition +// ) +// +// let activeValue = activeValue.update( +// component: MultilineTextComponent( +// text: .plain( +// NSAttributedString( +// string: component.activeValue, +// font: Font.semibold(15.0), +// textColor: rightTextColor +// ) +// ) +// ), +// availableSize: context.availableSize, +// transition: context.transition +// ) +// +// let activityPosition = floor(context.availableSize.width * component.badgeGraphPosition) +// +// var inactiveTitleOpacity: CGFloat = 1.0 +// var inactiveValueOpacity: CGFloat = 1.0 +// +// if 12.0 + inactiveValue.size.width + 4.0 + inactiveTitle.size.width + 12.0 >= activityPosition - 8.0 { +// inactiveTitleOpacity = 0.0 +// if 12.0 + inactiveValue.size.width + 12.0 >= activityPosition - 8.0 { +// inactiveValueOpacity = 0.0 +// } +// } +// +// context.add(inactiveTitle +// .position(CGPoint(x: inactiveTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0)) +// .opacity(inactiveTitleOpacity) +// ) +// +// context.add(inactiveValue +// .position(CGPoint(x: activityPosition - inactiveValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0)) +// .opacity(inactiveValueOpacity) +// ) +// +// context.add(activeTitle +// .position(CGPoint(x: activityPosition + 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 @@ -1047,7 +1202,7 @@ private final class LimitSheetContent: CombinedComponent { premiumTitle = "" - badgePosition = 0.32 + badgePosition = 1.0 badgeGraphPosition = badgePosition invertProgress = true diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 01646ad42d..a2d0072614 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1872,6 +1872,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSubject = .storiesWeekly case .storiesMonthly: mappedSubject = .storiesMonthly + case let .storiesChannelBoost(level, link): + mappedSubject = .storiesChannelBoost(level: level, link: link) } return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, forceDark: forceDark, cancel: cancel, action: action) }