Swiftgram/submodules/PremiumUI/Sources/PremiumLimitScreen.swift
2022-05-19 21:15:31 +04:00

851 lines
36 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SwiftSignalKit
import AccountContext
import TelegramPresentationData
import PresentationDataUtils
import ComponentFlow
import ViewControllerComponent
import SheetComponent
import MultilineTextComponent
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
private let badgeText: String?
private let badgePosition: CGFloat
init(
iconName: String,
inactiveColor: UIColor,
activeColors: [UIColor],
textColor: UIColor,
badgeText: String?,
badgePosition: CGFloat
) {
self.iconName = iconName
self.inactiveColor = inactiveColor
self.activeColors = activeColors
self.textColor = textColor
self.badgeText = badgeText
self.badgePosition = badgePosition
}
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
}
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
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.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.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.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(component: PremiumLimitAnimationComponent, 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: 0.0, y: 0.0))
positionAnimation.toValue = NSValue(cgPoint: self.badgeView.center)
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)
Queue.mainQueue().after(0.5, {
self.hapticFeedback.impact(.light)
})
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")
if let badgeText = component.badgeText, let num = Int(badgeText) {
self.badgeCountLabel.text(num: num)
}
}
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, height: lineHeight))
self.activeContainer.frame = CGRect(origin: CGPoint(x: containerFrame.width / 2.0, y: 0.0), size: CGSize(width: containerFrame.width / 2.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 - self.activeBackground.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
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.badgeView.bounds = CGRect(origin: .zero, size: badgeSize)
self.badgeView.center = CGPoint(x: availableSize.width * component.badgePosition, y: 82.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 !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() {
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<Empty>, 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 inactiveValue: String
public let inactiveTitleColor: UIColor
public let activeTitle: String
public let activeValue: String
public let activeTitleColor: UIColor
public let badgeIconName: String
public let badgeText: String?
public let badgePosition: CGFloat
public init(
inactiveColor: UIColor,
activeColors: [UIColor],
inactiveTitle: String,
inactiveValue: String,
inactiveTitleColor: UIColor,
activeTitle: String,
activeValue: String,
activeTitleColor: UIColor,
badgeIconName: String,
badgeText: String?,
badgePosition: CGFloat
) {
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
}
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
}
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 inactiveTitle = inactiveTitle.update(
component: MultilineTextComponent(
text: .plain(
NSAttributedString(
string: component.inactiveTitle,
font: Font.semibold(15.0),
textColor: component.inactiveTitleColor
)
)
),
availableSize: context.availableSize,
transition: context.transition
)
let inactiveValue = inactiveValue.update(
component: MultilineTextComponent(
text: .plain(
NSAttributedString(
string: component.inactiveValue,
font: Font.semibold(15.0),
textColor: component.inactiveTitleColor
)
)
),
availableSize: context.availableSize,
transition: context.transition
)
let activeTitle = activeTitle.update(
component: MultilineTextComponent(
text: .plain(
NSAttributedString(
string: component.activeTitle,
font: Font.semibold(15.0),
textColor: component.activeTitleColor
)
)
),
availableSize: context.availableSize,
transition: context.transition
)
let activeValue = activeValue.update(
component: MultilineTextComponent(
text: .plain(
NSAttributedString(
string: component.activeValue,
font: Font.semibold(15.0),
textColor: 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,
badgeText: component.badgeText,
badgePosition: component.badgePosition
),
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(inactiveValue
.position(CGPoint(x: context.availableSize.width / 2.0 - activeValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0))
)
context.add(activeTitle
.position(CGPoint(x: context.availableSize.width / 2.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
let context: AccountContext
let subject: PremiumLimitScreen.Subject
let count: Int32
let action: () -> Void
let dismiss: () -> Void
init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, action: @escaping () -> Void, dismiss: @escaping () -> Void) {
self.context = context
self.subject = subject
self.count = count
self.action = action
self.dismiss = dismiss
}
static func ==(lhs: LimitSheetContent, rhs: LimitSheetContent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.subject != rhs.subject {
return false
}
if lhs.count != rhs.count {
return false
}
return true
}
final class State: ComponentState {
private let context: AccountContext
private var disposable: Disposable?
var initialized = false
var limits: EngineConfiguration.UserLimits
var premiumLimits: EngineConfiguration.UserLimits
init(context: AccountContext, subject: PremiumLimitScreen.Subject) {
self.context = context
self.limits = EngineConfiguration.UserLimits.defaultValue
self.premiumLimits = EngineConfiguration.UserLimits.defaultValue
super.init()
self.disposable = (context.engine.data.get(
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true)
) |> deliverOnMainQueue).start(next: { [weak self] result in
if let strongSelf = self {
let (limits, premiumLimits) = result
strongSelf.initialized = true
strongSelf.limits = limits
strongSelf.premiumLimits = premiumLimits
strongSelf.updated(transition: .immediate)
}
})
}
deinit {
self.disposable?.dispose()
}
}
func makeState() -> State {
return State(context: self.context, subject: self.subject)
}
static var body: Body {
let title = Child(MultilineTextComponent.self)
let text = Child(MultilineTextComponent.self)
let limit = Child(PremiumLimitDisplayComponent.self)
let button = Child(SolidRoundedButtonComponent.self)
return { context in
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
let component = context.component
let theme = environment.theme
let strings = environment.strings
let state = context.state
let subject = component.subject
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let textSideInset: CGFloat = 24.0 + environment.safeInsets.left
let iconName: String
let badgeText: String
let string: String
let defaultValue: String
let premiumValue: String
let badgePosition: CGFloat
switch subject {
case .folders:
let limit = state.limits.maxFoldersCount
let premiumLimit = state.premiumLimits.maxFoldersCount
iconName = "Premium/Folder"
badgeText = "\(component.count)"
string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string
defaultValue = component.count > limit ? "\(limit)" : ""
premiumValue = "\(premiumLimit)"
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
case .chatsInFolder:
let limit = state.limits.maxFolderChatsCount
let premiumLimit = state.premiumLimits.maxFolderChatsCount
iconName = "Premium/Chat"
badgeText = "\(component.count)"
string = strings.Premium_MaxChatsInFolderCountText("\(limit)", "\(premiumLimit)").string
defaultValue = component.count > limit ? "\(limit)" : ""
premiumValue = "\(premiumLimit)"
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
case .pins:
let limit = state.limits.maxPinnedChatCount
let premiumLimit = state.premiumLimits.maxPinnedChatCount
iconName = "Premium/Pin"
badgeText = "\(component.count)"
string = strings.Premium_MaxPinsText("\(limit)", "\(premiumLimit)").string
defaultValue = component.count > limit ? "\(limit)" : ""
premiumValue = "\(premiumLimit)"
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
case .files:
let limit: Int64 = 2048 * 1024 * 1024 * Int64(state.limits.maxUploadFileParts)
let premiumLimit: Int64 = 4096 * 1024 * 1024 * Int64(state.limits.maxUploadFileParts)
iconName = "Premium/File"
badgeText = 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
defaultValue = ""
premiumValue = dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
}
let title = title.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: strings.Premium_LimitReached,
font: Font.semibold(17.0),
textColor: theme.actionSheet.primaryTextColor,
paragraphAlignment: .center
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let textFont = Font.regular(17.0)
let boldTextFont = Font.semibold(17.0)
let textColor = theme.actionSheet.primaryTextColor
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in
return nil
})
let text = text.update(
component: MultilineTextComponent(
text: .markdown(text: string, attributes: markdownAttributes),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.0
),
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
transition: .immediate
)
if state.initialized {
let limit = limit.update(
component: PremiumLimitDisplayComponent(
inactiveColor: UIColor(rgb: 0xE9E9EA),
activeColors: [
UIColor(rgb: 0x0077ff),
UIColor(rgb: 0x6b93ff),
UIColor(rgb: 0x8878ff),
UIColor(rgb: 0xe46ace)
],
inactiveTitle: strings.Premium_Free,
inactiveValue: defaultValue,
inactiveTitleColor: .black,
activeTitle: strings.Premium_Premium,
activeValue: premiumValue,
activeTitleColor: .white,
badgeIconName: iconName,
badgeText: badgeText,
badgePosition: badgePosition
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height),
transition: .immediate
)
context.add(limit
.position(CGPoint(x: context.availableSize.width / 2.0, y: limit.size.height / 2.0 + 44.0))
)
}
let button = button.update(
component: SolidRoundedButtonComponent(
title: strings.Premium_IncreaseLimit,
theme: SolidRoundedButtonComponent.Theme(
backgroundColor: .black,
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: true,
iconName: "Premium/X2",
iconPosition: .right,
action: { [weak component] in
guard let component = component else {
return
}
component.dismiss()
component.action()
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
transition: context.transition
)
context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: 28.0))
)
context.add(text
.position(CGPoint(x: context.availableSize.width / 2.0, y: 228.0))
)
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: 228.0 + ceil(text.size.height / 2.0) + 38.0), size: button.size)
context.add(button
.position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY))
)
let contentSize = CGSize(width: context.availableSize.width, height: buttonFrame.maxY + 5.0 + environment.safeInsets.bottom)
return contentSize
}
}
}
private final class LimitSheetComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let subject: PremiumLimitScreen.Subject
let count: Int32
let action: () -> Void
init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, action: @escaping () -> Void) {
self.context = context
self.subject = subject
self.count = count
self.action = action
}
static func ==(lhs: LimitSheetComponent, rhs: LimitSheetComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.subject != rhs.subject {
return false
}
return true
}
static var body: Body {
let sheet = Child(SheetComponent<EnvironmentType>.self)
let animateOut = StoredActionSlot(Action<Void>.self)
return { context in
let environment = context.environment[EnvironmentType.self]
let controller = environment.controller
let sheet = sheet.update(
component: SheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(LimitSheetContent(
context: context.component.context,
subject: context.component.subject,
count: context.component.count,
action: context.component.action,
dismiss: {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
}
)),
backgroundColor: environment.theme.actionSheet.opaqueItemBackgroundColor,
animateOut: animateOut
),
environment: {
environment
SheetComponentEnvironment(
isDisplaying: environment.value.isVisible,
dismiss: {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
}
)
},
availableSize: context.availableSize,
transition: context.transition
)
context.add(sheet
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
return context.availableSize
}
}
}
public class PremiumLimitScreen: ViewControllerComponentContainer {
public enum Subject {
case folders
case chatsInFolder
case pins
case files
}
public init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, action: @escaping () -> Void) {
super.init(context: context, component: LimitSheetComponent(context: context, subject: subject, count: count, action: action), navigationBarAppearance: .none)
self.navigationPresentation = .flatModal
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
self.view.disablesInteractiveModalDismiss = true
}
}