// // CloudDriveFileCell.swift // suixinkan // import Kingfisher import SnapKit import UIKit /// 云盘文件 Cell,严格对齐 Android 宫格与列表两种样式。 final class CloudDriveFileCell: UICollectionViewCell { static let reuseIdentifier = "CloudDriveFileCell" /// 点击文件操作入口时回调。 var onDetails: (() -> Void)? private let thumbnailView = UIImageView() private let folderIconView = UIImageView( image: CloudDriveAsset.image(named: "icon_cloud_storage_folder_type", fallbackSystemName: "folder.fill") ) private let playIconView = UIImageView( image: CloudDriveAsset.image(named: "icon_cloud_storage_file_play", fallbackSystemName: "play.circle.fill") ) private let extensionLabel = CloudDrivePaddingLabel() private let nameLabel = UILabel() private let detailLabel = UILabel() private let moreButton = UIButton(type: .system) private var gridConstraints: [Constraint] = [] private var listConstraints: [Constraint] = [] override init(frame: CGRect) { super.init(frame: frame) setupUI() setupConstraints() } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func prepareForReuse() { super.prepareForReuse() thumbnailView.kf.cancelDownloadTask() thumbnailView.image = nil folderIconView.isHidden = true playIconView.isHidden = true extensionLabel.isHidden = true onDetails = nil } /// 应用云盘文件数据。 func apply(file: CloudFile, mode: CloudDriveDisplayMode) { let isGrid = mode == .grid gridConstraints.forEach { isGrid ? $0.activate() : $0.deactivate() } listConstraints.forEach { isGrid ? $0.deactivate() : $0.activate() } nameLabel.text = file.name nameLabel.font = .systemFont(ofSize: 15, weight: isGrid ? .semibold : .regular) nameLabel.lineBreakMode = isGrid ? .byTruncatingTail : .byTruncatingMiddle detailLabel.text = isGrid ? CloudDriveFormatter.typeName(file.type) : CloudDriveFormatter.fileDescription(file) detailLabel.textColor = isGrid ? UIColor(hex: 0x4B5563) : UIColor(hex: 0x7B8EAA) contentView.layer.cornerRadius = isGrid ? 12 : 0 contentView.layer.borderWidth = isGrid ? 1 : 0 contentView.backgroundColor = isGrid ? .white : .clear thumbnailView.layer.cornerRadius = isGrid ? 12 : 0 thumbnailView.clipsToBounds = isGrid configureThumbnail(file, mode: mode) } private func setupUI() { backgroundColor = .clear contentView.clipsToBounds = true contentView.layer.borderColor = UIColor(hex: 0xF4F4F4).cgColor thumbnailView.contentMode = .scaleAspectFill thumbnailView.clipsToBounds = true folderIconView.contentMode = .scaleAspectFit folderIconView.isHidden = true playIconView.contentMode = .scaleAspectFit playIconView.isHidden = true extensionLabel.font = .systemFont(ofSize: 12, weight: .bold) extensionLabel.textColor = .white extensionLabel.backgroundColor = UIColor.black.withAlphaComponent(0.5) extensionLabel.layer.cornerRadius = 4 extensionLabel.clipsToBounds = true extensionLabel.isHidden = true nameLabel.textColor = UIColor(hex: 0x333333) nameLabel.numberOfLines = 1 detailLabel.font = .systemFont(ofSize: 12) detailLabel.numberOfLines = 1 let detailsImage = CloudDriveAsset.resizedImage( named: "icon_cloud_storage_file_item_details", fallbackSystemName: "ellipsis", size: CGSize(width: 16, height: 16) ) moreButton.setImage(detailsImage, for: .normal) moreButton.imageView?.contentMode = .scaleAspectFit moreButton.addTarget(self, action: #selector(moreTapped), for: .touchUpInside) moreButton.accessibilityLabel = "文件操作" [thumbnailView, folderIconView, playIconView, extensionLabel, nameLabel, detailLabel, moreButton] .forEach(contentView.addSubview) } private func setupConstraints() { thumbnailView.snp.makeConstraints { make in gridConstraints.append(make.top.leading.trailing.equalToSuperview().constraint) gridConstraints.append(make.height.equalTo(thumbnailView.snp.width).dividedBy(1.77).constraint) listConstraints.append(make.leading.centerY.equalToSuperview().constraint) listConstraints.append(make.width.height.equalTo(48).constraint) } folderIconView.snp.makeConstraints { make in make.center.equalTo(thumbnailView) make.width.height.equalTo(48) } playIconView.snp.makeConstraints { make in make.center.equalTo(thumbnailView) make.width.height.equalTo(24) } extensionLabel.snp.makeConstraints { make in make.top.leading.equalTo(thumbnailView).offset(8) } nameLabel.snp.makeConstraints { make in gridConstraints.append(make.top.equalTo(thumbnailView.snp.bottom).offset(8).constraint) gridConstraints.append(make.leading.equalToSuperview().offset(12).constraint) listConstraints.append(make.top.equalToSuperview().offset(12).constraint) listConstraints.append(make.leading.equalTo(thumbnailView.snp.trailing).offset(12).constraint) make.trailing.equalTo(moreButton.snp.leading).offset(-4) } detailLabel.snp.makeConstraints { make in make.top.equalTo(nameLabel.snp.bottom).offset(2) make.leading.trailing.equalTo(nameLabel) } moreButton.snp.makeConstraints { make in gridConstraints.append(make.trailing.equalToSuperview().inset(10).constraint) listConstraints.append(make.trailing.equalToSuperview().constraint) make.centerY.equalTo(nameLabel.snp.bottom).offset(4) make.width.height.equalTo(28) } listConstraints.forEach { $0.deactivate() } } private func configureThumbnail(_ file: CloudFile, mode: CloudDriveDisplayMode) { thumbnailView.kf.cancelDownloadTask() folderIconView.isHidden = true playIconView.isHidden = true extensionLabel.isHidden = true if mode == .list { thumbnailView.backgroundColor = .clear thumbnailView.contentMode = .scaleAspectFit let assetName: String let fallbackName: String if file.isFolder { assetName = "icon_cloud_storage_file_type_folder" fallbackName = "folder.fill" } else if file.isVideo { assetName = "icon_cloud_storage_file_type_video" fallbackName = "video.fill" } else { assetName = "icon_cloud_storage_file_type_photo" fallbackName = "photo.fill" } thumbnailView.image = CloudDriveAsset.image(named: assetName, fallbackSystemName: fallbackName) return } thumbnailView.contentMode = .scaleAspectFill if file.isFolder { thumbnailView.image = nil thumbnailView.backgroundColor = UIColor(hex: 0xFFF0E2) folderIconView.isHidden = false return } thumbnailView.backgroundColor = UIColor(hex: 0xF4F4F4) let previewURL = file.isVideo ? file.coverUrl : file.fileUrl let placeholder = CloudDriveAsset.image( named: file.isVideo ? "icon_cloud_storage_file_type_video" : "icon_cloud_storage_file_type_photo", fallbackSystemName: file.isVideo ? "video" : "photo" ) if let url = URL(string: previewURL), !previewURL.isEmpty { thumbnailView.kf.setImage(with: url, placeholder: placeholder) } else { thumbnailView.image = placeholder } playIconView.isHidden = !file.isVideo extensionLabel.text = CloudDriveFormatter.fileExtension(file.fileUrl.isEmpty ? file.name : file.fileUrl) extensionLabel.isHidden = extensionLabel.text?.isEmpty ?? true } @objc private func moreTapped() { onDetails?() } } /// 云盘文件扩展名标签,提供 Android 对齐的四边内边距。 private final class CloudDrivePaddingLabel: UILabel { private let insets = UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 4) override func drawText(in rect: CGRect) { super.drawText(in: rect.inset(by: insets)) } override var intrinsicContentSize: CGSize { let size = super.intrinsicContentSize return CGSize( width: size.width + insets.left + insets.right, height: size.height + insets.top + insets.bottom ) } }