Swiftgram/submodules/PremiumUI/Sources/PremiumOptionComponent.swift
2024-02-16 15:29:55 -04:00

341 lines
12 KiB
Swift

import Foundation
import UIKit
import Display
import ComponentFlow
import MultilineTextComponent
import CheckNode
import Markdown
final class PremiumOptionComponent: CombinedComponent {
let title: String
let subtitle: String
let labelPrice: String
let discount: String
let multiple: Bool
let selected: Bool
let primaryTextColor: UIColor
let secondaryTextColor: UIColor
let accentColor: UIColor
let checkForegroundColor: UIColor
let checkBorderColor: UIColor
init(
title: String,
subtitle: String,
labelPrice: String,
discount: String,
multiple: Bool = false,
selected: Bool,
primaryTextColor: UIColor,
secondaryTextColor: UIColor,
accentColor: UIColor,
checkForegroundColor: UIColor,
checkBorderColor: UIColor
) {
self.title = title
self.subtitle = subtitle
self.labelPrice = labelPrice
self.discount = discount
self.multiple = multiple
self.selected = selected
self.primaryTextColor = primaryTextColor
self.secondaryTextColor = secondaryTextColor
self.accentColor = accentColor
self.checkForegroundColor = checkForegroundColor
self.checkBorderColor = checkBorderColor
}
static func ==(lhs: PremiumOptionComponent, rhs: PremiumOptionComponent) -> Bool {
if lhs.title != rhs.title {
return false
}
if lhs.subtitle != rhs.subtitle {
return false
}
if lhs.labelPrice != rhs.labelPrice {
return false
}
if lhs.discount != rhs.discount {
return false
}
if lhs.multiple != rhs.multiple {
return false
}
if lhs.selected != rhs.selected {
return false
}
if lhs.primaryTextColor != rhs.primaryTextColor {
return false
}
if lhs.secondaryTextColor != rhs.secondaryTextColor {
return false
}
if lhs.accentColor != rhs.accentColor {
return false
}
if lhs.checkForegroundColor != rhs.checkForegroundColor {
return false
}
if lhs.checkBorderColor != rhs.checkBorderColor {
return false
}
return true
}
static var body: Body {
let check = Child(CheckComponent.self)
let title = Child(MultilineTextComponent.self)
let subtitle = Child(MultilineTextComponent.self)
let discountBackground = Child(RoundedRectangle.self)
let discount = Child(MultilineTextComponent.self)
let label = Child(MultilineTextComponent.self)
return { context in
let component = context.component
var insets = UIEdgeInsets(top: 11.0, left: 46.0, bottom: 13.0, right: 16.0)
let label = label.update(
component: MultilineTextComponent(
text: .plain(
NSAttributedString(
string: component.labelPrice,
font: Font.regular(17),
textColor: component.secondaryTextColor
)
),
maximumNumberOfLines: 1
),
availableSize: context.availableSize,
transition: context.transition
)
let title = title.update(
component: MultilineTextComponent(
text: .plain(
NSAttributedString(
string: component.title,
font: Font.regular(17),
textColor: component.primaryTextColor
)
),
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - insets.left - insets.right - label.size.width, height: context.availableSize.height),
transition: context.transition
)
let discountSize: CGSize
if !component.discount.isEmpty {
let discount = discount.update(
component: MultilineTextComponent(
text: .plain(
NSAttributedString(
string: component.discount,
font: Font.with(size: 14.0, design: .round, weight: .semibold, traits: []),
textColor: .white
)
),
maximumNumberOfLines: 1
),
availableSize: context.availableSize,
transition: context.transition
)
discountSize = CGSize(width: discount.size.width + 6.0, height: 18.0)
let discountBackground = discountBackground.update(
component: RoundedRectangle(
color: component.accentColor,
cornerRadius: 5.0
),
availableSize: discountSize,
transition: context.transition
)
let discountPosition = CGPoint(x: insets.left + title.size.width + 6.0 + discountSize.width / 2.0, y: insets.top + title.size.height / 2.0)
context.add(discountBackground
.position(discountPosition)
)
context.add(discount
.position(discountPosition)
)
} else {
discountSize = CGSize(width: 0.0, height: 18.0)
}
var spacing: CGFloat = 0.0
var subtitleSize = CGSize()
if !component.subtitle.isEmpty {
spacing = 2.0
let subtitleFont = Font.regular(13)
let subtitleColor = component.secondaryTextColor
let subtitleString = parseMarkdownIntoAttributedString(
component.subtitle,
attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: subtitleFont, textColor: subtitleColor),
bold: MarkdownAttributeSet(font: subtitleFont, textColor: subtitleColor, additionalAttributes: [NSAttributedString.Key.strikethroughStyle.rawValue: NSUnderlineStyle.single.rawValue as NSNumber]),
link: MarkdownAttributeSet(font: subtitleFont, textColor: subtitleColor),
linkAttribute: { _ in return nil }
)
)
let subtitle = subtitle.update(
component: MultilineTextComponent(
text: .plain(subtitleString),
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - insets.left - insets.right, height: context.availableSize.height),
transition: context.transition
)
context.add(subtitle
.position(CGPoint(x: insets.left + subtitle.size.width / 2.0, y: insets.top + title.size.height + spacing + subtitle.size.height / 2.0))
)
subtitleSize = subtitle.size
insets.top -= 2.0
insets.bottom -= 2.0
}
let check = check.update(
component: CheckComponent(
theme: CheckComponent.Theme(
backgroundColor: component.accentColor,
strokeColor: component.checkForegroundColor,
borderColor: component.checkBorderColor,
overlayBorder: false,
hasInset: false,
hasShadow: false
),
selected: component.selected
),
availableSize: context.availableSize,
transition: context.transition
)
context.add(title
.position(CGPoint(x: insets.left + title.size.width / 2.0, y: insets.top + title.size.height / 2.0))
)
let size = CGSize(width: context.availableSize.width, height: insets.top + title.size.height + spacing + subtitleSize.height + insets.bottom)
let distance = context.availableSize.width - insets.left - insets.right - label.size.width - subtitleSize.width
let labelY: CGFloat
if distance > 8.0 {
labelY = size.height / 2.0
} else {
labelY = insets.top + title.size.height / 2.0
}
context.add(label
.position(CGPoint(x: context.availableSize.width - insets.right - label.size.width / 2.0, y: labelY))
)
context.add(check
.position(CGPoint(x: 4.0 + check.size.width / 2.0, y: size.height / 2.0))
)
return size
}
}
}
private final class CheckComponent: Component {
struct Theme: Equatable {
public let backgroundColor: UIColor
public let strokeColor: UIColor
public let borderColor: UIColor
public let overlayBorder: Bool
public let hasInset: Bool
public let hasShadow: Bool
public let filledBorder: Bool
public let borderWidth: CGFloat?
public init(backgroundColor: UIColor, strokeColor: UIColor, borderColor: UIColor, overlayBorder: Bool, hasInset: Bool, hasShadow: Bool, filledBorder: Bool = false, borderWidth: CGFloat? = nil) {
self.backgroundColor = backgroundColor
self.strokeColor = strokeColor
self.borderColor = borderColor
self.overlayBorder = overlayBorder
self.hasInset = hasInset
self.hasShadow = hasShadow
self.filledBorder = filledBorder
self.borderWidth = borderWidth
}
var checkNodeTheme: CheckNodeTheme {
return CheckNodeTheme(
backgroundColor: self.backgroundColor,
strokeColor: self.strokeColor,
borderColor: self.borderColor,
overlayBorder: self.overlayBorder,
hasInset: self.hasInset,
hasShadow: self.hasShadow,
filledBorder: self.filledBorder,
borderWidth: self.borderWidth
)
}
}
let theme: Theme
let selected: Bool
init(
theme: Theme,
selected: Bool
) {
self.theme = theme
self.selected = selected
}
static func ==(lhs: CheckComponent, rhs: CheckComponent) -> Bool {
if lhs.theme != rhs.theme {
return false
}
if lhs.selected != rhs.selected {
return false
}
return true
}
final class View: UIView {
private var currentValue: CGFloat?
private var animator: DisplayLinkAnimator?
private var checkLayer: CheckLayer {
return self.layer as! CheckLayer
}
override class var layerClass: AnyClass {
return CheckLayer.self
}
init() {
super.init(frame: CGRect())
}
required init?(coder aDecoder: NSCoder) {
preconditionFailure()
}
func update(component: CheckComponent, availableSize: CGSize, transition: Transition) -> CGSize {
self.checkLayer.setSelected(component.selected, animated: true)
self.checkLayer.theme = component.theme.checkNodeTheme
return CGSize(width: 22.0, height: 22.0)
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}