// // CloudDriveTransferViewController.swift // suixinkan // import SnapKit import UIKit /// 云盘传输管理页,对齐 Android `CloudStorageTransitScreen`。 final class CloudDriveTransferViewController: BaseViewController { private enum Tab: Int { case upload case download } private let manager: CloudTransferManager private let segmentedControl = UISegmentedControl(items: ["上传", "下载"]) private let tableView = UITableView(frame: .zero, style: .plain) private var selectedTab: Tab = .upload /// 初始化传输管理页。 init(manager: CloudTransferManager = .shared) { self.manager = manager 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 segmentedControl.selectedSegmentIndex = selectedTab.rawValue tableView.backgroundColor = AppColor.pageBackground tableView.separatorStyle = .none tableView.dataSource = self tableView.delegate = self tableView.register(CloudTransferCell.self, forCellReuseIdentifier: CloudTransferCell.reuseIdentifier) view.addSubview(segmentedControl) view.addSubview(tableView) } override func setupConstraints() { segmentedControl.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide).offset(AppSpacing.sm) make.leading.trailing.equalToSuperview().inset(AppSpacing.md) make.height.equalTo(36) } tableView.snp.makeConstraints { make in make.top.equalTo(segmentedControl.snp.bottom).offset(AppSpacing.sm) make.leading.trailing.bottom.equalToSuperview() } } override func bindActions() { segmentedControl.addTarget(self, action: #selector(tabChanged), for: .valueChanged) manager.onTasksChange = { [weak self] in Task { @MainActor in self?.tableView.reloadData() } } } private var visibleTasks: [CloudTransferTask] { selectedTab == .upload ? manager.uploadTasks : manager.downloadTasks } @objc private func tabChanged() { selectedTab = Tab(rawValue: segmentedControl.selectedSegmentIndex) ?? .upload tableView.reloadData() } } extension CloudDriveTransferViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { visibleTasks.count } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 112 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: CloudTransferCell.reuseIdentifier, for: indexPath) as! CloudTransferCell let task = visibleTasks[indexPath.row] cell.apply(task: task) cell.onPrimaryAction = { [weak self] in guard let self else { return } if task.kind == .upload { task.status == .uploading ? self.manager.pauseUpload(id: task.id) : self.manager.resumeUpload(id: task.id) } else { task.status == .downloading ? self.manager.pauseDownload(id: task.id) : self.manager.resumeDownload(id: task.id) } } cell.onCancel = { [weak self] in guard let self else { return } task.kind == .upload ? self.manager.cancelUpload(id: task.id) : self.manager.cancelDownload(id: task.id) } return cell } } /// 云盘传输任务 Cell。 private final class CloudTransferCell: UITableViewCell { static let reuseIdentifier = "CloudTransferCell" var onPrimaryAction: (() -> Void)? var onCancel: (() -> Void)? private let cardView = UIView() private let iconView = UIImageView() private let titleLabel = UILabel() private let statusLabel = UILabel() private let primaryButton = UIButton(type: .system) private let cancelButton = UIButton(type: .system) private let progressView = UIProgressView(progressViewStyle: .default) private let percentLabel = UILabel() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupUI() setupConstraints() } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func prepareForReuse() { super.prepareForReuse() onPrimaryAction = nil onCancel = nil } func apply(task: CloudTransferTask) { iconView.image = CloudDriveAsset.image( named: task.fileType == 1 ? "icon_cloud_storage_file_type_video" : "icon_cloud_storage_file_type_photo", fallbackSystemName: task.fileType == 1 ? "video.fill" : "photo.fill" ) titleLabel.text = task.fileName statusLabel.text = "\(task.status.title) \(CloudDriveFormatter.fileSize(task.fileSize))" progressView.progress = Float(task.progress) / 100 percentLabel.text = "\(task.progress)%" let isRunning = task.status == .uploading || task.status == .downloading primaryButton.setImage(UIImage(systemName: isRunning ? "pause.fill" : "play.fill"), for: .normal) primaryButton.isHidden = task.status == .completed } private func setupUI() { backgroundColor = .clear selectionStyle = .none cardView.backgroundColor = .white cardView.layer.cornerRadius = AppRadius.sm cardView.clipsToBounds = true iconView.tintColor = AppColor.primary iconView.contentMode = .scaleAspectFit titleLabel.font = .app(.body) titleLabel.textColor = AppColor.textPrimary titleLabel.lineBreakMode = .byTruncatingMiddle statusLabel.font = .app(.caption) statusLabel.textColor = AppColor.textTertiary progressView.progressTintColor = AppColor.primary percentLabel.font = .app(.caption) percentLabel.textColor = AppColor.textSecondary percentLabel.textAlignment = .right primaryButton.tintColor = AppColor.primary cancelButton.tintColor = AppColor.textSecondary cancelButton.setImage(UIImage(systemName: "xmark"), for: .normal) primaryButton.addTarget(self, action: #selector(primaryTapped), for: .touchUpInside) cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside) contentView.addSubview(cardView) cardView.addSubview(iconView) cardView.addSubview(titleLabel) cardView.addSubview(statusLabel) cardView.addSubview(primaryButton) cardView.addSubview(cancelButton) cardView.addSubview(progressView) cardView.addSubview(percentLabel) } private func setupConstraints() { cardView.snp.makeConstraints { make in make.top.bottom.equalToSuperview().inset(AppSpacing.xs) make.leading.trailing.equalToSuperview().inset(AppSpacing.md) } iconView.snp.makeConstraints { make in make.leading.top.equalToSuperview().offset(AppSpacing.sm) make.width.height.equalTo(48) } cancelButton.snp.makeConstraints { make in make.trailing.top.equalToSuperview().inset(AppSpacing.xs) make.width.height.equalTo(36) } primaryButton.snp.makeConstraints { make in make.trailing.equalTo(cancelButton.snp.leading) make.centerY.equalTo(cancelButton) make.width.height.equalTo(36) } titleLabel.snp.makeConstraints { make in make.leading.equalTo(iconView.snp.trailing).offset(AppSpacing.sm) make.top.equalTo(iconView) make.trailing.equalTo(primaryButton.snp.leading).offset(-AppSpacing.xs) } statusLabel.snp.makeConstraints { make in make.leading.trailing.equalTo(titleLabel) make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.xxs) } progressView.snp.makeConstraints { make in make.leading.equalTo(iconView) make.trailing.equalToSuperview().inset(AppSpacing.sm) make.top.equalTo(iconView.snp.bottom).offset(AppSpacing.sm) } percentLabel.snp.makeConstraints { make in make.top.equalTo(progressView.snp.bottom).offset(AppSpacing.xxs) make.trailing.equalTo(progressView) } } @objc private func primaryTapped() { onPrimaryAction?() } @objc private func cancelTapped() { onCancel?() } }