315 lines
12 KiB
Swift
315 lines
12 KiB
Swift
//
|
|
// PriceLockup.swift
|
|
// VDS
|
|
//
|
|
// Created by Kanamarlapudi, Vasavi on 06/08/24.
|
|
//
|
|
|
|
import Foundation
|
|
import UIKit
|
|
import VDSCoreTokens
|
|
|
|
@objcMembers
|
|
@objc(VDSPriceLockup)
|
|
open class PriceLockup: View {
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Initializers
|
|
//--------------------------------------------------
|
|
required public init() {
|
|
super.init(frame: .zero)
|
|
}
|
|
|
|
public override init(frame: CGRect) {
|
|
super.init(frame: .zero)
|
|
}
|
|
|
|
public required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Enums
|
|
//--------------------------------------------------
|
|
/// Enum used to describe the term of PriceLockup.
|
|
public enum Term: String, CaseIterable {
|
|
case month, year, biennial, none
|
|
|
|
/// The default term is 'month'.
|
|
public static var defaultValue : Self { .month }
|
|
|
|
/// Text for this term of PriceLockup.
|
|
public var type: String {
|
|
switch self {
|
|
case .month:
|
|
return "mo"
|
|
case .year:
|
|
return "yr"
|
|
case .biennial:
|
|
return "biennial"
|
|
case .none:
|
|
return ""
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Enum that represents the size availble for PriceLockup.
|
|
public enum Size: String, CaseIterable {
|
|
case xxxsmall
|
|
case xxsmall
|
|
case xsmall
|
|
case small
|
|
case medium
|
|
case large
|
|
case xlarge
|
|
case xxlarge
|
|
|
|
public static var defaultValue: Self { .medium }
|
|
}
|
|
|
|
/// Enum used to describe the kind of PriceLockup.
|
|
public enum Kind: String, CaseIterable {
|
|
case primary, secondary, savings
|
|
|
|
/// The default kind is 'primary'.
|
|
public static var defaultValue : Self { .primary }
|
|
|
|
/// Color configuation relative to kind.
|
|
public var colorConfiguration: ViewColorConfiguration {
|
|
switch self {
|
|
case .primary:
|
|
return ViewColorConfiguration().with {
|
|
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false)}
|
|
case .secondary:
|
|
return ViewColorConfiguration().with {
|
|
$0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: false)}
|
|
case .savings:
|
|
return ViewColorConfiguration().with {
|
|
$0.setSurfaceColors(VDSColor.paletteGreen26, VDSColor.paletteGreen36, forDisabled: false)}
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Public Properties
|
|
//--------------------------------------------------
|
|
|
|
/// If true, the component will render as bold.
|
|
open var bold: Bool = false { didSet { setNeedsUpdate() } }
|
|
|
|
/// Currency - If hideCurrency true, the component will render without currency.
|
|
open var hideCurrency: Bool = false { didSet { setNeedsUpdate() } }
|
|
|
|
/// Leading text for the component.
|
|
open var leadingText: String? { didSet { setNeedsUpdate() } }
|
|
|
|
/// Value rendered for the component.
|
|
open var price: Float? { didSet { setNeedsUpdate() } }
|
|
|
|
/// Color to the component. The default kind is primary.
|
|
open var kind: Kind = Kind.defaultValue { didSet { setNeedsUpdate() } }
|
|
|
|
/// Size of the component. It varies by size and viewport(mobile/Tablet).
|
|
/// The default size is medium with viewport mobile.
|
|
open var size: Size = Size.defaultValue { didSet { setNeedsUpdate() } }
|
|
|
|
/// If true, the component with a strikethrough. It applies only when uniformSize is true.
|
|
/// Does not apply a strikethrough format to leading and trailing text.
|
|
open var strikethrough: Bool = false { didSet { setNeedsUpdate() } }
|
|
|
|
/// Term text for the component. The default term is 'month'.
|
|
/// Superscript placement can vary when term and delimeter are "none".
|
|
open var term: Term = Term.defaultValue { didSet { setNeedsUpdate() } }
|
|
|
|
/// Trailing text for the component.
|
|
open var trailingText: String? { didSet { setNeedsUpdate() } }
|
|
|
|
/// Superscript text for the component.
|
|
open var superscript: String? { didSet { setNeedsUpdate() } }
|
|
|
|
/// If true, currency and value have the same font text style as delimeter, term label and superscript.
|
|
/// This will render the pricing and term sections as a uniform size.
|
|
open var uniformSize: Bool = false { didSet { setNeedsUpdate() } }
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Private Properties
|
|
//--------------------------------------------------
|
|
internal var priceLockupLabel = Label().with {
|
|
$0.isAccessibilityElement = true
|
|
$0.lineBreakMode = .byWordWrapping
|
|
}
|
|
|
|
internal var delimiterIndex = 0
|
|
internal var strikethroughLocation = 0
|
|
internal var strikethroughLength = 0
|
|
|
|
internal var textPosition:TextPosition = .preDelimiter
|
|
enum TextPosition: String, CaseIterable {
|
|
case preDelimiter, postDelimiter
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Configuration Properties
|
|
//--------------------------------------------------
|
|
internal var containerSize: CGSize { CGSize(width: 45, height: 44) }
|
|
|
|
private var textStyle: TextStyle.StandardStyle {
|
|
switch (size, textPosition) {
|
|
case (.xxxsmall, .preDelimiter), (.xxxsmall, .postDelimiter):
|
|
return .micro
|
|
|
|
case (.xxsmall, .preDelimiter), (.xxsmall, .postDelimiter):
|
|
return .bodySmall
|
|
|
|
case (.xsmall, .preDelimiter), (.xsmall, .postDelimiter):
|
|
return .bodyMedium
|
|
|
|
case (.small, .preDelimiter), (.small, .postDelimiter):
|
|
return .bodyLarge
|
|
|
|
case (.medium, .preDelimiter):
|
|
return UIDevice.isIPad ? .titleSmall : .titleMedium
|
|
|
|
case (.medium, .postDelimiter):
|
|
return .bodyLarge
|
|
|
|
case (.large, .preDelimiter):
|
|
return UIDevice.isIPad ? .titleMedium : .titleLarge
|
|
|
|
case (.large, .postDelimiter):
|
|
return UIDevice.isIPad ? .titleSmall : .titleMedium
|
|
|
|
case (.xlarge, .preDelimiter):
|
|
return UIDevice.isIPad ? .titleLarge : .titleXLarge
|
|
|
|
case (.xlarge, .postDelimiter):
|
|
return UIDevice.isIPad ? .titleMedium : .titleLarge
|
|
|
|
case (.xxlarge, .preDelimiter):
|
|
return UIDevice.isIPad ? .titleXLarge : .featureSmall
|
|
|
|
case (.xxlarge, .postDelimiter):
|
|
return UIDevice.isIPad ? .titleLarge : .titleXLarge
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Overrides
|
|
//--------------------------------------------------
|
|
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
|
open override func setup() {
|
|
super.setup()
|
|
|
|
// Price lockup label
|
|
addSubview(priceLockupLabel)
|
|
priceLockupLabel.pinToSuperView()
|
|
}
|
|
|
|
/// Used to make changes to the View based off a change events or from local properties.
|
|
open override func updateView() {
|
|
super.updateView()
|
|
|
|
priceLockupLabel.text = fetchText()
|
|
priceLockupLabel.surface = surface
|
|
|
|
// Set the attributed text
|
|
updateLabelAttributes()
|
|
}
|
|
|
|
/// Resets to default settings.
|
|
open override func reset() {
|
|
super.reset()
|
|
shouldUpdateView = false
|
|
priceLockupLabel.reset()
|
|
shouldUpdateView = true
|
|
setNeedsUpdate()
|
|
}
|
|
|
|
open override var accessibilityElements: [Any]? {
|
|
get {
|
|
return nil
|
|
}
|
|
set {}
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Private Methods
|
|
//--------------------------------------------------
|
|
func updateLabelAttributes() {
|
|
var attributes: [any LabelAttributeModel] = []
|
|
attributes.append(ColorLabelAttribute(location: 0,
|
|
length: priceLockupLabel.text.count,
|
|
color: kind.colorConfiguration.getColor(self)))
|
|
textPosition = .postDelimiter
|
|
if strikethrough {
|
|
|
|
// strike applies only when uniformSize true. Does not apply a strikethrough format to leading, trailing, and superscript text.
|
|
attributes.append(TextStyleLabelAttribute(location: 0,
|
|
length: priceLockupLabel.text.count,
|
|
textStyle: bold ? textStyle.bold : textStyle.regular,
|
|
textPosition: .left))
|
|
attributes.append(StrikeThroughLabelAttribute(location:strikethroughLocation, length: strikethroughLength))
|
|
} else if uniformSize {
|
|
|
|
// currency and value have the same font text style as delimeter, term, trailing text and superscript.
|
|
attributes.append(TextStyleLabelAttribute(location: 0,
|
|
length: priceLockupLabel.text.count,
|
|
textStyle: bold ? textStyle.bold : textStyle.regular,
|
|
textPosition: .left))
|
|
} else {
|
|
|
|
// size updates relative to predelimiter, postdelimiter
|
|
if delimiterIndex > 0 {
|
|
textPosition = .preDelimiter
|
|
attributes.append(TextStyleLabelAttribute(location: 0,
|
|
length: delimiterIndex,
|
|
textStyle: bold ? textStyle.bold : textStyle.regular,
|
|
textPosition: .left))
|
|
|
|
textPosition = .postDelimiter
|
|
attributes.append(TextStyleLabelAttribute(location: delimiterIndex,
|
|
length: priceLockupLabel.text.count-delimiterIndex,
|
|
textStyle: bold ? textStyle.bold : textStyle.regular,
|
|
textPosition: .left))
|
|
}
|
|
}
|
|
priceLockupLabel.attributes = attributes
|
|
}
|
|
|
|
open func fetchText() -> String {
|
|
var text : String = ""
|
|
let space = " "
|
|
let delimiter = "/"
|
|
delimiterIndex = 0
|
|
strikethroughLength = 0
|
|
let currency: String = hideCurrency ? "" : "$"
|
|
if let leadingStr = leadingText {
|
|
text = text + leadingStr + space
|
|
delimiterIndex = delimiterIndex + leadingStr.count + space.count
|
|
}
|
|
strikethroughLocation = delimiterIndex
|
|
if let value = price {
|
|
let valueStr = "\(value.clean)"
|
|
text = text + currency + valueStr
|
|
delimiterIndex = delimiterIndex + valueStr.count + currency.count
|
|
strikethroughLength = valueStr.count + currency.count
|
|
}
|
|
if term != .none {
|
|
text = text + delimiter + term.type
|
|
strikethroughLength = strikethroughLength + delimiter.count + term.type.count
|
|
}
|
|
if let trailingStr = trailingText {
|
|
text = text + space + trailingStr
|
|
}
|
|
text = text + (superscript ?? "")
|
|
return text
|
|
}
|
|
}
|
|
|
|
extension Float {
|
|
// remove a decimal from a float if the decimal is equal to 0
|
|
var clean: String {
|
|
return self.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(self)
|
|
}
|
|
}
|