Files
suixinkan_uikit/suixinkan/UI/MaterialManagement/MaterialManagementDialogs.swift
汉秋 005349f8e6 模块化 AppStore 并完善素材管理与个人空间设置。
将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-13 11:01:08 +08:00

259 lines
9.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.

//
// MaterialManagementDialogs.swift
// suixinkan
//
import AVKit
import Kingfisher
import SnapKit
import UIKit
/// Android
final class MaterialDeleteDialogViewController: UIViewController {
private let dialogTitle: String
private let itemName: String
private let itemSuffix: String
private let onConfirm: () -> Void
private let onCancel: () -> Void
private let dimView = UIView()
private let cardView = UIView()
///
init(
title: String = "删除素材",
itemName: String,
itemSuffix: String = "素材吗?",
onCancel: @escaping () -> Void = {},
onConfirm: @escaping () -> Void
) {
dialogTitle = title
self.itemName = itemName
self.itemSuffix = itemSuffix
self.onConfirm = onConfirm
self.onCancel = onCancel
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .overFullScreen
modalTransitionStyle = .crossDissolve
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
dimView.backgroundColor = UIColor.black.withAlphaComponent(0.32)
cardView.backgroundColor = .white
cardView.layer.cornerRadius = 16
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 16
let titleLabel = UILabel()
titleLabel.text = dialogTitle
titleLabel.font = .systemFont(ofSize: 18, weight: .bold)
titleLabel.textColor = UIColor(hex: 0xFF0000)
let messageLabel = UILabel()
messageLabel.numberOfLines = 0
let prefix = "您确定要删除"
let suffix = itemSuffix
let message = NSMutableAttributedString(
string: prefix + itemName + suffix,
attributes: [
.font: UIFont.systemFont(ofSize: 16),
.foregroundColor: UIColor(hex: 0x333333),
]
)
if !itemName.isEmpty {
let prefixLength = (prefix as NSString).length
let itemLength = (itemName as NSString).length
message.addAttribute(
.font,
value: UIFont.systemFont(ofSize: 16, weight: .bold),
range: NSRange(location: prefixLength, length: itemLength)
)
}
messageLabel.attributedText = message
let buttons = UIStackView()
buttons.axis = .horizontal
buttons.spacing = 12
buttons.distribution = .fillEqually
let cancelButton = makeButton(title: "取消", titleColor: UIColor(hex: 0xFF0000), background: UIColor(hex: 0xFFE5E5))
let confirmButton = makeButton(title: "确认", titleColor: .white, background: UIColor(hex: 0xFF0000))
cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
[cancelButton, confirmButton].forEach { button in
button.snp.makeConstraints { make in make.height.equalTo(44) }
buttons.addArrangedSubview(button)
}
view.addSubview(dimView)
view.addSubview(cardView)
cardView.addSubview(stack)
[titleLabel, messageLabel, buttons].forEach(stack.addArrangedSubview)
stack.setCustomSpacing(24, after: messageLabel)
dimView.snp.makeConstraints { make in make.edges.equalToSuperview() }
cardView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.leading.trailing.equalToSuperview().inset(32)
}
stack.snp.makeConstraints { make in make.edges.equalToSuperview().inset(24) }
}
private func makeButton(title: String, titleColor: UIColor, background: UIColor) -> UIButton {
let button = UIButton(type: .system)
button.setTitle(title, for: .normal)
button.setTitleColor(titleColor, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
button.backgroundColor = background
button.layer.cornerRadius = 8
return button
}
@objc private func cancelTapped() {
dismiss(animated: true) { [onCancel] in onCancel() }
}
@objc private func confirmTapped() {
dismiss(animated: true) { [onConfirm] in onConfirm() }
}
}
///
struct MaterialPreviewItem: Equatable {
let url: String
let mediaType: MaterialMediaType
}
/// Android `MediaPreviewDialog`
final class MaterialMediaPreviewViewController: UIViewController, UIScrollViewDelegate {
private let items: [MaterialPreviewItem]
private let startIndex: Int
private let scrollView = UIScrollView()
private let closeButton = UIButton(type: .system)
private var pages: [UIView] = []
private var players: [Int: AVPlayerViewController] = [:]
private var currentIndex = 0
///
init(items: [MaterialPreviewItem], startIndex: Int) {
self.items = items
self.startIndex = max(0, min(startIndex, max(0, items.count - 1)))
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .overFullScreen
modalTransitionStyle = .crossDissolve
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
scrollView.backgroundColor = .black
scrollView.isPagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
scrollView.bounces = false
scrollView.delegate = self
closeButton.setImage(UIImage(named: "material_ic_clear"), for: .normal)
closeButton.tintColor = .white
closeButton.backgroundColor = UIColor.black.withAlphaComponent(0.5)
closeButton.layer.cornerRadius = 22
closeButton.accessibilityLabel = "关闭"
closeButton.addTarget(self, action: #selector(closeTapped), for: .touchUpInside)
view.addSubview(scrollView)
view.addSubview(closeButton)
scrollView.snp.makeConstraints { make in make.edges.equalToSuperview() }
closeButton.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(5)
make.trailing.equalToSuperview().inset(16)
make.size.equalTo(44)
}
buildPages()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let size = scrollView.bounds.size
guard size.width > 0, size.height > 0 else { return }
for (index, page) in pages.enumerated() {
page.frame = CGRect(x: CGFloat(index) * size.width, y: 0, width: size.width, height: size.height)
}
scrollView.contentSize = CGSize(width: CGFloat(items.count) * size.width, height: size.height)
scrollView.contentOffset = CGPoint(x: CGFloat(currentIndex) * size.width, y: 0)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
currentIndex = startIndex
view.setNeedsLayout()
view.layoutIfNeeded()
playCurrentVideo()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
players.values.forEach { $0.player?.pause() }
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
updateCurrentIndex()
}
private func buildPages() {
for (index, item) in items.enumerated() {
let page = UIView()
page.backgroundColor = .black
scrollView.addSubview(page)
pages.append(page)
if item.mediaType == .video, let url = URL(string: item.url) {
let controller = AVPlayerViewController()
controller.player = AVPlayer(url: url)
controller.showsPlaybackControls = true
addChild(controller)
page.addSubview(controller.view)
controller.view.snp.makeConstraints { make in make.edges.equalToSuperview() }
controller.didMove(toParent: self)
players[index] = controller
} else {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .black
page.addSubview(imageView)
imageView.snp.makeConstraints { make in make.edges.equalToSuperview() }
if let url = URL(string: item.url) {
imageView.kf.setImage(with: url)
}
}
}
}
private func updateCurrentIndex() {
guard scrollView.bounds.width > 0 else { return }
currentIndex = max(0, min(Int(round(scrollView.contentOffset.x / scrollView.bounds.width)), items.count - 1))
playCurrentVideo()
}
private func playCurrentVideo() {
players.forEach { index, controller in
if index == currentIndex {
controller.player?.play()
} else {
controller.player?.pause()
}
}
}
@objc private func closeTapped() {
dismiss(animated: true)
}
}