完善任务流程、键盘适配与页面交互

This commit is contained in:
2026-07-10 15:56:15 +08:00
parent f88a85a807
commit ab5220e460
189 changed files with 16779 additions and 1038 deletions

View File

@ -18,10 +18,16 @@ final class CloudStoragePickForTaskViewController: BaseViewController {
private let searchField = UITextField()
private let pathScrollView = UIScrollView()
private let pathStack = UIStackView()
private let filterBar = UIStackView()
private let sortButton = UIButton(type: .system)
private let typeButton = UIButton(type: .system)
private let displayModeButton = UIButton(type: .system)
private let refreshControl = UIRefreshControl()
private var collectionView: UICollectionView!
private var dataSource: UICollectionViewDiffableDataSource<Int, Int>!
private let confirmButton = AppButton(title: "确定")
private let bottomBar = UIView()
private var isGridMode = true
///
init(importedFileIDs: Set<Int> = [], allowedFileType: Int? = nil) {
@ -35,7 +41,7 @@ final class CloudStoragePickForTaskViewController: BaseViewController {
}
override func setupNavigationBar() {
title = "云盘导入"
title = "相册云盘"
}
override func setupUI() {
@ -54,10 +60,26 @@ final class CloudStoragePickForTaskViewController: BaseViewController {
make.height.equalToSuperview()
}
configureMenuButton(sortButton, imageName: "arrow.up.arrow.down")
configureMenuButton(typeButton, imageName: "line.3.horizontal.decrease")
displayModeButton.tintColor = AppColor.textSecondary
displayModeButton.backgroundColor = AppColor.inputBackground
displayModeButton.layer.cornerRadius = AppRadius.xs
displayModeButton.addTarget(self, action: #selector(displayModeTapped), for: .touchUpInside)
filterBar.axis = .horizontal
filterBar.spacing = AppSpacing.sm
filterBar.distribution = .fill
filterBar.addArrangedSubview(sortButton)
filterBar.addArrangedSubview(typeButton)
filterBar.addArrangedSubview(displayModeButton)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: makeLayout())
collectionView.backgroundColor = AppColor.pageBackground
collectionView.register(CloudPickCell.self, forCellWithReuseIdentifier: CloudPickCell.reuseIdentifier)
collectionView.delegate = self
collectionView.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(refreshTriggered), for: .valueChanged)
dataSource = UICollectionViewDiffableDataSource<Int, Int>(collectionView: collectionView) {
[weak self] collectionView, indexPath, fileID in
@ -69,7 +91,8 @@ final class CloudStoragePickForTaskViewController: BaseViewController {
cell.apply(
file: file,
isSelected: self?.viewModel.isSelected(fileID) == true,
isImported: self?.viewModel.isImported(fileID) == true
isImported: self?.viewModel.isImported(fileID) == true,
isGridMode: self?.isGridMode == true
)
}
return cell
@ -78,6 +101,7 @@ final class CloudStoragePickForTaskViewController: BaseViewController {
bottomBar.backgroundColor = .white
view.addSubview(searchField)
view.addSubview(pathScrollView)
view.addSubview(filterBar)
view.addSubview(collectionView)
view.addSubview(bottomBar)
bottomBar.addSubview(confirmButton)
@ -94,6 +118,13 @@ final class CloudStoragePickForTaskViewController: BaseViewController {
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
make.height.equalTo(28)
}
filterBar.snp.makeConstraints { make in
make.top.equalTo(pathScrollView.snp.bottom).offset(AppSpacing.sm)
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
make.height.equalTo(36)
}
sortButton.snp.makeConstraints { make in make.width.equalTo(typeButton) }
displayModeButton.snp.makeConstraints { make in make.width.equalTo(36) }
bottomBar.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
}
@ -101,7 +132,7 @@ final class CloudStoragePickForTaskViewController: BaseViewController {
make.edges.equalToSuperview().inset(AppSpacing.md)
}
collectionView.snp.makeConstraints { make in
make.top.equalTo(pathScrollView.snp.bottom).offset(AppSpacing.sm)
make.top.equalTo(filterBar.snp.bottom).offset(AppSpacing.md)
make.leading.trailing.equalToSuperview()
make.bottom.equalTo(bottomBar.snp.top)
}
@ -125,15 +156,24 @@ final class CloudStoragePickForTaskViewController: BaseViewController {
@MainActor
private func applyViewModel() {
rebuildPathButtons()
rebuildFilterMenus()
var snapshot = NSDiffableDataSourceSnapshot<Int, Int>()
snapshot.appendSections([0])
snapshot.appendItems(viewModel.files.map(\.id))
let existingItems = Set(dataSource.snapshot().itemIdentifiers)
let itemsToReconfigure = snapshot.itemIdentifiers.filter(existingItems.contains)
snapshot.reconfigureItems(itemsToReconfigure)
dataSource.apply(snapshot, animatingDifferences: false)
let selectedCount = viewModel.selectedFileList.count
confirmButton.setTitle(selectedCount > 0 ? "确认选择(\(selectedCount))" : "确认选择", for: .normal)
if viewModel.isLoading && viewModel.files.isEmpty {
showLoading()
} else {
hideLoading()
}
if !viewModel.isRefreshing {
refreshControl.endRefreshing()
}
}
private func rebuildPathButtons() {
@ -160,26 +200,81 @@ final class CloudStoragePickForTaskViewController: BaseViewController {
}
@objc private func confirmTapped() {
guard !viewModel.selectedFileList.isEmpty else {
showToast("请至少选择一个文件")
return
}
onConfirmed?(viewModel.selectedFileList)
navigationController?.popViewController(animated: true)
}
@objc private func refreshTriggered() {
Task { await viewModel.refresh(api: taskAPI) }
}
@objc private func displayModeTapped() {
isGridMode.toggle()
collectionView.setCollectionViewLayout(makeLayout(), animated: true)
applyViewModel()
}
private func configureMenuButton(_ button: UIButton, imageName: String) {
button.tintColor = AppColor.textSecondary
button.backgroundColor = AppColor.inputBackground
button.layer.cornerRadius = AppRadius.xs
button.contentHorizontalAlignment = .leading
button.titleLabel?.font = .app(.body)
button.setTitleColor(AppColor.textPrimary, for: .normal)
button.setImage(UIImage(systemName: imageName), for: .normal)
button.semanticContentAttribute = .forceRightToLeft
button.showsMenuAsPrimaryAction = true
}
private func rebuildFilterMenus() {
let sortItems = [(1, "创建时间顺序"), (2, "创建时间倒序")]
sortButton.setTitle(sortItems.first(where: { $0.0 == viewModel.sortType })?.1 ?? "排序", for: .normal)
sortButton.menu = UIMenu(children: sortItems.map { value, title in
UIAction(title: title, state: value == viewModel.sortType ? .on : .off) { [weak self] _ in
guard let self else { return }
self.viewModel.updateSortType(value)
Task { await self.viewModel.refresh(api: self.taskAPI) }
}
})
let typeItems = [(0, "全部"), (2, "图片"), (1, "视频"), (99, "文件夹")]
typeButton.setTitle(typeItems.first(where: { $0.0 == viewModel.filterType })?.1 ?? "全部", for: .normal)
typeButton.menu = UIMenu(children: typeItems.map { value, title in
UIAction(title: title, state: value == viewModel.filterType ? .on : .off) { [weak self] _ in
guard let self else { return }
self.viewModel.updateFilterType(value)
Task { await self.viewModel.refresh(api: self.taskAPI) }
}
})
displayModeButton.setImage(
UIImage(systemName: isGridMode ? "list.bullet" : "square.grid.2x2"),
for: .normal
)
displayModeButton.accessibilityLabel = isGridMode ? "列表显示" : "宫格显示"
}
private func makeLayout() -> UICollectionViewCompositionalLayout {
UICollectionViewCompositionalLayout { _, environment in
let columns = 3
let isGridMode = isGridMode
return UICollectionViewCompositionalLayout { _, environment in
let columns = isGridMode ? 2 : 1
let spacing: CGFloat = 8
let inset = AppSpacing.md * 2
let availableWidth = environment.container.effectiveContentSize.width - inset
let totalSpacing = spacing * CGFloat(columns - 1)
let itemWidth = max(0, (availableWidth - totalSpacing) / CGFloat(columns))
let itemHeight = isGridMode ? itemWidth + 38 : 72
let itemSize = NSCollectionLayoutSize(
widthDimension: .absolute(itemWidth),
heightDimension: .absolute(itemWidth)
heightDimension: .absolute(itemHeight)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .absolute(itemWidth)
heightDimension: .absolute(itemHeight)
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
@ -297,12 +392,37 @@ private final class CloudPickCell: UICollectionViewCell {
contentView.alpha = 1
}
func apply(file: CloudFile, isSelected: Bool, isImported: Bool) {
func apply(file: CloudFile, isSelected: Bool, isImported: Bool, isGridMode: Bool) {
nameLabel.text = file.name
contentView.layer.borderWidth = isSelected ? 2 : 0
contentView.layer.borderColor = AppColor.primary.cgColor
badgeView.isHidden = !isSelected
if isGridMode {
imageView.snp.remakeConstraints { make in
make.top.leading.trailing.equalToSuperview()
make.height.equalTo(imageView.snp.width)
}
nameLabel.textAlignment = .center
nameLabel.numberOfLines = 2
nameLabel.snp.remakeConstraints { make in
make.top.equalTo(imageView.snp.bottom).offset(AppSpacing.xxs)
make.leading.trailing.bottom.equalToSuperview().inset(AppSpacing.xxs)
}
} else {
imageView.snp.remakeConstraints { make in
make.top.bottom.leading.equalToSuperview()
make.width.equalTo(imageView.snp.height)
}
nameLabel.textAlignment = .left
nameLabel.numberOfLines = 2
nameLabel.snp.remakeConstraints { make in
make.leading.equalTo(imageView.snp.trailing).offset(AppSpacing.sm)
make.trailing.equalToSuperview().inset(AppSpacing.sm)
make.centerY.equalToSuperview()
}
}
if file.isFolder {
contentView.alpha = 1
imageView.image = nil