Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5704531f3a | ||
|
|
87819780c9 |
@@ -197,7 +197,7 @@ protocol TravelAlbumPreviewActionHandling: AnyObject {
|
|||||||
/// 新接口接入前的占位操作实现,不修改任何业务数据。
|
/// 新接口接入前的占位操作实现,不修改任何业务数据。
|
||||||
final class PlaceholderTravelAlbumPreviewActionHandler: TravelAlbumPreviewActionHandling {
|
final class PlaceholderTravelAlbumPreviewActionHandler: TravelAlbumPreviewActionHandling {
|
||||||
func requestAIRetouch(originalMaterialId: Int) async -> TravelAlbumPreviewActionResult {
|
func requestAIRetouch(originalMaterialId: Int) async -> TravelAlbumPreviewActionResult {
|
||||||
.unavailable("AI修复功能待接口接入")
|
.unavailable("AI修图功能待接口接入")
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteProject(originalMaterialId: Int) async -> TravelAlbumPreviewActionResult {
|
func deleteProject(originalMaterialId: Int) async -> TravelAlbumPreviewActionResult {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ final class TravelAlbumPreviewActionHandler: TravelAlbumPreviewActionHandling {
|
|||||||
self.api = api
|
self.api = api
|
||||||
}
|
}
|
||||||
|
|
||||||
/// AI 修复仍由相册选择态的模板流程发起。
|
/// AI 修图仍由相册选择态的模板流程发起。
|
||||||
func requestAIRetouch(originalMaterialId: Int) async -> TravelAlbumPreviewActionResult {
|
func requestAIRetouch(originalMaterialId: Int) async -> TravelAlbumPreviewActionResult {
|
||||||
.unavailable("请在相册中选择照片后使用AI修图")
|
.unavailable("请在相册中选择照片后使用AI修图")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ final class TravelAlbumDetailViewController: BaseViewController {
|
|||||||
private let purchasedTabButton = UIButton(type: .system)
|
private let purchasedTabButton = UIButton(type: .system)
|
||||||
private let sortButton = UIButton(type: .system)
|
private let sortButton = UIButton(type: .system)
|
||||||
private let selectButton = UIButton(type: .system)
|
private let selectButton = UIButton(type: .system)
|
||||||
|
private let refreshControl = UIRefreshControl()
|
||||||
private var collectionView: UICollectionView!
|
private var collectionView: UICollectionView!
|
||||||
private var dataSource: UICollectionViewDiffableDataSource<Int, TravelAlbumMaterial>!
|
private var dataSource: UICollectionViewDiffableDataSource<Int, TravelAlbumMaterial>!
|
||||||
private let bottomBar = UIView()
|
private let bottomBar = UIView()
|
||||||
@@ -97,6 +98,9 @@ final class TravelAlbumDetailViewController: BaseViewController {
|
|||||||
collectionView.alwaysBounceVertical = true
|
collectionView.alwaysBounceVertical = true
|
||||||
collectionView.showsVerticalScrollIndicator = false
|
collectionView.showsVerticalScrollIndicator = false
|
||||||
collectionView.delegate = self
|
collectionView.delegate = self
|
||||||
|
refreshControl.tintColor = TravelAlbumDetailStyle.primary
|
||||||
|
refreshControl.accessibilityIdentifier = "travelAlbum.refreshControl"
|
||||||
|
collectionView.refreshControl = refreshControl
|
||||||
collectionView.register(
|
collectionView.register(
|
||||||
TravelAlbumMaterialCell.self,
|
TravelAlbumMaterialCell.self,
|
||||||
forCellWithReuseIdentifier: TravelAlbumMaterialCell.reuseIdentifier
|
forCellWithReuseIdentifier: TravelAlbumMaterialCell.reuseIdentifier
|
||||||
@@ -225,6 +229,7 @@ final class TravelAlbumDetailViewController: BaseViewController {
|
|||||||
aiRetouchButton.addTarget(self, action: #selector(aiRetouchTapped), for: .touchUpInside)
|
aiRetouchButton.addTarget(self, action: #selector(aiRetouchTapped), for: .touchUpInside)
|
||||||
deleteSelectedButton.addTarget(self, action: #selector(deleteSelectedTapped), for: .touchUpInside)
|
deleteSelectedButton.addTarget(self, action: #selector(deleteSelectedTapped), for: .touchUpInside)
|
||||||
uploadButton.addTarget(self, action: #selector(uploadTapped), for: .touchUpInside)
|
uploadButton.addTarget(self, action: #selector(uploadTapped), for: .touchUpInside)
|
||||||
|
refreshControl.addTarget(self, action: #selector(refreshPulled), for: .valueChanged)
|
||||||
viewModel.onStateChange = { [weak self] in
|
viewModel.onStateChange = { [weak self] in
|
||||||
Task { @MainActor in self?.applyViewModel() }
|
Task { @MainActor in self?.applyViewModel() }
|
||||||
}
|
}
|
||||||
@@ -291,13 +296,20 @@ final class TravelAlbumDetailViewController: BaseViewController {
|
|||||||
}
|
}
|
||||||
dataSource.apply(snapshot, animatingDifferences: true)
|
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()
|
showLoading()
|
||||||
} else {
|
} else {
|
||||||
hideLoading()
|
hideLoading()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func refreshPulled() {
|
||||||
|
Task { await viewModel.refreshAll(api: api) }
|
||||||
|
}
|
||||||
|
|
||||||
private func configureTabButtonBase(_ button: UIButton) {
|
private func configureTabButtonBase(_ button: UIButton) {
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 13, weight: .medium)
|
button.titleLabel?.font = .systemFont(ofSize: 13, weight: .medium)
|
||||||
button.accessibilityTraits.insert(.button)
|
button.accessibilityTraits.insert(.button)
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ final class TravelAlbumPhotoPreviewViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func configureActions() {
|
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(
|
let deleteConfiguration = makeActionButton(
|
||||||
title: "删除",
|
title: "删除",
|
||||||
systemName: "trash",
|
systemName: "trash",
|
||||||
|
|||||||
@@ -7,9 +7,50 @@ import UIKit
|
|||||||
import XCTest
|
import XCTest
|
||||||
@testable import suixinkan
|
@testable import suixinkan
|
||||||
|
|
||||||
/// 相册管理页选择态底部操作区测试。
|
/// 相册管理页刷新、预览与选择态交互测试。
|
||||||
@MainActor
|
@MainActor
|
||||||
final class TravelAlbumDetailViewControllerTests: XCTestCase {
|
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 {
|
func testPreviewDeleteHandlerCallsMaterialAPIAndPreservesServerFailure() async {
|
||||||
let api = TravelAlbumMockAPI()
|
let api = TravelAlbumMockAPI()
|
||||||
let handler = TravelAlbumPreviewActionHandler(api: api)
|
let handler = TravelAlbumPreviewActionHandler(api: api)
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ final class TravelAlbumModelsTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
aiResult,
|
aiResult,
|
||||||
.unavailable("AI修复功能待接口接入")
|
.unavailable("AI修图功能待接口接入")
|
||||||
)
|
)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
deleteResult,
|
deleteResult,
|
||||||
|
|||||||
Reference in New Issue
Block a user