import UIKit
/// Cell for displaying position, holding and order data
///
/// ![ScripListPHOCell](../img/DocumentationSnapshot.ScripListPHOCell.png)
public protocol ScripListPHOCellDelegate: AnyObject {
func didTapDetailsBottomView(at index: Int)
func didTapCancelBottomView(at index: Int)
func didTapModifyBottomView(at index: Int)
func didTapRetryBottomView(at index: Int)
}
open class ScripListPHOCell: UITableViewCell {
// MARK: - Properties
public weak var delegate: ScripListPHOCellDelegate?
private let leadingCheckbox: Checkbox = {
let checkbox = Checkbox()
checkbox.isHidden = true
checkbox.isUserInteractionEnabled = false
return checkbox
}()
private let rootStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
return stackView
}()
private let mainHorizontalStackView: UIStackView = {
let stackView = UIStackView()
stackView.isLayoutMarginsRelativeArrangement = true
stackView.directionalLayoutMargins = Constants.mainHorizontalStackViewDefaultMargins
return stackView
}()
private let bottomView: ScripListPHOView = {
let view = ScripListPHOView(size: .small, isTapNeededForLegs: false)
view.setContentHuggingPriority(.defaultHigh, for: .vertical)
view.isHidden = true
return view
}()
private let failureBottomView: BottomFailureView = {
let view = BottomFailureView()
view.isHidden = true
return view
}()
private let mainVerticalStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = .halfOfQuarterOfDefaultMargin
return stackView
}()
private let leadingPrimaryLabel = ScripName()
private let trailingPrimaryLabel: Label = {
let label = Label(.bodyBold)
label.textAlignment = .right
return label
}()
private let trailingPrimaryImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .center
imageView.isHidden = true
return imageView
}()
private let trailingPrimaryStackView: UIStackView = {
let stackView = UIStackView()
stackView.spacing = .halfOfThreeQuartersOfDefaultMargin
return stackView
}()
private let primaryStackView = UIStackView()
private let leadingSecondaryLabel: Label = {
let label = Label(.meta)
label.textColor = .appUI5Alt
return label
}()
private let trailingStackViewForTopLabel: UIStackView = {
let stackView = UIStackView()
stackView.spacing = .halfOfDefaultMargin
stackView.alignment = .center
return stackView
}()
private let warningIcon: UIImageView = {
let imageView = UIImageView()
imageView.image = .appIconErrorCircle16.withColor(.appBackgroundYellow)
imageView.contentMode = .scaleAspectFit
imageView.setContentHuggingPriority(.required, for: .horizontal)
imageView.isHidden = true
return imageView
}()
private let trailingSecondaryLabel: Label = {
let label = Label(.meta)
label.textColor = .appUI5Alt
label.textAlignment = .right
return label
}()
private let secondaryStackView = UIStackView()
private let leadingTertiaryLabel: Label = {
let label = Label(.meta)
label.textColor = .appUI5Alt
return label
}()
private let trailingTertiaryLabel: Label = {
let label = Label(.meta)
label.textAlignment = .right
label.textColor = .appUI5Alt
return label
}()
private let tertiaryStackView = UIStackView()
private let bottomButtonsStackView: UIStackView = {
let stackView = UIStackView()
stackView.backgroundColor = .appUi1
stackView.distribution = .fillEqually
stackView.isHidden = true
return stackView
}()
private lazy var detailsBottomView = makeCustomButtonView(image: .appIconFileList2Line, text: L10n.Other.details, shouldAddVerticalSeparator: false)
private lazy var modifyBottomView = makeCustomButtonView(image: .appIconEdit2Line, text: L10n.Other.modify)
private lazy var cancelBottomView = makeCustomButtonView(image: .appIconCloseCircleLine, text: L10n.Other.cancel)
private lazy var retryBottomView = makeCustomButtonView(image: .appIconReloadLine, text: L10n.Other.retry)
private let separatorStackView: UIStackView = {
let stackView = UIStackView()
stackView.isLayoutMarginsRelativeArrangement = true
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: .zero,
leading: .defaultMargin,
bottom: .zero,
trailing: .defaultMargin)
return stackView
}()
private let separator = Divider().withBackgroundColor(.appUi1)
private lazy var separatorHeightConstraint = separator.heightAnchor.constraint(equalToConstant: Constants.defaultSeparatorHeight)
// MARK: - Lifecycle
override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .appBackgroundDefault
selectedBackgroundView = UIView().withBackgroundColor(.appInteractiveUIPressed)
rootStackView.addArrangedSubviews(
mainHorizontalStackView.addingArrangedSubviews([
leadingCheckbox,
mainVerticalStackView.addingArrangedSubviews([
primaryStackView.addingArrangedSubviews([
leadingPrimaryLabel,
trailingPrimaryStackView.addingArrangedSubviews([
trailingStackViewForTopLabel.addingArrangedSubviews([trailingPrimaryLabel, warningIcon]),
trailingPrimaryImageView
])
]),
secondaryStackView.addingArrangedSubviews([
leadingSecondaryLabel,
trailingSecondaryLabel
]),
tertiaryStackView.addingArrangedSubviews([
leadingTertiaryLabel,
trailingTertiaryLabel
]),
bottomView,
failureBottomView
])
]),
separatorStackView.addingArrangedSubviews(separator),
bottomButtonsStackView.addingArrangedSubviews(detailsBottomView, modifyBottomView, cancelBottomView, retryBottomView)
)
contentView.addSubviewsForAutoLayout([rootStackView])
NSLayoutConstraint.activate([
leadingCheckbox.widthAnchor.constraint(equalToConstant: .threeAndHalfOfDefaultMargin),
separatorHeightConstraint,
rootStackView.topAnchor.constraint(equalTo: contentView.topAnchor),
rootStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
rootStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
rootStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
trailingPrimaryImageView.widthAnchor.constraint(equalToConstant: .defaultMargin)
])
detailsBottomView.addGestureRecognizer((UITapGestureRecognizer(target: self, action: #selector(didTapDetailsBottomView(_:)))))
cancelBottomView.addGestureRecognizer((UITapGestureRecognizer(target: self, action: #selector(didTapCancelBottomView(_:)))))
modifyBottomView.addGestureRecognizer((UITapGestureRecognizer(target: self, action: #selector(didTapModifyBottomView(_:)))))
retryBottomView.addGestureRecognizer((UITapGestureRecognizer(target: self, action: #selector(didTapRetryBottomView(_:)))))
}
@available(*, unavailable)
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override open func prepareForReuse() {
super.prepareForReuse()
failureBottomView.isHidden = true
trailingPrimaryImageView.isHidden = true
warningIcon.isHidden = true
bottomView.layoutIfNeeded()
}
override open func setEditing(_ editing: Bool, animated: Bool) {
// We need hide the default leading accessory, so will send editing as false here
super.setEditing(false, animated: animated)
}
override open func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
leadingCheckbox.setIsSelected(selected)
}
public func isHiddenSeparator(_ value: Bool) {
separator.isHidden = value
}
public func configure(name: String = "", PHO: PHO) {
switch PHO {
case let .orders(orderCategory):
updateForOrder(orderCategory)
case let .holdings(data):
updateForHolding(data: data)
case let .positions(data):
updateForPosition(data: data)
case let .basketOrder(data):
updateForBasketItem(data: data)
}
}
public func updateConditionalOrderLTP(with attributedText: NSAttributedString?) {
trailingPrimaryLabel.isHidden = attributedText == nil
trailingPrimaryLabel.attributedText = attributedText
}
public func updateBasketOrder(withLTP ltp: String, isBasketOrderExecuted: Bool, side: OrderSide) {
if isBasketOrderExecuted {
let trailingSecondaryAttributedText = NSMutableAttributedString(attributedString: NSAttributedString(string: L10n.Lists.ltpSuffix(ltp), attributes: [.foregroundColor: UIColor.appUI5Alt]))
trailingSecondaryAttributedText.append(NSAttributedString(string: " \(L10n.Lists.interpunct) "))
let sideText = side == .buy ? L10n.Lists.buy : L10n.Lists.sell
let sideTextColor = side == .buy ? UIColor.appTextPositiveTrend : UIColor.appTextNegativeTrend
let sideAttributedText = sideText.attributedString(textStyle: .meta, originalColor: sideTextColor, subStrings: (sideText, .appText2))
trailingSecondaryAttributedText.append(sideAttributedText)
trailingSecondaryLabel.attributedText = trailingSecondaryAttributedText
} else {
trailingSecondaryLabel.text = L10n.Lists.ltpSuffix(ltp)
}
}
public func updateConditionalOrderPNL(with pnlText: String?, pnlColor: UIColor) {
bottomView.updatePNL(pnlText, pnlColor: pnlColor)
}
public func updateWarningIconVisibility(to shouldShow: Bool) {
warningIcon.isHidden = !shouldShow
}
}
// MARK: - UpdateUIHelper
private typealias UpdateUIHelper = ScripListPHOCell
private extension UpdateUIHelper {
func setDefaultSeparator() {
separatorStackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: .zero,
leading: .defaultMargin,
bottom: .zero,
trailing: .defaultMargin)
separatorHeightConstraint.constant = Constants.defaultSeparatorHeight
layoutIfNeeded()
}
func setConditionalSeparator() {
separatorStackView.directionalLayoutMargins = .zero
separatorHeightConstraint.constant = Constants.conditionalSeparatorHeight
layoutIfNeeded()
}
func updateForOrder(_ order: PHO.OrderCategory) {
switch order {
case .regular(let (order, filling, trailingImage)):
trailingPrimaryLabel.isHidden = false
leadingSecondaryLabel.isHidden = false
trailingSecondaryLabel.isHidden = false
leadingTertiaryLabel.isHidden = false
trailingTertiaryLabel.isHidden = false
tertiaryStackView.isHidden = false
failureBottomView.isHidden = true
bottomView.isHidden = true
trailingPrimaryImageView.isHidden = true
backgroundColor = order.status == .failed ? .appDataNegativeSubtle : backgroundColor
leadingPrimaryLabel.configure(.scripName(title: order.name, tag: nil, titleFont: .bodyBold, trailingImage: trailingImage))
leadingSecondaryLabel.text = additionalScripInfo(index: order.index, instrumentType: order.instrumentType, date: order.date)
let productTypeAttributedText = NSAttributedString(string: "\(order.productType.title) ", attributes: [.foregroundColor: UIColor.appText1])
leadingTertiaryLabel.attributedText = productTypeAttributedText
trailingPrimaryLabel.textColor = order.status == .failed ? .appSupportError : .appText1
trailingPrimaryLabel.text = !order.isScheduled ? titleForOrderStatus(order.status) : L10n.Lists.scheduled
switch order.side {
case .buy:
trailingSecondaryLabel.textColor = .appTextPositiveTrend
trailingSecondaryLabel.text = L10n.Lists.buy
case .sell:
trailingSecondaryLabel.textColor = .appTextNegativeTrend
trailingSecondaryLabel.text = L10n.Lists.sell
}
let price: String = {
if case let .limit(limit) = filling {
return order.isCurrency ? FormattedNumber.currencyFourFractionals(limit).stringValue : FormattedNumber.currency(limit).stringValue
} else {
return L10n.Lists.market
}
}()
trailingTertiaryLabel.textColor = .appText2
trailingTertiaryLabel.text = "\(order.quantityFilled)/\(order.quantityOrdered) \(L10n.Other.qty)." + " @ " + price
separator.backgroundColor = order.isOpen ? .appUi1 : .appUi3
setDefaultSeparator()
if order.status == .failed {
trailingPrimaryImageView.isHidden = false
trailingPrimaryImageView.image = .appIconErrorCircle16
trailingPrimaryImageView.tintColor = .appSupportError
if !order.message.isEmpty {
failureBottomView.isHidden = false
failureBottomView.configure(with: .regularError(order.message))
}
}
warningIcon.isHidden = !order.shouldShowWarningIcon
case .conditional(let(parent, childLegs, scripDetails, message)):
warningIcon.isHidden = true
bottomView.isHidden = false
trailingPrimaryLabel.isHidden = false
leadingSecondaryLabel.isHidden = false
trailingSecondaryLabel.isHidden = false
leadingTertiaryLabel.isHidden = true
trailingTertiaryLabel.isHidden = true
tertiaryStackView.isHidden = true
failureBottomView.isHidden = true
trailingPrimaryImageView.isHidden = true
trailingPrimaryLabel.attributedText = nil
leadingPrimaryLabel.configure(.scripName(title: scripDetails.scripName,
tag: nil,
titleFont: .bodyBold))
leadingSecondaryLabel.text = additionalScripInfo(index: scripDetails.scripIndex, instrumentType: scripDetails.instrumentType, date: scripDetails.scripDate)
trailingSecondaryLabel.text = scripDetails.scripProductType.title
trailingSecondaryLabel.textColor = .appUi5
trailingPrimaryLabel.textColor = .appText2
updateConditionalOrderLTP(with: Self.getAttributedLTP(ltpValue: parent.ltp, ltpColor: parent.ltpColor))
updateConditionalOrderPNL(with: parent.pnlValue, pnlColor: parent.pnlColor)
setConditionalSeparator()
separator.backgroundColor = .appUi3
if let errorMessage = message, !errorMessage.isEmpty {
failureBottomView.isHidden = false
failureBottomView.configure(with: .conditionalError(errorMessage))
}
bottomView.configureParentStatusView(parent: parent)
bottomView.configureConditionalChildsViews(childs: childLegs, parentInfo: parent)
}
}
func updateForHolding(data: HoldingDataModel) {
warningIcon.isHidden = true
leadingPrimaryLabel.configure(.scripName(title: data.name, tag: nil, titleFont: .bodyBold))
updateOverall(overall: data.overall, percentage: data.percentageChange, showFourFractionalsForPrice: data.showFourFractionalsForPrice)
leadingSecondaryLabel.text = L10n.Lists.invested(FormattedNumber.currency(data.amountInvested).stringValue)
updateAveragePrice(price: data.averagePrice, showFourFractionalsForPrice: data.showFourFractionalsForPrice)
let quantityString = NSMutableAttributedString()
let quantity = NSAttributedString(
string: L10n.Lists.quantityPrefix(data.quantity),
attributes: [.foregroundColor: UIColor.appUI5Alt]
)
quantityString.append(quantity)
quantityString.append(getAdditionalQuantityInfo(additionalInfo: data.quantityAdditionalInfo))
leadingTertiaryLabel.attributedText = quantityString
if !data.isIlliquid {
updateTextForLTP(ltp: data.ltp, ltpPercentage: data.ltpPercentage, showFourFractionalsForPrice: data.showFourFractionalsForPrice)
} else {
trailingTertiaryLabel.text = L10n.UIElements.illiquid
}
separator.backgroundColor = data.isActive ? .appUi1 : .appUi3
}
func updateForPosition(data: PositionDataModel) {
warningIcon.isHidden = true
leadingPrimaryLabel.configure(.scripName(title: data.name, tag: nil, titleFont: .bodyBold))
updateOverall(overall: data.overall, showFourFractionalsForPrice: false)
leadingSecondaryLabel.text = additionalScripInfo(index: data.index, instrumentType: data.instrumentType, date: data.date)
updateAveragePrice(price: data.averagePriceOfShare, showFourFractionalsForPrice: data.showFourFractionalsForPrice)
updateQuantity(productType: data.productType.title, quantity: data.quantityOfShares)
updateTextForLTP(ltp: data.lastTradedPrice, ltpPercentage: data.ltpPercentage, showFourFractionalsForPrice: data.showFourFractionalsForPrice)
separator.backgroundColor = data.isOpen ? .appUi1 : .appUi3
bottomView.isHidden = true
}
func updateForBasketItem(data: BasketOrderDataModel) {
mainHorizontalStackView.directionalLayoutMargins = data.isEditing ? NSDirectionalEdgeInsets(top: .halfOfDefaultMargin, leading: .zero, bottom: .halfOfDefaultMargin, trailing: .defaultMargin) : Constants.mainHorizontalStackViewDefaultMargins
leadingCheckbox.isHidden = !data.isEditing
trailingPrimaryLabel.textAlignment = data.isEditing ? .left : .right
mainVerticalStackView.spacing = data.isEditing ? .halfOfThreeQuartersOfDefaultMargin : .halfOfQuarterOfDefaultMargin
trailingSecondaryLabel.isHidden = data.isEditing
tertiaryStackView.isHidden = data.isEditing
leadingPrimaryLabel.isTitleExpanded = data.expansionDetails.isExpanded
leadingPrimaryLabel.configure(.scripName(title: data.name,
shouldAddSeparatorAfterTitle: data.isEditing,
tag: nil,
titleFont: .bodyBold,
titleColor: data.isExecuted ? .appTextLink : .appText1,
isExpandable: data.isExecuted,
shouldShowTagDate: !data.isEditing))
leadingSecondaryLabel.text = additionalScripInfo(index: data.index, instrumentType: data.segment, date: nil)
let productTypeAttributedText = NSMutableAttributedString(attributedString: NSAttributedString(string: "\(data.productType.title)", attributes: [.foregroundColor: UIColor.appText1]))
productTypeAttributedText.append(NSAttributedString(string: " \(L10n.Lists.interpunct) "))
let quantityAttributedText = data.orderType.attributedString(textStyle: .meta, originalColor: .appUI5Alt, subStrings: (data.orderType, .appText2))
productTypeAttributedText.append(quantityAttributedText)
leadingTertiaryLabel.attributedText = productTypeAttributedText
if data.isExecuted {
trailingPrimaryLabel.textColor = data.orderStatus.color
trailingPrimaryLabel.text = !data.isScheduled ? titleForOrderStatus(data.orderStatus) : L10n.Lists.scheduled
} else {
switch data.side {
case .buy:
trailingPrimaryLabel.textColor = .appTextPositiveTrend
trailingPrimaryLabel.text = L10n.Lists.buy
case .sell:
trailingPrimaryLabel.textColor = .appTextNegativeTrend
trailingPrimaryLabel.text = L10n.Lists.sell
}
}
trailingSecondaryLabel.textColor = .appUi5
trailingTertiaryLabel.textColor = .appText2
let priceString = (data.shouldShowFourFractionalsForPrice ? FormattedNumber.currencyFourFractionals(data.price) : FormattedNumber.currency(data.price)).stringValue
let quantityText = data.isLimitPrice ? "\(data.quantity) \(L10n.Other.qty). @ \(priceString)" : "\(data.quantity) \(L10n.Other.qty)."
trailingTertiaryLabel.text = quantityText
bottomButtonsStackView.isHidden = !data.expansionDetails.isExpanded
guard data.isExecuted else { return }
detailsBottomView.isHidden = false
switch data.expansionDetails.buttonsConfig {
case .detailsModifyCancel:
modifyBottomView.isHidden = false
cancelBottomView.isHidden = false
retryBottomView.isHidden = true
case .detailsRetry:
retryBottomView.isHidden = false
modifyBottomView.isHidden = true
cancelBottomView.isHidden = true
case .details:
modifyBottomView.isHidden = true
cancelBottomView.isHidden = true
retryBottomView.isHidden = true
}
}
func titleForOrderStatus(_ orderStatus: OrderStatus) -> String {
switch orderStatus {
case .triggerPending:
return L10n.Lists.triggerPendingOrders
default:
return orderStatus.title
}
}
func additionalScripInfo(index: String, instrumentType: String?, date: Date?) -> String {
var string = "\(index)"
if let date = date {
string.append(" " + L10n.Lists.interpunct + " ")
string.append("\(DateFormatter.scripInlineSpacedFormatter.string(from: date).uppercased())")
}
if let instrumentType = instrumentType {
string.append(" \(instrumentType)")
}
return string
}
func getAdditionalQuantityInfo(additionalInfo: OrderQuantityAdditionalInformation?) -> NSAttributedString {
guard let additionalInfo = additionalInfo else {
return NSAttributedString(string: "")
}
let attributedString: NSAttributedString
switch additionalInfo {
case let .t1(quantity):
attributedString = NSAttributedString(string: L10n.Lists.t1PendingText(quantity), attributes: [.foregroundColor: UIColor.appSupportProcessing])
case .delisted:
attributedString = NSAttributedString(string: L10n.Lists.delistedSuffix, attributes: [.foregroundColor: UIColor.appSupportWarning])
case .suspended:
attributedString = NSAttributedString(string: L10n.Lists.suspendedSuffix, attributes: [.foregroundColor: UIColor.appSupportWarning])
}
return attributedString
}
func updateQuantity(productType: String, quantity: Int) {
let attributedText = NSMutableAttributedString()
let productTypeAttributedText = NSAttributedString(string: "\(productType) ", attributes: [.foregroundColor: UIColor.appText2])
attributedText.append(productTypeAttributedText)
let seperatorAttachment = NSTextAttachment()
seperatorAttachment.image = .appIconSeperator
attributedText.append(NSAttributedString(attachment: seperatorAttachment))
attributedText.append(NSAttributedString(string: " "))
let quantityInDouble = Double(quantity)
let quantityFormattedText = FormattedNumber.decimalNoFractionalWithSign(quantityInDouble).stringValue
let quantityTextColor = quantityInDouble.currencyColor(defaultColor: .appText2)
let quantityAttributedText = L10n.Lists.quantityPrefix(quantityFormattedText).attributedString(textStyle: .meta, originalColor: .appUI5Alt, subStrings: (quantityFormattedText, quantityTextColor))
attributedText.append(quantityAttributedText)
leadingTertiaryLabel.attributedText = attributedText
}
func updateOverall(overall: Double?, percentage: Double? = nil, showFourFractionalsForPrice: Bool) {
let overallText = showFourFractionalsForPrice ? FormattedNumber.currencyWithSignFourFractionals(overall).stringValue : FormattedNumber.currencyWithSign(overall).stringValue
let overallTextColor = overall?.currencyColor(defaultColor: .appText2) ?? .appText2
guard let percentage = percentage else {
trailingPrimaryLabel.textColor = overallTextColor
trailingPrimaryLabel.text = overallText
return
}
let percentChangeText = FormattedNumber.percentWithSign(percentage).stringValue
trailingPrimaryLabel.attributedText = "\(overallText) (\(percentChangeText))".attributedString(textStyle: .meta, originalColor: overallTextColor, subStrings: (overallText, overallTextColor, .bodyBold))
}
func updateAveragePrice(price: Double?, showFourFractionalsForPrice: Bool) {
let avgPriceFormatter: FormattedNumber = showFourFractionalsForPrice ? FormattedNumber.currencyFourFractionals(price) : FormattedNumber.currency(price)
trailingSecondaryLabel.text = L10n.Lists.averageSuffix(avgPriceFormatter.stringValue)
}
func updateTextForLTP(ltp: Double?, ltpPercentage: Double?, showFourFractionalsForPrice: Bool) {
let formattedLTPText = showFourFractionalsForPrice ? FormattedNumber.currencyFourFractionals(ltp).stringValue : FormattedNumber.currency(ltp).stringValue
let formattedLTPChangeText = "(\(FormattedNumber.percentWithSignWithMultiplier(ltpPercentage).stringValue))"
let trailingTertiaryLabelText = L10n.Lists.ltpSuffix("\(formattedLTPText) \(formattedLTPChangeText)")
let formattedLTPChangeTextColor = ltpPercentage?.currencyColor(defaultColor: .appText2) ?? .appText2
trailingTertiaryLabel.attributedText = trailingTertiaryLabelText.attributedString(textStyle: .meta, originalColor: .appUI5Alt, subStrings: (formattedLTPChangeText, formattedLTPChangeTextColor))
}
}
public extension ScripListPHOCell {
public static func getAttributedLTP(ltpValue: String?, ltpColor: UIColor) -> NSAttributedString? {
guard let ltpValue = ltpValue else { return nil }
let attributedText: NSAttributedString = L10n.Lists.ltp(ltpValue).attributedString(textStyle: .meta,
originalColor: .appUI5Alt,
subStrings: (ltpValue, ltpColor, .metaBold))
return attributedText
}
}
// MARK: Substructures
private typealias Substructures = ScripListPHOCell
private extension Substructures {
public struct HoldingDataModel {
let name: String
let amountInvested: Double?
let overall: Double?
let percentageChange: Double?
let quantity: Int
let quantityAdditionalInfo: OrderQuantityAdditionalInformation?
let averagePrice: Double?
let showFourFractionalsForPrice: Bool
let ltp: Double?
let ltpPercentage: Double?
let isActive: Bool
let isIlliquid: Bool
public init(name: String,
amountInvested: Double?,
overall: Double?,
percentageChange: Double?,
quantity: Int,
quantityAdditionalInfo: OrderQuantityAdditionalInformation?,
averagePrice: Double?,
showFourFractionalsForPrice: Bool,
ltp: Double?,
ltpPercentage: Double?,
isActive: Bool,
isIlliquid: Bool) {
self.name = name
self.amountInvested = amountInvested
self.overall = overall
self.percentageChange = percentageChange
self.quantity = quantity
self.quantityAdditionalInfo = quantityAdditionalInfo
self.averagePrice = averagePrice
self.showFourFractionalsForPrice = showFourFractionalsForPrice
self.ltp = ltp
self.ltpPercentage = ltpPercentage
self.isActive = isActive
self.isIlliquid = isIlliquid
}
}
public struct BasketOrderDataModel {
public struct ExpansionDetails {
public enum ButtonsConfig {
case detailsModifyCancel
case detailsRetry
case details
}
let isExpanded: Bool
let buttonsConfig: ButtonsConfig
public init(isExpanded: Bool, buttonsConfig: ButtonsConfig) {
self.isExpanded = isExpanded
self.buttonsConfig = buttonsConfig
}
}
let name: String
let side: OrderSide
let productType: ProductType
let quantity: Int
let orderType: String
let price: Double
let index: String
let segment: String
let isLimitPrice: Bool
let isEditing: Bool
let isExecuted: Bool
let expansionDetails: ExpansionDetails
let orderStatus: OrderStatus
let isScheduled: Bool
let shouldShowFourFractionalsForPrice: Bool
public init(name: String,
side: OrderSide,
productType: ProductType,
quantity: Int,
orderType: String,
price: Double,
index: String,
segment: String,
isLimitPrice: Bool,
isEditing: Bool,
isExecuted: Bool,
expansionDetails: ExpansionDetails,
orderStatus: OrderStatus,
isScheduled: Bool,
shouldShowFourFractionalsForPrice: Bool) {
self.name = name
self.side = side
self.productType = productType
self.quantity = quantity
self.orderType = orderType
self.price = price
self.index = index
self.segment = segment
self.isLimitPrice = isLimitPrice
self.isEditing = isEditing
self.isExecuted = isExecuted
self.expansionDetails = expansionDetails
self.orderStatus = orderStatus
self.isScheduled = isScheduled
self.shouldShowFourFractionalsForPrice = shouldShowFourFractionalsForPrice
}
}
public struct PositionDataModel {
let name: String
let index: String
let date: Date?
let productType: ProductType
let overall: Double?
let lastTradedPrice: Double?
let quantityOfShares: Int
let averagePriceOfShare: Double
let showFourFractionalsForPrice: Bool
let ltpPercentage: Double
let isOpen: Bool
let instrumentType: String?
public init(name: String,
index: String,
date: Date?,
productType: ProductType,
overall: Double?,
lastTradedPrice: Double?,
quantityOfShares: Int,
averagePriceOfShare: Double,
showFourFractionalsForPrice: Bool = false,
ltpPercentage: Double,
isOpen: Bool,
instrumentType: String? = nil) {
self.name = name
self.index = index
self.date = date
self.productType = productType
self.overall = overall
self.lastTradedPrice = lastTradedPrice
self.quantityOfShares = quantityOfShares
self.averagePriceOfShare = averagePriceOfShare
self.showFourFractionalsForPrice = showFourFractionalsForPrice
self.ltpPercentage = ltpPercentage
self.isOpen = isOpen
self.instrumentType = instrumentType
}
}
public enum PHO {
case positions(PositionDataModel)
case holdings(HoldingDataModel)
case orders(OrderCategory)
case basketOrder(BasketOrderDataModel)
public enum OrderCategory {
case regular(order: Order, filling: OrderFilling, trailingImage: UIImage? = nil)
case conditional(
parent: ScripListPHOView.ParentViewInfo,
childLegs: [ScripListPHOView.ConditionalViewInfo],
scripDetails: ScripDetails,
errorMessage: String?
)
}
}
public enum OrderSide {
case buy
case sell
var title: String {
switch self {
case .buy: return L10n.Lists.buy
case .sell: return L10n.Lists.sell
}
}
}
public struct ScripDetails {
let scripName: String
let scripIndex: String
let scripDate: Date?
let scripProductType: ProductType
let scripQuantity: Int
let scripSegment: String?
let instrumentType: String?
public init(scripName: String,
scripIndex: String,
scripDate: Date?,
scripProductType: ProductType,
scripQuantity: Int,
scripSegment: String?,
instrumentType: String?) {
self.scripName = scripName
self.scripIndex = scripIndex
self.scripDate = scripDate
self.scripProductType = scripProductType
self.scripQuantity = scripQuantity
self.scripSegment = scripSegment
self.instrumentType = instrumentType
}
}
public struct Order {
let name: String
let index: String
let date: Date?
let productType: ProductType
let status: OrderStatus
let side: OrderSide
let quantityFilled: Int
let quantityOrdered: Int
let isScheduled: Bool
let isCurrency: Bool
let price: OrderPrice
let instrumentType: String?
let isOpen: Bool
let message: String
let shouldShowWarningIcon: Bool
public init(name: String,
index: String,
date: Date?,
productType: ProductType,
status: OrderStatus,
side: OrderSide,
quantityFilled: Int,
quantityOrdered: Int,
isScheduled: Bool,
isCurrency: Bool,
price: OrderPrice,
instrumentType: String?,
isOpen: Bool,
message: String,
shouldShowWarningIcon: Bool) {
self.name = name
self.index = index
self.date = date
self.productType = productType
self.status = status
self.side = side
self.quantityFilled = quantityFilled
self.quantityOrdered = quantityOrdered
self.isScheduled = isScheduled
self.isCurrency = isCurrency
self.price = price
self.instrumentType = instrumentType
self.isOpen = isOpen
self.message = message
self.shouldShowWarningIcon = shouldShowWarningIcon
}
}
public struct OrderPrice {
let stopLoss: Double?
let squareOff: Double?
public init(stopLoss: Double?,
squareOff: Double?) {
self.stopLoss = stopLoss
self.squareOff = squareOff
}
}
public struct OrderLeg {
public let title: String
public let subtitle: String
public let status: OrderStatus
public let completionHandler: (() -> Void)?
public init(title: String,
subtitle: String,
status: OrderStatus,
completionHandler: (() -> Void)?) {
self.title = title
self.status = status
self.completionHandler = completionHandler
self.subtitle = subtitle
}
}
public enum OrderPriceBand {
case above
case below
var title: String {
switch self {
case .above: return L10n.Other.above
case .below: return L10n.Other.below
}
}
}
public enum OrderFilling {
case limit(Double)
case market
case smartOrder
}
public enum OrderQuantityAdditionalInformation {
case suspended
case delisted
case t1(Int)
}
public enum ProductType: Hashable {
case intraday
case delivery
case mtf
case mtfs
case co
case oco
case strategy
var title: String {
switch self {
case .intraday: return L10n.Lists.intraday
case .delivery: return L10n.Lists.delivery
case .mtf: return L10n.Lists.mtf
case .mtfs: return L10n.Lists.mtfs
case .co: return L10n.Lists.co
case .oco: return L10n.Lists.oco
case .strategy: return L10n.Lists.oco
}
}
}
public enum BasketOrderStatus {
case completed
case error
case pending
case inQueue
case cancelled
}
}
// MARK: Helper
private typealias Helper = ScripListPHOCell
private extension Helper {
func makeCustomButtonView(image: UIImage, text: String, shouldAddVerticalSeparator: Bool = true) -> UIView {
let label = Label(.metaBold)
label.textAlignment = .center
let attachment = NSTextAttachment()
attachment.bounds = CGRect(x: .zero,
y: -.quarterOfDefaultMargin,
width: .defaultMargin,
height: .defaultMargin)
attachment.image = image
let space = NSTextAttachment()
space.bounds.size.width = .halfOfDefaultMargin
let attributedText = NSMutableAttributedString()
attributedText.append(NSAttributedString(attachment: attachment))
attributedText.append(NSAttributedString(attachment: space))
attributedText.append(NSAttributedString(string: text))
label.attributedText = attributedText
let contentStackView = UIStackView()
contentStackView.spacing = .halfOfDefaultMargin
contentStackView.addArrangedSubview(label)
let mainStackView = UIStackView()
mainStackView.isLayoutMarginsRelativeArrangement = true
mainStackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: .halfOfDefaultMargin,
leading: .zero,
bottom: .halfOfDefaultMargin,
trailing: .zero)
if shouldAddVerticalSeparator {
let verticalDivider = UIView().withBackgroundColor(.appUi3)
verticalDivider.widthAnchor.constraint(equalToConstant: 1).isActive = true
mainStackView.addArrangedSubview(verticalDivider)
}
mainStackView.addArrangedSubview(contentStackView)
mainStackView.isHidden = true
return mainStackView
}
}
// MARK: ActionsHelper
private typealias ActionsHelper = ScripListPHOCell
private extension ActionsHelper {
@objc func didTapDetailsBottomView(_ gestureRecognizer: UITapGestureRecognizer) {
delegate?.didTapDetailsBottomView(at: tag)
}
@objc func didTapCancelBottomView(_ gestureRecognizer: UITapGestureRecognizer) {
delegate?.didTapCancelBottomView(at: tag)
}
@objc func didTapModifyBottomView(_ gestureRecognizer: UITapGestureRecognizer) {
delegate?.didTapModifyBottomView(at: tag)
}
@objc func didTapRetryBottomView(_ gestureRecognizer: UITapGestureRecognizer) {
delegate?.didTapRetryBottomView(at: tag)
}
}
// MARK: Constants
private typealias Constant = ScripListPHOCell
private extension Constant {
enum Constants {
static let conditionalSeparatorHeight: CGFloat = 4
static let defaultSeparatorHeight: CGFloat = 1
static let mainHorizontalStackViewDefaultMargins = NSDirectionalEdgeInsets(top: .threeQuartersOfDefaultMargin,
leading: .defaultMargin,
bottom: .threeQuartersOfDefaultMargin,
trailing: .defaultMargin)
}
}