vds_ios/VDS/Components/PriceLockup/PriceLockup.swift
Matt Bruce 69cfb38149 cleaned up code
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-15 15:43:01 -05:00

334 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, DefaultValuing, 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, DefaultValuing, CaseIterable {
case xxxsmall = "3XSmall"
case xxsmall = "2XSmall"
case xsmall = "XSmall"
case small
case medium
case large
case xlarge = "XLarge"
case xxlarge = "2XLarge"
public static var defaultValue: Self { .medium }
}
/// Enum used to describe the kind of PriceLockup.
public enum Kind: String, DefaultValuing, CaseIterable {
case primary, secondary, savings
/// The default kind is 'primary'.
public static var defaultValue : Self { .primary }
/// Color configuation relative to kind.
public var colorConfiguration: SurfaceColorConfiguration {
switch self {
case .primary:
return SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark)
case .secondary:
return SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark)
case .savings:
return SurfaceColorConfiguration(VDSColor.paletteGreen26, VDSColor.paletteGreen36)
}
}
}
//--------------------------------------------------
// 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 = .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 = .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 = .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) }
// TextStyle for the size.
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 = formatText()
priceLockupLabel.surface = surface
// Set the attributed text
updateLabelAttributes()
}
open override func setDefaults() {
super.setDefaults()
bold = false
hideCurrency = false
leadingText = nil
price = nil
kind = .defaultValue
size = .defaultValue
strikethrough = false
term = .defaultValue
trailingText = nil
superscript = nil
uniformSize = false
}
/// Resets to default settings.
open override func reset() {
priceLockupLabel.reset()
super.reset()
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------
// Update PriceLockup text attributes
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
}
// Get text for PriceLockup.
private func formatText() -> String {
var text : String = ""
let space = " "
let delimiter = "/"
delimiterIndex = 0
strikethroughLength = 0
let currency: String = hideCurrency ? "" : "$"
if let leadingText {
text.append(leadingText)
text.append(space)
delimiterIndex = delimiterIndex + leadingText.count + space.count
}
strikethroughLocation = delimiterIndex
if let price = price?.clean {
text.append(currency)
text.append(price)
delimiterIndex = delimiterIndex + price.count + currency.count
strikethroughLength = price.count + currency.count
}
if term != .none {
text.append(delimiter)
text.append(term.type)
strikethroughLength = strikethroughLength + delimiter.count + term.type.count
}
if let trailingText {
text.append(space)
text.append(trailingText)
}
if let superscript {
text.append(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(describing: self)
}
}