同步相册云盘界面并完善相关交互

This commit is contained in:
2026-07-10 17:15:15 +08:00
parent ab5220e460
commit ceca780ab3
13 changed files with 2269 additions and 532 deletions

View File

@ -7,15 +7,20 @@ import Kingfisher
import SnapKit
import UIKit
/// Cell Android
/// 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 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()
@ -47,126 +52,151 @@ final class CloudDriveFileCell: UICollectionViewCell {
///
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
detailLabel.text = mode == .grid ? CloudDriveFormatter.typeName(file.type) : CloudDriveFormatter.fileDescription(file)
configureThumbnail(file)
gridConstraints.forEach { mode == .grid ? $0.activate() : $0.deactivate() }
listConstraints.forEach { mode == .list ? $0.activate() : $0.deactivate() }
contentView.layer.cornerRadius = mode == .grid ? AppRadius.lg : 0
contentView.layer.borderWidth = mode == .grid ? 1 : 0
contentView.backgroundColor = mode == .grid ? .white : .clear
thumbnailView.layer.cornerRadius = mode == .grid ? AppRadius.lg : 0
nameLabel.textAlignment = mode == .grid ? .left : .left
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
thumbnailView.backgroundColor = AppColor.warningBackground
folderIconView.tintColor = AppColor.warning
folderIconView.contentMode = .scaleAspectFit
folderIconView.isHidden = true
playIconView.tintColor = .white
playIconView.contentMode = .scaleAspectFit
playIconView.isHidden = true
extensionLabel.font = .app(.caption)
extensionLabel.font = .systemFont(ofSize: 12, weight: .bold)
extensionLabel.textColor = .white
extensionLabel.backgroundColor = UIColor.black.withAlphaComponent(0.5)
extensionLabel.layer.cornerRadius = AppRadius.xs
extensionLabel.layer.cornerRadius = 4
extensionLabel.clipsToBounds = true
extensionLabel.isHidden = true
nameLabel.font = .app(.body)
nameLabel.textColor = AppColor.textPrimary
nameLabel.textColor = UIColor(hex: 0x333333)
nameLabel.numberOfLines = 1
detailLabel.font = .app(.caption)
detailLabel.textColor = UIColor(hex: 0x4B5563)
detailLabel.font = .systemFont(ofSize: 12)
detailLabel.numberOfLines = 1
moreButton.setImage(CloudDriveAsset.image(named: "icon_cloud_storage_file_details", fallbackSystemName: "ellipsis"), for: .normal)
moreButton.tintColor = AppColor.textSecondary
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 = "文件操作"
contentView.addSubview(thumbnailView)
contentView.addSubview(folderIconView)
contentView.addSubview(playIconView)
contentView.addSubview(extensionLabel)
contentView.addSubview(nameLabel)
contentView.addSubview(detailLabel)
contentView.addSubview(moreButton)
[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).multipliedBy(0.56).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(42)
make.width.height.equalTo(48)
}
playIconView.snp.makeConstraints { make in
make.center.equalTo(thumbnailView)
make.width.height.equalTo(28)
make.width.height.equalTo(24)
}
extensionLabel.snp.makeConstraints { make in
make.top.leading.equalTo(thumbnailView).offset(AppSpacing.xs)
make.top.leading.equalTo(thumbnailView).offset(8)
}
nameLabel.snp.makeConstraints { make in
gridConstraints.append(make.top.equalTo(thumbnailView.snp.bottom).offset(AppSpacing.xs).constraint)
gridConstraints.append(make.leading.equalToSuperview().offset(AppSpacing.sm).constraint)
listConstraints.append(make.top.equalToSuperview().offset(AppSpacing.sm).constraint)
listConstraints.append(make.leading.equalTo(thumbnailView.snp.trailing).offset(AppSpacing.sm).constraint)
make.trailing.equalTo(moreButton.snp.leading).offset(-AppSpacing.xs)
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(AppSpacing.xxs)
make.leading.equalTo(nameLabel)
make.trailing.equalTo(nameLabel)
make.top.equalTo(nameLabel.snp.bottom).offset(2)
make.leading.trailing.equalTo(nameLabel)
}
moreButton.snp.makeConstraints { make in
make.trailing.equalToSuperview().inset(AppSpacing.xs)
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(32)
make.width.height.equalTo(28)
}
listConstraints.forEach { $0.deactivate() }
}
private func configureThumbnail(_ file: CloudFile) {
if file.isFolder {
thumbnailView.image = nil
thumbnailView.backgroundColor = AppColor.warningBackground
folderIconView.isHidden = false
extensionLabel.isHidden = true
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
}
folderIconView.isHidden = true
thumbnailView.backgroundColor = AppColor.inputBackground
let preview = file.isVideo ? file.coverUrl : file.fileUrl
if let url = URL(string: preview), !preview.isEmpty {
let placeholder = CloudDriveAsset.image(
named: file.isVideo ? "icon_cloud_storage_file_type_video" : "icon_cloud_storage_file_type_photo",
fallbackSystemName: file.isVideo ? "video" : "photo"
)
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 = CloudDriveAsset.image(
named: file.isVideo ? "icon_cloud_storage_file_type_video" : "icon_cloud_storage_file_type_photo",
fallbackSystemName: file.isVideo ? "video" : "photo"
)
thumbnailView.tintColor = AppColor.textTertiary
thumbnailView.image = placeholder
}
playIconView.isHidden = !file.isVideo
extensionLabel.text = CloudDriveFormatter.fileExtension(file.fileUrl.isEmpty ? file.name : file.fileUrl)
extensionLabel.isHidden = false
extensionLabel.isHidden = extensionLabel.text?.isEmpty ?? true
}
@objc private func moreTapped() {
@ -174,16 +204,19 @@ final class CloudDriveFileCell: UICollectionViewCell {
}
}
///
/// Android
private final class CloudDrivePaddingLabel: UILabel {
var insets = UIEdgeInsets(top: 3, left: 5, bottom: 3, right: 5)
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + insets.left + insets.right, height: size.height + insets.top + insets.bottom)
}
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
)
}
}