Files
suixinkan_uikit/suixinkan/UI/ScenicApplication/Views/ScenicApplicationViews.swift

685 lines
21 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// ScenicApplicationViews.swift
// suixinkan
//
import Kingfisher
import SnapKit
import UIKit
/// Android `WarmReminderCard`
final class ScenicApplicationWarmReminderView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = AppColor.primaryBanner
layer.cornerRadius = 8
let titleLabel = UILabel()
titleLabel.text = "温馨提示"
titleLabel.font = .systemFont(ofSize: 16, weight: .bold)
titleLabel.textColor = .white
let bodyLabel = UILabel()
bodyLabel.text = "如果你想入驻更多的景区,平台可以帮你跟景区官方谈合作,如果已经有的景区,只要你符合入驻的条件,即可入驻。"
bodyLabel.font = .systemFont(ofSize: 14)
bodyLabel.textColor = .white
bodyLabel.numberOfLines = 0
let stack = UIStackView(arrangedSubviews: [titleLabel, bodyLabel])
stack.axis = .vertical
stack.spacing = 8
addSubview(stack)
stack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
/// Android `ScenicApplicationAuditStatusCard`
final class ScenicApplicationAuditStatusView: UIView {
func apply(pending: ScenicApplicationPendingResponse) {
subviews.forEach { $0.removeFromSuperview() }
let statusLabel: String
let textColor: UIColor
let background: UIColor
switch pending.status {
case 1:
statusLabel = "待审核"
textColor = AppColor.warning
background = AppColor.warningBackground
case 2:
statusLabel = "通过"
textColor = AppColor.info
background = AppColor.primaryLight
case 3, 9:
statusLabel = pending.status == 3 ? "拒绝" : "取消"
textColor = AppColor.danger
background = AppColor.dangerBackground
default:
statusLabel = "--"
textColor = AppColor.warning
background = AppColor.warningBackground
}
backgroundColor = background
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 8
stack.addArrangedSubview(makeRow(label: "审核状态", value: statusLabel, color: textColor))
if pending.status == 2 {
stack.addArrangedSubview(makeRow(label: "审核人", value: pending.auditedBy ?? "--", color: textColor))
stack.addArrangedSubview(makeRow(label: "审核时间", value: pending.auditedAt ?? "--", color: textColor))
}
let note = pending.rejectReason ?? pending.auditNote ?? "--"
stack.addArrangedSubview(makeRow(label: "备注", value: note.isEmpty ? "--" : note, color: textColor))
addSubview(stack)
stack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20))
}
}
private func makeRow(label: String, value: String, color: UIColor) -> UIView {
let row = UIStackView()
row.axis = .horizontal
row.distribution = .equalSpacing
let left = UILabel()
left.text = label
left.font = .systemFont(ofSize: 14)
left.textColor = color
let right = UILabel()
right.text = value
right.font = .systemFont(ofSize: 14)
right.textColor = color
right.textAlignment = .right
right.numberOfLines = 0
row.addArrangedSubview(left)
row.addArrangedSubview(right)
return row
}
}
/// +
final class ScenicApplicationFormFieldView: UIView {
private let titleLabel = UILabel()
private let contentContainer = UIView()
init(title: String) {
super.init(frame: .zero)
titleLabel.text = title
titleLabel.font = .systemFont(ofSize: 16, weight: .medium)
titleLabel.textColor = AppColor.textPrimary
let stack = UIStackView(arrangedSubviews: [titleLabel, contentContainer])
stack.axis = .vertical
stack.spacing = 8
addSubview(stack)
stack.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setContent(_ view: UIView) {
contentContainer.subviews.forEach { $0.removeFromSuperview() }
contentContainer.addSubview(view)
view.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
func updateTitle(_ title: String) {
titleLabel.text = title
}
}
/// /
final class ScenicApplicationLocationSelector: UIControl {
private let valueLabel = UILabel()
private let chevron = UIImageView(image: UIImage(systemName: "chevron.down"))
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
layer.cornerRadius = 8
layer.borderWidth = 1
layer.borderColor = AppColor.border.cgColor
valueLabel.font = .systemFont(ofSize: 14)
valueLabel.textColor = AppColor.textTertiary
valueLabel.text = "请选择"
chevron.tintColor = AppColor.textTertiary
chevron.contentMode = .scaleAspectFit
addSubview(valueLabel)
addSubview(chevron)
valueLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(12)
make.centerY.equalToSuperview()
make.trailing.lessThanOrEqualTo(chevron.snp.leading).offset(-8)
}
chevron.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-12)
make.centerY.equalToSuperview()
make.size.equalTo(16)
}
snp.makeConstraints { make in
make.height.equalTo(44)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setValue(_ value: String?, placeholder: String) {
if let value, !value.isEmpty {
valueLabel.text = value
valueLabel.textColor = AppColor.textPrimary
} else {
valueLabel.text = placeholder
valueLabel.textColor = AppColor.textTertiary
}
}
override var isEnabled: Bool {
didSet {
alpha = isEnabled ? 1 : 0.5
}
}
}
///
final class ScenicApplicationCooperationOptionView: UIControl {
private let titleLabel = UILabel()
override var isSelected: Bool {
didSet { applyStyle() }
}
init(title: String) {
super.init(frame: .zero)
titleLabel.text = title
titleLabel.font = .systemFont(ofSize: 14)
titleLabel.numberOfLines = 0
layer.cornerRadius = 8
layer.borderWidth = 2
addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
}
applyStyle()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func applyStyle() {
if isSelected {
backgroundColor = AppColor.primaryBanner
titleLabel.textColor = .white
layer.borderColor = UIColor.clear.cgColor
} else {
backgroundColor = .white
titleLabel.textColor = AppColor.textSecondary
layer.borderColor = AppColor.inputBackground.cgColor
}
}
}
///
final class ScenicApplicationImageUploadView: UIView {
var onAdd: (() -> Void)?
var onDelete: ((Int) -> Void)?
var onRetry: ((Int) -> Void)?
private let scrollView = UIScrollView()
private let stackView = UIStackView()
override init(frame: CGRect) {
super.init(frame: frame)
stackView.axis = .horizontal
stackView.spacing = 8
stackView.alignment = .center
scrollView.showsHorizontalScrollIndicator = false
scrollView.addSubview(stackView)
addSubview(scrollView)
scrollView.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.height.equalTo(80)
}
stackView.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.height.equalToSuperview()
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(images: [ScenicApplicationImageItem], isReadOnly: Bool) {
stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
for (index, item) in images.enumerated() {
let cell = ScenicApplicationImageCell()
cell.apply(item: item, showDelete: !isReadOnly)
cell.onDelete = { [weak self] in self?.onDelete?(index) }
cell.onRetry = { [weak self] in self?.onRetry?(index) }
stackView.addArrangedSubview(cell)
cell.snp.makeConstraints { make in
make.size.equalTo(80)
}
}
if images.count < 20, !isReadOnly {
let addButton = ScenicApplicationAddImageButton()
addButton.addTarget(self, action: #selector(addTapped), for: .touchUpInside)
stackView.addArrangedSubview(addButton)
addButton.snp.makeConstraints { make in
make.size.equalTo(80)
}
}
}
@objc private func addTapped() {
onAdd?()
}
}
private final class ScenicApplicationAddImageButton: UIControl {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = AppColor.inputBackground
layer.cornerRadius = 8
let icon = UIImageView(image: UIImage(systemName: "plus"))
icon.tintColor = UIColor(hex: 0xB6BECA)
let label = UILabel()
label.text = "点击上传"
label.font = .systemFont(ofSize: 12)
label.textColor = UIColor(hex: 0xB6BECA)
let stack = UIStackView(arrangedSubviews: [icon, label])
stack.axis = .vertical
stack.spacing = 4
stack.alignment = .center
stack.isUserInteractionEnabled = false
addSubview(stack)
stack.snp.makeConstraints { make in
make.center.equalToSuperview()
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private final class ScenicApplicationImageCell: UIView {
var onDelete: (() -> Void)?
var onRetry: (() -> Void)?
private let imageView = UIImageView()
private let progressView = UIProgressView(progressViewStyle: .default)
private let deleteButton = UIButton(type: .system)
private let retryButton = UIButton(type: .system)
private let errorLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = 8
clipsToBounds = true
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
deleteButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal)
deleteButton.tintColor = .white
deleteButton.addTarget(self, action: #selector(deleteTapped), for: .touchUpInside)
retryButton.setTitle("重试", for: .normal)
retryButton.titleLabel?.font = .systemFont(ofSize: 12)
retryButton.setTitleColor(.white, for: .normal)
retryButton.backgroundColor = UIColor.black.withAlphaComponent(0.5)
retryButton.layer.cornerRadius = 4
retryButton.addTarget(self, action: #selector(retryTapped), for: .touchUpInside)
retryButton.isHidden = true
errorLabel.font = .systemFont(ofSize: 10)
errorLabel.textColor = .white
errorLabel.textAlignment = .center
errorLabel.backgroundColor = UIColor.black.withAlphaComponent(0.5)
errorLabel.isHidden = true
addSubview(imageView)
addSubview(progressView)
addSubview(errorLabel)
addSubview(retryButton)
addSubview(deleteButton)
imageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
progressView.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview().inset(4)
}
deleteButton.snp.makeConstraints { make in
make.top.trailing.equalToSuperview().inset(2)
make.size.equalTo(22)
}
retryButton.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(44)
make.height.equalTo(24)
}
errorLabel.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
make.height.equalTo(18)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(item: ScenicApplicationImageItem, showDelete: Bool) {
deleteButton.isHidden = !showDelete
progressView.isHidden = !item.isUploading
progressView.progress = Float(item.uploadProgress) / 100
if let data = item.localData, let image = UIImage(data: data) {
imageView.image = image
} else if !item.remoteURL.isEmpty, let url = URL(string: item.remoteURL) {
imageView.kf.setImage(with: url)
} else {
imageView.image = nil
}
if let error = item.errorMessage, !error.isEmpty {
errorLabel.isHidden = false
errorLabel.text = "失败"
retryButton.isHidden = false
} else {
errorLabel.isHidden = true
retryButton.isHidden = true
}
}
@objc private func deleteTapped() {
onDelete?()
}
@objc private func retryTapped() {
onRetry?()
}
}
/// Android `AgreementCheckbox`
final class ScenicApplicationAgreementView: UIView {
var onToggle: (() -> Void)?
private let checkbox = UIButton(type: .custom)
private var isChecked = false
override init(frame: CGRect) {
super.init(frame: frame)
checkbox.setImage(UIImage(systemName: "square"), for: .normal)
checkbox.setImage(UIImage(systemName: "checkmark.square.fill"), for: .selected)
checkbox.tintColor = AppColor.primaryBanner
checkbox.addTarget(self, action: #selector(toggleTapped), for: .touchUpInside)
let prefix = UILabel()
prefix.text = "已阅读并同意"
prefix.font = .systemFont(ofSize: 14)
prefix.textColor = AppColor.textSecondary
let userNotice = UILabel()
userNotice.text = "《用户须知》"
userNotice.font = .systemFont(ofSize: 14)
userNotice.textColor = AppColor.primaryBanner
let middle = UILabel()
middle.text = ""
middle.font = .systemFont(ofSize: 14)
middle.textColor = AppColor.textSecondary
let privacy = UILabel()
privacy.text = "《隐私政策》"
privacy.font = .systemFont(ofSize: 14)
privacy.textColor = AppColor.primaryBanner
let textStack = UIStackView(arrangedSubviews: [prefix, userNotice, middle, privacy])
textStack.axis = .horizontal
textStack.spacing = 2
textStack.alignment = .center
let row = UIStackView(arrangedSubviews: [checkbox, textStack])
row.axis = .horizontal
row.spacing = 2
row.alignment = .center
let tap = UITapGestureRecognizer(target: self, action: #selector(toggleTapped))
addGestureRecognizer(tap)
addSubview(row)
row.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
checkbox.snp.makeConstraints { make in
make.size.equalTo(24)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setChecked(_ checked: Bool) {
isChecked = checked
checkbox.isSelected = checked
}
@objc private func toggleTapped() {
onToggle?()
}
}
///
final class ScenicApplicationSubmitButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setTitle("确认提交", for: .normal)
setTitleColor(.white, for: .normal)
titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
backgroundColor = AppColor.primaryBanner
layer.cornerRadius = 8
snp.makeConstraints { make in
make.height.equalTo(46)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(canSubmit: Bool, isSubmitting: Bool, isReadOnly: Bool) {
let enabled = !isReadOnly && canSubmit && !isSubmitting
isEnabled = enabled
alpha = enabled ? 1 : 1
backgroundColor = enabled ? AppColor.primaryBanner : UIColor(hex: 0xE0E0E0)
setTitle(isSubmitting ? "提交中..." : "确认提交", for: .normal)
}
}
///
final class ScenicApplicationUploadProgressView: UIView {
private let titleLabel = UILabel()
private let progressView = UIProgressView(progressViewStyle: .default)
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.black.withAlphaComponent(0.35)
let card = UIView()
card.backgroundColor = .white
card.layer.cornerRadius = 12
titleLabel.font = .systemFont(ofSize: 16, weight: .medium)
titleLabel.textColor = AppColor.textPrimary
titleLabel.textAlignment = .center
progressView.progressTintColor = AppColor.primaryBanner
let stack = UIStackView(arrangedSubviews: [titleLabel, progressView])
stack.axis = .vertical
stack.spacing = 16
card.addSubview(stack)
stack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(20)
}
addSubview(card)
card.snp.makeConstraints { make in
make.center.equalToSuperview()
make.leading.trailing.equalToSuperview().inset(40)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(state: ScenicApplicationUploadDialogState?) {
isHidden = state == nil
guard let state else { return }
titleLabel.text = state.title
progressView.progress = Float(state.progress) / 100
}
}
/// placeholder
final class ScenicApplicationPlaceholderTextView: UITextView, UITextViewDelegate {
private let placeholderLabel = UILabel()
var placeholder: String {
get { placeholderLabel.text ?? "" }
set {
placeholderLabel.text = newValue
refreshPlaceholderVisibility()
}
}
var onTextChange: ((String) -> Void)?
override var text: String! {
didSet { refreshPlaceholderVisibility() }
}
override var delegate: UITextViewDelegate? {
get { super.delegate }
set {
// placeholder delegate onTextChange
}
}
init(placeholder: String) {
super.init(frame: .zero, textContainer: nil)
font = .systemFont(ofSize: 14)
textColor = AppColor.textPrimary
backgroundColor = .white
layer.cornerRadius = 8
layer.borderWidth = 1
layer.borderColor = AppColor.border.cgColor
textContainerInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8)
super.delegate = self
placeholderLabel.text = placeholder
placeholderLabel.font = font
placeholderLabel.textColor = AppColor.textTertiary
placeholderLabel.numberOfLines = 0
addSubview(placeholderLabel)
placeholderLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(10)
make.leading.equalToSuperview().offset(12)
make.trailing.equalToSuperview().offset(-12)
}
snp.makeConstraints { make in
make.height.equalTo(100)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func textViewDidChange(_ textView: UITextView) {
refreshPlaceholderVisibility()
onTextChange?(textView.text ?? "")
}
private func refreshPlaceholderVisibility() {
placeholderLabel.isHidden = !(text ?? "").isEmpty
}
}
///
enum ScenicApplicationFieldStyle {
static func makeTextField(placeholder: String) -> UITextField {
let field = UITextField()
field.placeholder = placeholder
field.font = .systemFont(ofSize: 14)
field.borderStyle = .none
field.backgroundColor = .white
field.layer.cornerRadius = 8
field.layer.borderWidth = 1
field.layer.borderColor = AppColor.border.cgColor
field.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: 1))
field.leftViewMode = .always
field.snp.makeConstraints { make in
make.height.equalTo(44)
}
return field
}
}