vds_ios/VDS/Components/Notification/Notification.swift
2023-03-30 19:53:25 +05:30

295 lines
9.6 KiB
Swift

//
// Notification.swift
// VDS
//
// Created by Nadigadda, Sumanth on 14/03/23.
//
import Foundation
import UIKit
import VDSColorTokens
@objc(VDSNotification)
/// A VDS Component that will render a view with information
public class Notification: View {
//--------------------------------------------------
// MARK: - Enums
//--------------------------------------------------
public enum NotificationStyle: String, CaseIterable {
case info, success, warning, error
func styleIconName() -> Icon.Name {
switch self {
case .info:
return .infoBold
case .success:
return .checkmarkAltBold
case .warning:
return .warningBold
case .error:
return .errorBold
}
}
}
public enum ButtonsPosition: String, CaseIterable {
case bottom, right
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private var mainStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.alignment = .top
$0.axis = .horizontal
$0.spacing = VDSLayout.Spacing.space2X.value
}
private var labelsView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.alignment = .top
$0.axis = .vertical
}
private var labelButtonView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.alignment = .top
$0.distribution = .fillEqually
$0.axis = .vertical
$0.spacing = VDSLayout.Spacing.space2X.value
}
private var edgeSpacing: CGFloat {
return UIDevice.isIPad ? VDSLayout.Spacing.space5X.value : VDSLayout.Spacing.space4X.value
}
private var minViewHeight: CGFloat {
return UIDevice.isIPad ? VDSLayout.Spacing.space16X.value : VDSLayout.Spacing.space12X.value
}
private var minContentHeight: CGFloat {
return UIDevice.isIPad ? VDSLayout.Spacing.space5X.value : VDSLayout.Spacing.space4X.value
}
//--------------------------------------------------
// MARK: - View Properties
//--------------------------------------------------
open var typeIcon = Icon().with {
$0.name = .infoBold
}
open var closeButton = Icon().with {
$0.name = .close
}
open var titleLabel = Label().with {
$0.textStyle = .boldBodyLarge
}
open var subTitleLabel = Label().with {
$0.textStyle = .bodyLarge
}
open var buttonsView = ButtonGroup().with {
$0.buttonPosition = .left
}
//Text
open var titleText: String? { didSet{didChange()}}
open var subTitleText: String? { didSet{didChange()}}
//Buttons
open var primaryButtonModel: ButtonModel? { didSet{didChange()}}
open var primaryButton = Button().with {
$0.size = .small
$0.use = .secondary
}
open var secondaryButtonModel: ButtonModel? { didSet{didChange()}}
open var secondaryButton = Button().with {
$0.size = .small
$0.use = .secondary
}
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open var type: NotificationStyle = .info { didSet{didChange()}}
var _buttonPosition: ButtonsPosition = .bottom
open var buttonPosition: ButtonsPosition {
set {
if !UIDevice.isIPad, newValue == .right { return }
_buttonPosition = newValue
buttonsView.buttonPosition = _buttonPosition == .right ? .center : .left
didChange()
}
get { _buttonPosition }
}
//--------------------------------------------------
// MARK: - Configuration
//--------------------------------------------------
private var backgroundColorConfiguration: AnyColorable = {
let config = KeyedColorConfiguration<Notification, NotificationStyle>(keyPath: \.type)
config.setSurfaceColors(VDSColor.feedbackInformationBackgroundOnlight, VDSColor.feedbackInformationBackgroundOndark, forKey: .info)
config.setSurfaceColors(VDSColor.feedbackWarningBackgroundOnlight, VDSColor.feedbackWarningBackgroundOndark, forKey: .warning)
config.setSurfaceColors(VDSColor.feedbackSuccessBackgroundOnlight, VDSColor.feedbackSuccessBackgroundOndark, forKey: .success)
config.setSurfaceColors(VDSColor.feedbackErrorBackgroundOnlight, VDSColor.feedbackErrorBackgroundOndark, forKey: .error)
return config.eraseToAnyColorable()
}()
private var textColorConfig = ViewColorConfiguration().with {
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false)
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: true)
}
//--------------------------------------------------
// 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: - Lifecycle
//--------------------------------------------------
open override func setup() {
super.setup()
addSubview(mainStackView)
mainStackView.pinToSuperView(.init(top: edgeSpacing, left: edgeSpacing, bottom: edgeSpacing, right: edgeSpacing))
NSLayoutConstraint.activate([
heightAnchor.constraint(greaterThanOrEqualToConstant: minViewHeight),
mainStackView.heightAnchor.constraint(greaterThanOrEqualToConstant: minContentHeight)
])
mainStackView.addArrangedSubview(typeIcon)
mainStackView.addArrangedSubview(labelButtonView)
mainStackView.addArrangedSubview(closeButton)
labelButtonView.addArrangedSubview(labelsView)
closeButton.publisher(for: UITapGestureRecognizer()).sink { [weak self] _ in
self?.didClickOnCloseButton()
}.store(in: &subscribers)
//labels
titleLabel.textColorConfiguration = textColorConfig.eraseToAnyColorable()
subTitleLabel.textColorConfiguration = textColorConfig.eraseToAnyColorable()
}
open override func reset() {
super.reset()
titleLabel.reset()
subTitleLabel.reset()
buttonsView.reset()
primaryButton.reset()
secondaryButton.reset()
type = .info
typeIcon.name = .infoBold
closeButton.name = .close
buttonPosition = .bottom
}
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView() {
backgroundColor = backgroundColorConfiguration.getColor(self)
updateIcons()
updateLabels()
updateButtons()
}
private func updateIcons() {
let iconColor = surface == .dark ? Icon.Color.white : Icon.Color.black
typeIcon.name = type.styleIconName()
typeIcon.color = iconColor
closeButton.color = iconColor
}
private func updateLabels() {
titleLabel.surface = surface
subTitleLabel.surface = surface
if let titleText {
titleLabel.text = titleText
labelsView.addArrangedSubview(titleLabel)
} else {
titleLabel.removeFromSuperview()
}
if let subTitleText {
subTitleLabel.text = subTitleText
labelsView.addArrangedSubview(subTitleLabel)
} else {
subTitleLabel.removeFromSuperview()
}
}
private func updateButtons() {
var buttons: [Button] = []
if let primaryButtonModel {
primaryButton.text = primaryButtonModel.text
primaryButton.surface = surface
primaryButton.onClickSubscriber = primaryButton
.publisher(for: .touchUpInside)
.sink(receiveValue: { button in
primaryButtonModel.onClick(button)
})
buttons.append(primaryButton)
}
if let secondaryButtonModel {
secondaryButton.text = secondaryButtonModel.text
secondaryButton.surface = surface
secondaryButton.onClickSubscriber = secondaryButton
.publisher(for: .touchUpInside)
.sink(receiveValue: { button in
secondaryButtonModel.onClick(button)
})
buttons.append(secondaryButton)
}
if buttons.isEmpty {
labelsView.setCustomSpacing(0, after: subTitleLabel)
buttonsView.removeFromSuperview()
} else {
labelsView.setCustomSpacing(VDSLayout.Spacing.space3X.value, after: subTitleLabel)
buttonsView.buttons = buttons
labelButtonView.axis = buttonPosition == .bottom ? .vertical : .horizontal
labelButtonView.addArrangedSubview(buttonsView)
buttonsView
.pinLeading()
.pinTrailing()
}
}
func didClickOnCloseButton() {
print("Notification close button clicked!!!")
}
}