session-ios/Session/Sheets & Modals/ConfirmationModal.swift

234 lines
8.0 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SessionUIKit
public class ConfirmationModal: Modal {
public struct Info: Equatable, Hashable {
enum State {
case whenEnabled
case whenDisabled
case always
func shouldShow(for value: Bool) -> Bool {
switch self {
case .whenEnabled: return (value == true)
case .whenDisabled: return (value == false)
case .always: return true
}
}
}
let title: String
let explanation: String?
let attributedExplanation: NSAttributedString?
let stateToShow: State
let confirmTitle: String?
let confirmStyle: ThemeValue
let cancelTitle: String
let cancelStyle: ThemeValue
let dismissOnConfirm: Bool
let onConfirm: ((UIViewController) -> ())?
let afterClosed: (() -> ())?
// MARK: - Initialization
init(
title: String,
explanation: String? = nil,
attributedExplanation: NSAttributedString? = nil,
stateToShow: State = .always,
confirmTitle: String? = nil,
confirmStyle: ThemeValue = .alert_text,
cancelTitle: String = "TXT_CANCEL_TITLE".localized(),
cancelStyle: ThemeValue = .danger,
dismissOnConfirm: Bool = true,
onConfirm: ((UIViewController) -> ())? = nil,
afterClosed: (() -> ())? = nil
) {
self.title = title
self.explanation = explanation
self.attributedExplanation = attributedExplanation
self.stateToShow = stateToShow
self.confirmTitle = confirmTitle
self.confirmStyle = confirmStyle
self.cancelTitle = cancelTitle
self.cancelStyle = cancelStyle
self.dismissOnConfirm = dismissOnConfirm
self.onConfirm = onConfirm
self.afterClosed = afterClosed
}
// MARK: - Mutation
public func with(
onConfirm: ((UIViewController) -> ())? = nil,
afterClosed: (() -> ())? = nil
) -> Info {
return Info(
title: self.title,
explanation: self.explanation,
stateToShow: self.stateToShow,
confirmTitle: self.confirmTitle,
confirmStyle: self.confirmStyle,
cancelTitle: self.cancelTitle,
cancelStyle: self.cancelStyle,
dismissOnConfirm: self.dismissOnConfirm,
onConfirm: (onConfirm ?? self.onConfirm),
afterClosed: (afterClosed ?? self.afterClosed)
)
}
// MARK: - Confirmance
public static func == (lhs: ConfirmationModal.Info, rhs: ConfirmationModal.Info) -> Bool {
return (
lhs.title == rhs.title &&
lhs.explanation == rhs.explanation &&
lhs.attributedExplanation == rhs.attributedExplanation &&
lhs.stateToShow == rhs.stateToShow &&
lhs.confirmTitle == rhs.confirmTitle &&
lhs.confirmStyle == rhs.confirmStyle &&
lhs.cancelTitle == rhs.cancelTitle &&
lhs.cancelStyle == rhs.cancelStyle &&
lhs.dismissOnConfirm == rhs.dismissOnConfirm
)
}
public func hash(into hasher: inout Hasher) {
title.hash(into: &hasher)
explanation.hash(into: &hasher)
attributedExplanation.hash(into: &hasher)
stateToShow.hash(into: &hasher)
confirmTitle.hash(into: &hasher)
confirmStyle.hash(into: &hasher)
cancelTitle.hash(into: &hasher)
cancelStyle.hash(into: &hasher)
dismissOnConfirm.hash(into: &hasher)
}
}
private let internalOnConfirm: (UIViewController) -> ()
// MARK: - Components
private lazy var titleLabel: UILabel = {
let result: UILabel = UILabel()
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.themeTextColor = .alert_text
result.textAlignment = .center
result.lineBreakMode = .byWordWrapping
result.numberOfLines = 0
return result
}()
private lazy var explanationLabel: UILabel = {
let result: UILabel = UILabel()
result.font = .systemFont(ofSize: Values.smallFontSize)
result.themeTextColor = .alert_text
result.textAlignment = .center
result.lineBreakMode = .byWordWrapping
result.numberOfLines = 0
return result
}()
private lazy var confirmButton: UIButton = {
let result: UIButton = Modal.createButton(
title: "",
titleColor: .danger
)
result.addTarget(self, action: #selector(confirmationPressed), for: .touchUpInside)
return result
}()
private lazy var buttonStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ confirmButton, cancelButton ])
result.axis = .horizontal
result.distribution = .fillEqually
return result
}()
private lazy var contentStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel ])
result.axis = .vertical
result.spacing = Values.smallSpacing
result.isLayoutMarginsRelativeArrangement = true
result.layoutMargins = UIEdgeInsets(
top: Values.largeSpacing,
leading: Values.largeSpacing,
bottom: Values.verySmallSpacing,
trailing: Values.largeSpacing
)
return result
}()
private lazy var mainStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ contentStackView, buttonStackView ])
result.axis = .vertical
result.spacing = Values.largeSpacing - Values.smallFontSize / 2
return result
}()
// MARK: - Lifecycle
init(info: Info) {
self.internalOnConfirm = { viewController in
if info.dismissOnConfirm {
viewController.dismiss(animated: true)
}
info.onConfirm?(viewController)
}
super.init(afterClosed: info.afterClosed)
self.modalPresentationStyle = .overFullScreen
self.modalTransitionStyle = .crossDissolve
// Set the content based on the provided info
titleLabel.text = info.title
// Note: We should only set the appropriate explanation/attributedExplanation value (as
// setting both when one is null can result in the other being removed)
if let explanation: String = info.explanation {
explanationLabel.text = explanation
}
if let attributedExplanation: NSAttributedString = info.attributedExplanation {
explanationLabel.attributedText = attributedExplanation
}
explanationLabel.isHidden = (
info.explanation == nil &&
info.attributedExplanation == nil
)
confirmButton.setTitle(info.confirmTitle, for: .normal)
confirmButton.setThemeTitleColor(info.confirmStyle, for: .normal)
confirmButton.isHidden = (info.confirmTitle == nil)
cancelButton.setTitle(info.cancelTitle, for: .normal)
cancelButton.setThemeTitleColor(info.cancelStyle, for: .normal)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func populateContentView() {
contentView.addSubview(mainStackView)
mainStackView.pin(to: contentView)
}
// MARK: - Interaction
@objc private func confirmationPressed() {
internalOnConfirm(self)
}
}