Files
suixinkan_uikit/suixinkan/UI/Home/Views/ScenicSelectionViews.swift
汉秋 efb3925257 完善首页权限申请流程与队列播报体验。
对齐 Android 无权限强制弹窗、角色下拉与申请页交互,补充 UIButton configuration 适配,并增强景区排队 TTS 与相关单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 13:46:45 +08:00

309 lines
11 KiB
Swift
Raw 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.

//
// ScenicSelectionViews.swift
// suixinkan
//
import Kingfisher
import SnapKit
import UIKit
///
final class ScenicPermissionBannerView: UIView {
var onApplyNow: (() -> Void)?
private let titleLabel = UILabel()
private let applyButton = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = AppColor.warningBackground
titleLabel.text = "希望开通更多景区权限"
titleLabel.font = .systemFont(ofSize: 14, weight: .medium)
titleLabel.textColor = AppColor.warning
titleLabel.numberOfLines = 2
applyButton.setTitle("立即申请 ", for: .normal)
applyButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
applyButton.setTitleColor(AppColor.warning, for: .normal)
applyButton.addTarget(self, action: #selector(applyTapped), for: .touchUpInside)
addSubview(titleLabel)
addSubview(applyButton)
titleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.centerY.equalToSuperview()
make.trailing.lessThanOrEqualTo(applyButton.snp.leading).offset(-8)
}
applyButton.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-16)
make.centerY.equalToSuperview()
}
snp.makeConstraints { make in
make.height.equalTo(44)
}
setContentHuggingPriority(.required, for: .vertical)
setContentCompressionResistancePriority(.required, for: .vertical)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func applyTapped() {
onApplyNow?()
}
}
///
final class ScenicSelectionSearchBar: UIView, UITextFieldDelegate {
var onSearchTextChange: ((String) -> Void)?
let textField = UITextField()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor(hex: 0xF2F4F8)
layer.cornerRadius = 12
clipsToBounds = true
let icon = UIImageView(image: UIImage(systemName: "magnifyingglass"))
icon.tintColor = UIColor(hex: 0xB3B8C2)
icon.contentMode = .scaleAspectFit
textField.placeholder = "搜索景区"
textField.font = .systemFont(ofSize: 14)
textField.textColor = AppColor.textPrimary
textField.returnKeyType = .search
textField.delegate = self
textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)
addSubview(icon)
addSubview(textField)
icon.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(12)
make.centerY.equalToSuperview()
make.size.equalTo(18)
}
textField.snp.makeConstraints { make in
make.leading.equalTo(icon.snp.trailing).offset(8)
make.trailing.equalToSuperview().offset(-12)
make.top.bottom.equalToSuperview().inset(10)
}
snp.makeConstraints { make in
make.height.equalTo(44)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func textChanged() {
onSearchTextChange?(textField.text ?? "")
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
///
final class ScenicCurrentLocationBar: UIView {
var onRelocate: (() -> Void)?
private let iconView = UIImageView(image: UIImage(systemName: "location.fill"))
private let locationLabel = UILabel()
private let relocateButton = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
iconView.tintColor = AppColor.primary
iconView.contentMode = .scaleAspectFit
locationLabel.font = .systemFont(ofSize: 14)
locationLabel.textColor = AppColor.textPrimary
locationLabel.numberOfLines = 2
relocateButton.setTitle("重新定位", for: .normal)
relocateButton.titleLabel?.font = .systemFont(ofSize: 12)
relocateButton.setTitleColor(AppColor.primary, for: .normal)
relocateButton.backgroundColor = AppColor.primaryLight
relocateButton.layer.cornerRadius = 4
relocateButton.setConfigurationContentInsets(
NSDirectionalEdgeInsets(top: 2, leading: 8, bottom: 2, trailing: 8)
)
relocateButton.addTarget(self, action: #selector(relocateTapped), for: .touchUpInside)
relocateButton.setContentHuggingPriority(.required, for: .horizontal)
relocateButton.setContentCompressionResistancePriority(.required, for: .horizontal)
addSubview(iconView)
addSubview(locationLabel)
addSubview(relocateButton)
iconView.snp.makeConstraints { make in
make.leading.equalToSuperview()
make.centerY.equalTo(locationLabel)
make.size.equalTo(16)
}
locationLabel.snp.makeConstraints { make in
make.leading.equalTo(iconView.snp.trailing).offset(4)
make.top.bottom.equalToSuperview().inset(8)
make.trailing.lessThanOrEqualTo(relocateButton.snp.leading).offset(-12)
}
relocateButton.snp.makeConstraints { make in
make.trailing.equalToSuperview()
make.centerY.equalTo(locationLabel)
}
setContentHuggingPriority(.required, for: .vertical)
setContentCompressionResistancePriority(.required, for: .vertical)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(locationText: String, isLocating: Bool = false) {
locationLabel.text = isLocating ? "定位中..." : locationText
relocateButton.isEnabled = !isLocating
relocateButton.alpha = isLocating ? 0.5 : 1
}
@objc private func relocateTapped() {
onRelocate?()
}
}
/// Cell
final class ScenicSpotCell: UITableViewCell {
static let reuseIdentifier = "ScenicSpotCell"
var onTap: (() -> Void)?
private let cardView = UIView()
private let coverImageView = UIImageView()
private let nameLabel = UILabel()
private let distanceLabel = UILabel()
private let addressLabel = UILabel()
private let statusTag = UILabel()
private let selectedBorderColor = UIColor(hex: 0xE74C3C)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
contentView.backgroundColor = .clear
cardView.backgroundColor = AppColor.pageBackgroundSoft
cardView.layer.cornerRadius = 8
cardView.layer.borderWidth = 2
cardView.layer.borderColor = UIColor(hex: 0xF4F4F4).cgColor
coverImageView.contentMode = .scaleAspectFill
coverImageView.clipsToBounds = true
coverImageView.layer.cornerRadius = 6
coverImageView.backgroundColor = UIColor(hex: 0xF0F0F0)
nameLabel.font = .systemFont(ofSize: 16, weight: .bold)
nameLabel.textColor = AppColor.textPrimary
distanceLabel.font = .systemFont(ofSize: 12)
distanceLabel.textColor = AppColor.textSecondary
addressLabel.font = .systemFont(ofSize: 12)
addressLabel.textColor = AppColor.textTertiary
addressLabel.numberOfLines = 2
statusTag.font = .systemFont(ofSize: 11, weight: .medium)
statusTag.textColor = .white
statusTag.textAlignment = .center
statusTag.layer.cornerRadius = 4
statusTag.clipsToBounds = true
statusTag.isHidden = true
contentView.addSubview(cardView)
cardView.addSubview(coverImageView)
cardView.addSubview(nameLabel)
cardView.addSubview(statusTag)
cardView.addSubview(distanceLabel)
cardView.addSubview(addressLabel)
cardView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 12, right: 16))
}
coverImageView.snp.makeConstraints { make in
make.leading.top.bottom.equalToSuperview().inset(12)
make.size.equalTo(80)
}
nameLabel.snp.makeConstraints { make in
make.leading.equalTo(coverImageView.snp.trailing).offset(12)
make.top.equalToSuperview().offset(12)
make.trailing.lessThanOrEqualTo(statusTag.snp.leading).offset(-8)
}
statusTag.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-12)
make.centerY.equalTo(nameLabel)
make.height.equalTo(20)
}
distanceLabel.snp.makeConstraints { make in
make.leading.equalTo(nameLabel)
make.top.equalTo(nameLabel.snp.bottom).offset(6)
}
addressLabel.snp.makeConstraints { make in
make.leading.equalTo(nameLabel)
make.trailing.equalToSuperview().offset(-12)
make.top.equalTo(distanceLabel.snp.bottom).offset(4)
make.bottom.lessThanOrEqualToSuperview().offset(-12)
}
let tap = UITapGestureRecognizer(target: self, action: #selector(cardTapped))
cardView.addGestureRecognizer(tap)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(_ item: ScenicSpotDisplayItem) {
nameLabel.text = item.name
distanceLabel.text = "\(item.distanceText)km"
addressLabel.text = item.address
if let url = URL(string: item.coverImg), !item.coverImg.isEmpty {
coverImageView.kf.setImage(with: url, placeholder: UIImage(systemName: "photo"))
} else {
coverImageView.image = UIImage(systemName: "photo")
coverImageView.tintColor = AppColor.textTertiary
}
if let label = item.statusLabel {
statusTag.isHidden = false
statusTag.text = " \(label) "
switch item.openStatus {
case .nearest, .closed:
statusTag.backgroundColor = selectedBorderColor
case .none:
statusTag.backgroundColor = AppColor.textTertiary
}
} else {
statusTag.isHidden = true
}
cardView.layer.borderColor = (item.isSelected ? selectedBorderColor : UIColor(hex: 0xF4F4F4)).cgColor
}
@objc private func cardTapped() {
onTap?()
}
}