2 Commits
Author SHA1 Message Date
lujiuyin 5704531f3a fix(album): rename AI retouch action label 2026-08-10 17:56:29 +08:00
lujiuyin 87819780c9 feat(album): add pull-to-refresh to management grid 2026-08-10 17:46:25 +08:00
6 changed files with 59 additions and 6 deletions
@@ -197,7 +197,7 @@ protocol TravelAlbumPreviewActionHandling: AnyObject {
/// 新接口接入前的占位操作实现,不修改任何业务数据。
final class PlaceholderTravelAlbumPreviewActionHandler: TravelAlbumPreviewActionHandling {
func requestAIRetouch(originalMaterialId: Int) async -> TravelAlbumPreviewActionResult {
.unavailable("AI修复功能待接口接入")
.unavailable("AI修图功能待接口接入")
}
func deleteProject(originalMaterialId: Int) async -> TravelAlbumPreviewActionResult {
@@ -14,7 +14,7 @@ final class TravelAlbumPreviewActionHandler: TravelAlbumPreviewActionHandling {
self.api = api
}
/// AI 修复仍由相册选择态的模板流程发起。
/// AI 修图仍由相册选择态的模板流程发起。
func requestAIRetouch(originalMaterialId: Int) async -> TravelAlbumPreviewActionResult {
.unavailable("请在相册中选择照片后使用AI修图")
}
@@ -22,6 +22,7 @@ final class TravelAlbumDetailViewController: BaseViewController {
private let purchasedTabButton = UIButton(type: .system)
private let sortButton = UIButton(type: .system)
private let selectButton = UIButton(type: .system)
private let refreshControl = UIRefreshControl()
private var collectionView: UICollectionView!
private var dataSource: UICollectionViewDiffableDataSource<Int, TravelAlbumMaterial>!
private let bottomBar = UIView()
@@ -97,6 +98,9 @@ final class TravelAlbumDetailViewController: BaseViewController {
collectionView.alwaysBounceVertical = true
collectionView.showsVerticalScrollIndicator = false
collectionView.delegate = self
refreshControl.tintColor = TravelAlbumDetailStyle.primary
refreshControl.accessibilityIdentifier = "travelAlbum.refreshControl"
collectionView.refreshControl = refreshControl
collectionView.register(
TravelAlbumMaterialCell.self,
forCellWithReuseIdentifier: TravelAlbumMaterialCell.reuseIdentifier
@@ -225,6 +229,7 @@ final class TravelAlbumDetailViewController: BaseViewController {
aiRetouchButton.addTarget(self, action: #selector(aiRetouchTapped), for: .touchUpInside)
deleteSelectedButton.addTarget(self, action: #selector(deleteSelectedTapped), for: .touchUpInside)
uploadButton.addTarget(self, action: #selector(uploadTapped), for: .touchUpInside)
refreshControl.addTarget(self, action: #selector(refreshPulled), for: .valueChanged)
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
@@ -291,13 +296,20 @@ final class TravelAlbumDetailViewController: BaseViewController {
}
dataSource.apply(snapshot, animatingDifferences: true)
if viewModel.isLoading && viewModel.album == nil {
if !viewModel.isRefreshing {
refreshControl.endRefreshing()
}
if viewModel.isLoading && viewModel.album == nil && !refreshControl.isRefreshing {
showLoading()
} else {
hideLoading()
}
}
@objc private func refreshPulled() {
Task { await viewModel.refreshAll(api: api) }
}
private func configureTabButtonBase(_ button: UIButton) {
button.titleLabel?.font = .systemFont(ofSize: 13, weight: .medium)
button.accessibilityTraits.insert(.button)
@@ -193,7 +193,7 @@ final class TravelAlbumPhotoPreviewViewController: UIViewController {
}
private func configureActions() {
let aiButton = makeActionButton(title: "AI修复", systemName: "wand.and.stars", color: UIColor(hex: 0x60A5FA))
let aiButton = makeActionButton(title: "AI修图", systemName: "wand.and.stars", color: UIColor(hex: 0x60A5FA))
let deleteConfiguration = makeActionButton(
title: "删除",
systemName: "trash",
@@ -7,9 +7,50 @@ import UIKit
import XCTest
@testable import suixinkan
/// 相册管理页选择态底部操作区测试。
/// 相册管理页刷新、预览与选择态交互测试。
@MainActor
final class TravelAlbumDetailViewControllerTests: XCTestCase {
func testPullToRefreshReloadsGridAndEndsRefreshing() async throws {
let api = TravelAlbumMockAPI()
api.infoResponse = TravelAlbum(id: 2, name: "测试相册")
api.materialListResponses = [
TravelAlbumListResponse(total: 1, list: []),
TravelAlbumListResponse(total: 0, list: []),
TravelAlbumListResponse(total: 1, list: [TravelAlbumMaterial(id: 1)]),
TravelAlbumListResponse(total: 2, list: []),
TravelAlbumListResponse(total: 1, list: []),
TravelAlbumListResponse(
total: 2,
list: [TravelAlbumMaterial(id: 1), TravelAlbumMaterial(id: 2)]
),
]
let controller = TravelAlbumDetailViewController(albumId: 2, api: api)
let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 390, height: 844))
window.rootViewController = controller
window.makeKeyAndVisible()
defer { window.isHidden = true }
controller.loadViewIfNeeded()
controller.view.layoutIfNeeded()
let collectionView = try XCTUnwrap(
controller.view.findSubview { $0 is UICollectionView } as? UICollectionView
)
let refreshControl = try XCTUnwrap(collectionView.refreshControl)
await waitUntil { api.materialRequests.count == 3 }
refreshControl.beginRefreshing()
XCTAssertTrue(refreshControl.isRefreshing)
refreshControl.sendActions(for: .valueChanged)
await waitUntil {
api.materialRequests.count == 6
&& !refreshControl.isRefreshing
&& collectionView.numberOfItems(inSection: 0) == 2
}
XCTAssertEqual(refreshControl.accessibilityIdentifier, "travelAlbum.refreshControl")
XCTAssertEqual(collectionView.numberOfItems(inSection: 0), 2)
XCTAssertFalse(refreshControl.isRefreshing)
}
func testPreviewDeleteHandlerCallsMaterialAPIAndPreservesServerFailure() async {
let api = TravelAlbumMockAPI()
let handler = TravelAlbumPreviewActionHandler(api: api)
+1 -1
View File
@@ -169,7 +169,7 @@ final class TravelAlbumModelsTests: XCTestCase {
XCTAssertEqual(
aiResult,
.unavailable("AI修复功能待接口接入")
.unavailable("AI修图功能待接口接入")
)
XCTAssertEqual(
deleteResult,