feat: add live module and optimize report list

This commit is contained in:
2026-07-08 14:25:14 +08:00
parent 8ca8bcf948
commit 4a78a0c21a
14 changed files with 3712 additions and 102 deletions

View File

@ -0,0 +1,700 @@
//
// LiveAlbumViewControllers.swift
// suixinkan
//
import Kingfisher
import PhotosUI
import SnapKit
import UIKit
/// Android `AliveAlbumScreen`
final class LiveAlbumListViewController: BaseViewController, UITableViewDelegate {
private enum Section {
case main
}
private let viewModel = LiveAlbumListViewModel()
private let liveAPI: any LiveServing
private let filterCard = UIView()
private let startButton = UIButton(type: .system)
private let endButton = UIButton(type: .system)
private let tableView = UITableView(frame: .zero, style: .plain)
private let refreshControl = UIRefreshControl()
private let emptyLabel = UILabel()
private let loadingIndicator = UIActivityIndicatorView(style: .medium)
private let bottomBar = UIView()
private let uploadButton = AppButton(title: "上传素材")
private var dataSource: UITableViewDiffableDataSource<Section, LiveAlbumFolder>!
///
@MainActor
init(liveAPI: (any LiveServing)? = nil) {
self.liveAPI = liveAPI ?? NetworkServices.shared.liveAPI
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "直播相册"
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackground
filterCard.backgroundColor = .white
filterCard.layer.cornerRadius = 12
configureDateButton(startButton, title: "开始时间")
configureDateButton(endButton, title: "结束时间")
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.delegate = self
tableView.refreshControl = refreshControl
tableView.register(LiveAlbumFolderCell.self, forCellReuseIdentifier: LiveAlbumFolderCell.reuseIdentifier)
tableView.contentInset = UIEdgeInsets(top: 12, left: 0, bottom: 12, right: 0)
dataSource = UITableViewDiffableDataSource<Section, LiveAlbumFolder>(tableView: tableView) { [weak self] tableView, indexPath, folder in
guard let cell = tableView.dequeueReusableCell(withIdentifier: LiveAlbumFolderCell.reuseIdentifier, for: indexPath) as? LiveAlbumFolderCell else {
return UITableViewCell()
}
cell.apply(folder)
cell.onDelete = { [weak self] in self?.confirmDelete(folder) }
cell.onImageSelected = { [weak self] index in
self?.navigationController?.pushViewController(
LiveAlbumPreviewViewController(folderId: folder.id, startIndex: index),
animated: true
)
}
return cell
}
emptyLabel.text = "暂无素材"
emptyLabel.font = .systemFont(ofSize: 14)
emptyLabel.textColor = UIColor(hex: 0x4B5563)
emptyLabel.isHidden = true
loadingIndicator.hidesWhenStopped = true
loadingIndicator.color = AppColor.primary
bottomBar.backgroundColor = .white
view.addSubview(filterCard)
filterCard.addSubview(startButton)
filterCard.addSubview(endButton)
view.addSubview(tableView)
view.addSubview(emptyLabel)
view.addSubview(loadingIndicator)
view.addSubview(bottomBar)
bottomBar.addSubview(uploadButton)
}
override func setupConstraints() {
filterCard.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide)
make.leading.trailing.equalToSuperview()
make.height.equalTo(59)
}
startButton.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(12)
make.centerY.equalToSuperview()
make.height.equalTo(35)
}
endButton.snp.makeConstraints { make in
make.leading.equalTo(startButton.snp.trailing).offset(8)
make.trailing.equalToSuperview().inset(12)
make.centerY.width.height.equalTo(startButton)
}
bottomBar.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
}
uploadButton.snp.makeConstraints { make in
make.top.equalToSuperview().offset(16)
make.leading.trailing.equalToSuperview().inset(16)
make.height.equalTo(48)
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16)
}
tableView.snp.makeConstraints { make in
make.top.equalTo(filterCard.snp.bottom)
make.leading.trailing.equalToSuperview()
make.bottom.equalTo(bottomBar.snp.top)
}
emptyLabel.snp.makeConstraints { make in
make.center.equalTo(tableView)
}
loadingIndicator.snp.makeConstraints { make in
make.center.equalTo(tableView)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
viewModel.onShowMessage = { [weak self] message in
Task { @MainActor in self?.showToast(message) }
}
refreshControl.addTarget(self, action: #selector(refreshTriggered), for: .valueChanged)
startButton.addTarget(self, action: #selector(selectStartDate), for: .touchUpInside)
endButton.addTarget(self, action: #selector(selectEndDate), for: .touchUpInside)
uploadButton.addTarget(self, action: #selector(openUpload), for: .touchUpInside)
}
override func viewDidLoad() {
super.viewDidLoad()
Task { await viewModel.loadInitial(api: liveAPI) }
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
Task { await viewModel.refresh(api: liveAPI) }
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let last = tableView.indexPathsForVisibleRows?.map(\.row).max() else { return }
Task { await viewModel.loadMoreIfNeeded(lastVisibleIndex: last, api: liveAPI) }
}
@objc private func refreshTriggered() {
Task { await viewModel.refresh(api: liveAPI) }
}
@objc private func selectStartDate() {
presentDatePicker(initialDate: viewModel.startDate ?? Date()) { [weak self] date in
guard let self else { return }
Task { await self.viewModel.setStartDate(date, api: self.liveAPI) }
}
}
@objc private func selectEndDate() {
presentDatePicker(initialDate: viewModel.endDate ?? Date()) { [weak self] date in
guard let self else { return }
Task { await self.viewModel.setEndDate(date, api: self.liveAPI) }
}
}
@objc private func openUpload() {
navigationController?.pushViewController(LiveAlbumAddViewController(), animated: true)
}
private func applyViewModel() {
let startText = LiveUIFormatter.date(viewModel.startDate)
let endText = LiveUIFormatter.date(viewModel.endDate)
setDateButtonTitle(startButton, text: startText.isEmpty ? "开始时间" : startText)
setDateButtonTitle(endButton, text: endText.isEmpty ? "结束时间" : endText)
if viewModel.isRefreshing {
if !refreshControl.isRefreshing { refreshControl.beginRefreshing() }
} else {
refreshControl.endRefreshing()
}
viewModel.isLoading && viewModel.folders.isEmpty ? loadingIndicator.startAnimating() : loadingIndicator.stopAnimating()
emptyLabel.isHidden = viewModel.isLoading || !viewModel.folders.isEmpty
var snapshot = NSDiffableDataSourceSnapshot<Section, LiveAlbumFolder>()
snapshot.appendSections([.main])
snapshot.appendItems(viewModel.folders)
dataSource.apply(snapshot, animatingDifferences: true)
}
private func confirmDelete(_ folder: LiveAlbumFolder) {
showAlert(title: "确认删除", message: "是否确认删除该相册?删除后将无法恢复。") { [weak self] in
guard let self else { return }
Task { await self.viewModel.deleteFolder(folder, api: self.liveAPI) }
}
}
private func configureDateButton(_ button: UIButton, title: String) {
button.backgroundColor = UIColor(hex: 0xF9FAFB)
button.layer.cornerRadius = 4
button.setTitleColor(AppColor.textPrimary, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 13)
button.setImage(UIImage(systemName: "calendar"), for: .normal)
button.tintColor = AppColor.textPrimary
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -4, bottom: 0, right: 4)
setDateButtonTitle(button, text: title)
}
private func setDateButtonTitle(_ button: UIButton, text: String) {
button.setTitle(text, for: .normal)
}
private func presentDatePicker(initialDate: Date, onConfirm: @escaping (Date) -> Void) {
let alert = UIAlertController(title: nil, message: "\n\n\n\n\n\n\n\n", preferredStyle: .actionSheet)
let picker = UIDatePicker()
picker.datePickerMode = .date
picker.preferredDatePickerStyle = .wheels
picker.locale = Locale(identifier: "zh_CN")
picker.date = initialDate
alert.view.addSubview(picker)
picker.snp.makeConstraints { make in
make.top.equalToSuperview().offset(8)
make.leading.trailing.equalToSuperview().inset(8)
make.height.equalTo(216)
}
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in onConfirm(picker.date) })
present(alert, animated: true)
}
}
/// Cell Android `AliveAlbumFolderView`
private final class LiveAlbumFolderCell: UITableViewCell {
static let reuseIdentifier = "LiveAlbumFolderCell"
var onDelete: (() -> Void)?
var onImageSelected: ((Int) -> Void)?
private let container = UIView()
private let titleLabel = UILabel()
private let deleteButton = UIButton(type: .system)
private let gridStack = UIStackView()
private var imageViews: [LiveCoverImageView] = []
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
onDelete = nil
onImageSelected = nil
imageViews.forEach { $0.removeFromSuperview() }
imageViews.removeAll()
gridStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
}
///
func apply(_ folder: LiveAlbumFolder) {
titleLabel.text = folder.displayName
buildGrid(items: folder.items)
}
private func setup() {
selectionStyle = .none
backgroundColor = .clear
container.backgroundColor = .white
titleLabel.font = .systemFont(ofSize: 16, weight: .medium)
titleLabel.textColor = AppColor.textPrimary
deleteButton.setImage(UIImage(systemName: "trash"), for: .normal)
deleteButton.tintColor = AppColor.danger
gridStack.axis = .vertical
gridStack.spacing = 12
contentView.addSubview(container)
container.addSubview(titleLabel)
container.addSubview(deleteButton)
container.addSubview(gridStack)
container.snp.makeConstraints { make in
make.top.bottom.equalToSuperview()
make.leading.trailing.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.top.leading.equalToSuperview().offset(16)
make.trailing.lessThanOrEqualTo(deleteButton.snp.leading).offset(-8)
}
deleteButton.snp.makeConstraints { make in
make.top.trailing.equalToSuperview().inset(8)
make.size.equalTo(44)
}
gridStack.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(12)
make.leading.trailing.equalToSuperview().inset(12)
make.bottom.equalToSuperview().inset(12)
}
deleteButton.addTarget(self, action: #selector(deleteTapped), for: .touchUpInside)
}
private func buildGrid(items: [LiveAlbumFile]) {
let rows = stride(from: 0, to: items.count, by: 2).map { start in
Array(items[start ..< min(start + 2, items.count)])
}
for (rowIndex, rowItems) in rows.enumerated() {
let rowStack = UIStackView()
rowStack.axis = .horizontal
rowStack.spacing = 12
rowStack.distribution = .fillEqually
gridStack.addArrangedSubview(rowStack)
for (columnIndex, item) in rowItems.enumerated() {
let imageView = LiveCoverImageView(cornerRadius: 12)
imageView.setURL(item.url)
imageView.isUserInteractionEnabled = true
imageView.tag = rowIndex * 2 + columnIndex
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imageTapped(_:))))
rowStack.addArrangedSubview(imageView)
imageViews.append(imageView)
imageView.snp.makeConstraints { make in
make.height.equalTo(imageView.snp.width).multipliedBy(9.0 / 16.0)
}
}
if rowItems.count == 1 {
rowStack.addArrangedSubview(UIView())
}
}
}
@objc private func deleteTapped() {
onDelete?()
}
@objc private func imageTapped(_ recognizer: UITapGestureRecognizer) {
guard let view = recognizer.view else { return }
onImageSelected?(view.tag)
}
}
/// Android `AliveAlbumAddScreen`
final class LiveAlbumAddViewController: BaseViewController, PHPickerViewControllerDelegate {
private let viewModel = LiveAlbumAddViewModel()
private let liveAPI: any LiveServing
private let uploader: any LiveOSSUploading
private let scrollView = UIScrollView()
private let cardView = UIView()
private let stackView = UIStackView()
private let titleField = UITextField()
private let uploadView = LiveDashedUploadView(title: "点击上传图片", subtitle: "支持 jpg、png、mp4 格式")
private let filesStack = UIStackView()
private let bottomBar = UIView()
private let confirmButton = AppButton(title: "确认")
///
@MainActor
init(
liveAPI: (any LiveServing)? = nil,
uploader: (any LiveOSSUploading)? = nil
) {
self.liveAPI = liveAPI ?? NetworkServices.shared.liveAPI
self.uploader = uploader ?? NetworkServices.shared.ossUploadService
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "上传素材"
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackground
cardView.backgroundColor = .white
cardView.layer.cornerRadius = 12
stackView.axis = .vertical
stackView.spacing = 8
filesStack.axis = .vertical
filesStack.spacing = 12
titleField.placeholder = "请输入作品名称"
titleField.font = .systemFont(ofSize: 14)
titleField.layer.borderWidth = 1
titleField.layer.borderColor = AppColor.border.cgColor
titleField.layer.cornerRadius = 8
titleField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: 1))
titleField.leftViewMode = .always
bottomBar.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(cardView)
cardView.addSubview(stackView)
addRequiredTitle("直播标题", to: stackView)
stackView.addArrangedSubview(titleField)
addRequiredTitle("封面图片", suffix: "(单次最多可上传 20 张图片)", to: stackView)
stackView.addArrangedSubview(uploadView)
stackView.addArrangedSubview(filesStack)
view.addSubview(bottomBar)
bottomBar.addSubview(confirmButton)
}
override func setupConstraints() {
bottomBar.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
}
confirmButton.snp.makeConstraints { make in
make.top.equalToSuperview().offset(16)
make.leading.trailing.equalToSuperview().inset(16)
make.height.equalTo(48)
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16)
}
scrollView.snp.makeConstraints { make in
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
make.bottom.equalTo(bottomBar.snp.top)
}
cardView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
make.width.equalTo(scrollView).offset(-32)
}
stackView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
}
titleField.snp.makeConstraints { make in
make.height.equalTo(44)
}
uploadView.snp.makeConstraints { make in
make.height.equalTo(120)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
viewModel.onShowMessage = { [weak self] message in
Task { @MainActor in self?.showToast(message) }
}
viewModel.onCreateSuccess = { [weak self] in
Task { @MainActor in self?.navigationController?.popViewController(animated: true) }
}
titleField.addTarget(self, action: #selector(titleChanged), for: .editingChanged)
uploadView.addTarget(self, action: #selector(pickImage), for: .touchUpInside)
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let result = results.first else { return }
Task {
do {
let media = try await LivePhotoPickerLoader.loadImage(from: result)
await viewModel.addLocalMedia(media, uploader: uploader)
} catch {
showToast(error.localizedDescription)
}
}
}
@objc private func titleChanged() {
viewModel.updateTitle(titleField.text ?? "")
if titleField.text != viewModel.title {
titleField.text = viewModel.title
}
}
@objc private func pickImage() {
var configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.filter = .images
configuration.selectionLimit = 1
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
@objc private func confirmTapped() {
Task { await viewModel.create(api: liveAPI) }
}
private func applyViewModel() {
viewModel.isLoading ? showLoading() : hideLoading()
filesStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
for (index, file) in viewModel.files.enumerated() {
let view = LiveAlbumUploadedFileView(file: file)
view.onDelete = { [weak self] in self?.confirmDeleteLocalFile(at: index) }
filesStack.addArrangedSubview(view)
view.snp.makeConstraints { make in
make.height.equalTo(view.snp.width).multipliedBy(9.0 / 16.0)
}
}
}
private func confirmDeleteLocalFile(at index: Int) {
showAlert(title: "确认删除", message: "是否确认删除该相册?删除后将无法恢复。") { [weak self] in
self?.viewModel.deleteLocalFile(at: index)
}
}
private func addRequiredTitle(_ title: String, suffix: String? = nil, to stack: UIStackView) {
let label = UILabel()
let text = NSMutableAttributedString(
string: "\(title) *",
attributes: [.font: UIFont.systemFont(ofSize: 14, weight: .medium), .foregroundColor: AppColor.textPrimary]
)
text.addAttribute(.foregroundColor, value: UIColor.red, range: NSRange(location: title.count + 1, length: 1))
if let suffix {
text.append(NSAttributedString(string: suffix, attributes: [.font: UIFont.systemFont(ofSize: 14), .foregroundColor: AppColor.textPrimary]))
}
label.attributedText = text
stack.addArrangedSubview(label)
}
}
///
private final class LiveAlbumUploadedFileView: UIView {
var onDelete: (() -> Void)?
private let imageView = LiveCoverImageView()
private let deleteButton = UIButton(type: .system)
///
init(file: LiveAlbumFile) {
super.init(frame: .zero)
imageView.setURL(file.url)
deleteButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal)
deleteButton.tintColor = .gray
addSubview(imageView)
addSubview(deleteButton)
imageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
deleteButton.snp.makeConstraints { make in
make.top.trailing.equalToSuperview().inset(4)
make.size.equalTo(28)
}
deleteButton.addTarget(self, action: #selector(deleteTapped), for: .touchUpInside)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func deleteTapped() {
onDelete?()
}
}
/// Android `AliveAlbumPreviewScreen`
final class LiveAlbumPreviewViewController: BaseViewController, UIScrollViewDelegate {
private let viewModel: LiveAlbumPreviewViewModel
private let liveAPI: any LiveServing
private let pagingScrollView = UIScrollView()
private let pageControl = UIPageControl()
private let emptyLabel = UILabel()
private let bottomBar = UIView()
private let deleteButton = LiveActionButton(title: "删除素材")
///
@MainActor
init(folderId: Int, startIndex: Int, liveAPI: (any LiveServing)? = nil) {
viewModel = LiveAlbumPreviewViewModel(folderId: folderId, startIndex: startIndex)
self.liveAPI = liveAPI ?? NetworkServices.shared.liveAPI
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "预览"
navigationController?.navigationBar.barTintColor = .black
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white]
navigationController?.navigationBar.tintColor = .white
}
override func setupUI() {
view.backgroundColor = .black
pagingScrollView.isPagingEnabled = true
pagingScrollView.showsHorizontalScrollIndicator = false
pagingScrollView.delegate = self
pageControl.currentPageIndicatorTintColor = .white
pageControl.pageIndicatorTintColor = UIColor.white.withAlphaComponent(0.4)
emptyLabel.text = "没有可预览的图片"
emptyLabel.textColor = .white
emptyLabel.font = .systemFont(ofSize: 16)
emptyLabel.textAlignment = .center
bottomBar.backgroundColor = .black
deleteButton.buttonStyle = .danger
view.addSubview(pagingScrollView)
view.addSubview(pageControl)
view.addSubview(emptyLabel)
view.addSubview(bottomBar)
bottomBar.addSubview(deleteButton)
}
override func setupConstraints() {
bottomBar.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
}
deleteButton.snp.makeConstraints { make in
make.top.equalToSuperview().offset(16)
make.leading.trailing.equalToSuperview().inset(16)
make.height.equalTo(48)
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16)
}
pagingScrollView.snp.makeConstraints { make in
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
make.bottom.equalTo(bottomBar.snp.top)
}
pageControl.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(pagingScrollView).inset(8)
}
emptyLabel.snp.makeConstraints { make in
make.center.equalTo(pagingScrollView)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
viewModel.onShowMessage = { [weak self] message in
Task { @MainActor in self?.showToast(message) }
}
deleteButton.addTarget(self, action: #selector(confirmDelete), for: .touchUpInside)
}
override func viewDidLoad() {
super.viewDidLoad()
Task { await viewModel.load(api: liveAPI) }
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.barTintColor = nil
navigationController?.navigationBar.titleTextAttributes = nil
navigationController?.navigationBar.tintColor = nil
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
layoutPages()
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let index = Int(round(scrollView.contentOffset.x / max(scrollView.bounds.width, 1)))
viewModel.updateCurrentIndex(index)
}
private func applyViewModel() {
viewModel.isLoading && viewModel.files.isEmpty ? showLoading() : hideLoading()
pageControl.numberOfPages = viewModel.files.count
pageControl.currentPage = viewModel.currentIndex
emptyLabel.isHidden = !viewModel.files.isEmpty
deleteButton.isEnabled = !viewModel.files.isEmpty
buildPages()
}
private func buildPages() {
pagingScrollView.subviews.forEach { $0.removeFromSuperview() }
for (index, file) in viewModel.files.enumerated() {
let imageView = LiveCoverImageView(cornerRadius: 0)
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .black
imageView.setURL(file.url)
imageView.tag = index
pagingScrollView.addSubview(imageView)
}
layoutPages()
}
private func layoutPages() {
let size = pagingScrollView.bounds.size
guard size.width > 0 else { return }
for (index, view) in pagingScrollView.subviews.enumerated() {
view.frame = CGRect(x: CGFloat(index) * size.width, y: 0, width: size.width, height: size.height)
}
pagingScrollView.contentSize = CGSize(width: size.width * CGFloat(viewModel.files.count), height: size.height)
pagingScrollView.setContentOffset(CGPoint(x: CGFloat(viewModel.currentIndex) * size.width, y: 0), animated: false)
}
@objc private func confirmDelete() {
showAlert(title: "确认删除", message: "是否确认删除该素材?删除后将无法恢复。") { [weak self] in
guard let self else { return }
Task { await self.viewModel.deleteCurrentFile(api: self.liveAPI) }
}
}
}