Files
suixinkan_uikit/suixinkan/UI/Live/LiveUIComponents.swift
汉秋 6336feff27 完善景区排队设置页与全局按钮配置迁移。
重构排队设置与变更日志交互,补充 Overlay 与资源,并将多页面主操作按钮统一到 UIButton Configuration,同步更新相关单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 16:21:53 +08:00

243 lines
8.3 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.

//
// LiveUIComponents.swift
// suixinkan
//
import Kingfisher
import PhotosUI
import SnapKit
import UIKit
/// UI Android 使
enum LiveUIFormatter {
///
static func timeRange(start: Int64, end: Int64) -> String {
guard start > 0 || end > 0 else { return "" }
let startText = timestampText(start)
let endText = timestampText(end)
if startText.isEmpty { return endText }
if endText.isEmpty { return startText }
return "\(startText) - \(endText)"
}
///
static func duration(_ seconds: Int64) -> String {
let value = max(seconds, 0)
let hour = value / 3600
let minute = (value % 3600) / 60
let second = value % 60
if hour > 0 {
return String(format: "%02lld:%02lld:%02lld", hour, minute, second)
}
return String(format: "%02lld:%02lld", minute, second)
}
///
static func date(_ date: Date?) -> String {
guard let date else { return "" }
return LiveAlbumListViewModel.dateString(date)
}
private static func timestampText(_ timestamp: Int64) -> String {
guard timestamp > 0 else { return "" }
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .gregorian)
formatter.locale = Locale(identifier: "zh_CN")
formatter.dateFormat = "yyyy-MM-dd HH:mm"
return formatter.string(from: date)
}
}
/// Android
final class LiveActionButton: UIButton {
enum Style {
case primary
case warning
case danger
case disabled
}
var buttonStyle: Style = .primary {
didSet { updateAppearance() }
}
override var isEnabled: Bool {
didSet { updateAppearance() }
}
///
init(title: String, image: UIImage? = nil) {
super.init(frame: .zero)
setTitle(title, for: .normal)
setImage(image, for: .normal)
titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
layer.cornerRadius = 8
clipsToBounds = true
tintColor = .white
setConfigurationImagePlacement(.leading, padding: 4)
updateAppearance()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func updateAppearance() {
let effectiveStyle: Style = isEnabled ? buttonStyle : .disabled
switch effectiveStyle {
case .primary:
backgroundColor = UIColor(hex: 0x0066FF)
setTitleColor(.white, for: .normal)
tintColor = .white
case .warning:
backgroundColor = AppColor.warning
setTitleColor(.white, for: .normal)
tintColor = .white
case .danger:
backgroundColor = AppColor.danger
setTitleColor(.white, for: .normal)
tintColor = .white
case .disabled:
backgroundColor = AppColor.inputBackground
setTitleColor(UIColor(hex: 0xB6BECA), for: .normal)
tintColor = UIColor(hex: 0xB6BECA)
}
}
}
/// Kingfisher
final class LiveCoverImageView: UIImageView {
///
init(cornerRadius: CGFloat = 8) {
super.init(frame: .zero)
contentMode = .scaleAspectFill
clipsToBounds = true
backgroundColor = UIColor(hex: 0xF4F4F4)
layer.cornerRadius = cornerRadius
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
///
func setURL(_ text: String) {
if let url = URL(string: text), !text.isEmpty {
kf.setImage(with: url, placeholder: placeholderImage())
} else {
image = placeholderImage()
}
}
private func placeholderImage() -> UIImage? {
UIImage(named: "img_square_loading_big") ?? UIImage(systemName: "photo")
}
}
/// 线
final class LiveDashedUploadView: UIControl {
private let plusContainer = UIView()
private let plusImageView = UIImageView(image: UIImage(systemName: "plus"))
private let titleLabel = UILabel()
private let subtitleLabel = UILabel()
private var dashLayer: CAShapeLayer?
/// 线
init(title: String = "点击上传图片", subtitle: String? = nil) {
super.init(frame: .zero)
setup(title: title, subtitle: subtitle)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
dashLayer?.removeFromSuperlayer()
let layer = CAShapeLayer()
layer.strokeColor = AppColor.border.cgColor
layer.fillColor = UIColor.clear.cgColor
layer.lineWidth = 2
layer.lineDashPattern = [6, 6]
layer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 8).cgPath
self.layer.insertSublayer(layer, at: 0)
dashLayer = layer
}
private func setup(title: String, subtitle: String?) {
backgroundColor = .white
plusContainer.backgroundColor = AppColor.primary
plusContainer.layer.cornerRadius = 16
plusImageView.tintColor = .white
plusImageView.contentMode = .scaleAspectFit
titleLabel.text = title
titleLabel.font = .systemFont(ofSize: 14)
titleLabel.textColor = subtitle == nil ? UIColor(hex: 0xB6BECA) : UIColor(hex: 0x4B5563)
subtitleLabel.text = subtitle
subtitleLabel.font = .systemFont(ofSize: 12)
subtitleLabel.textColor = UIColor(hex: 0xB6BECA)
subtitleLabel.textAlignment = .center
addSubview(plusContainer)
plusContainer.addSubview(plusImageView)
addSubview(titleLabel)
addSubview(subtitleLabel)
plusContainer.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.centerY.equalToSuperview().offset(subtitle == nil ? -14 : -24)
make.size.equalTo(32)
}
plusImageView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.size.equalTo(18)
}
titleLabel.snp.makeConstraints { make in
make.top.equalTo(plusContainer.snp.bottom).offset(12)
make.centerX.equalToSuperview()
}
subtitleLabel.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(8)
make.centerX.equalToSuperview()
}
}
}
///
enum LivePhotoPickerLoader {
/// PHPicker
static func loadImage(from result: PHPickerResult) async throws -> LivePickedMedia {
try await withCheckedThrowingContinuation { continuation in
let provider = result.itemProvider
guard provider.canLoadObject(ofClass: UIImage.self) else {
continuation.resume(throwing: OSSUploadError.unsupportedFileType)
return
}
provider.loadObject(ofClass: UIImage.self) { object, error in
if let error {
continuation.resume(throwing: error)
return
}
guard let image = object as? UIImage, let data = image.jpegData(compressionQuality: 0.9) else {
continuation.resume(throwing: OSSUploadError.emptyFile)
return
}
continuation.resume(
returning: LivePickedMedia(
data: data,
fileName: "image_\(UUID().uuidString).jpg",
size: Int64(data.count),
width: Int(image.size.width),
height: Int(image.size.height)
)
)
}
}
}
}