feat: 新增相册自动修图与OTG状态预览
支持相册修图配置、模板选择和传输模式选择;上传后自动提交修图并展示状态角标及精修预览。补充接口文档与相关测试。
This commit is contained in:
@@ -260,7 +260,7 @@ final class TravelAlbumDetailViewControllerTests: XCTestCase {
|
||||
XCTAssertFalse(GlobalLoadingManager.shared.isShowing)
|
||||
}
|
||||
|
||||
func testAIJobEntryOnlyAppearsOnAlbumManagementNavigationBar() {
|
||||
func testAlbumManagementShowsAIJobEntryButAlbumEntryDoesNot() {
|
||||
let detail = TravelAlbumDetailViewController(albumId: 2, api: TravelAlbumMockAPI())
|
||||
detail.setupNavigationBar()
|
||||
let entry = TravelAlbumEntryViewController(api: TravelAlbumMockAPI())
|
||||
@@ -1394,6 +1394,111 @@ final class TravelAlbumDetailViewControllerTests: XCTestCase {
|
||||
XCTAssertFalse(hiddenCell.accessibilityLabel?.contains("修图状态:") == true)
|
||||
}
|
||||
|
||||
func testWiredTransferProgressUsesOnlyUploadStatusAndRealProgress() throws {
|
||||
let cell = WiredTransferPhotoCell(frame: CGRect(x: 0, y: 0, width: 300, height: 66))
|
||||
let progressView = try XCTUnwrap(cell.findSubview {
|
||||
$0.accessibilityIdentifier == "wiredTransfer.uploadProgress"
|
||||
} as? UIProgressView)
|
||||
let uploadStates: [TravelAlbumOTGUploadStatus] = [.pending, .transferring, .uploading, .uploaded, .failed]
|
||||
let retouchStates: [TravelAlbumAutoRetouchState] = [.none, .pendingSubmission, .submitting, .processing, .completed, .failed]
|
||||
|
||||
for uploadState in uploadStates {
|
||||
for retouchState in retouchStates {
|
||||
var item = makeWiredTransferPhotoItem(state: retouchState)
|
||||
item.status = uploadState
|
||||
item.progress = uploadState == .uploaded ? 100 : 37
|
||||
|
||||
cell.apply(item: item, selectionMode: false, selected: false)
|
||||
|
||||
XCTAssertEqual(progressView.isHidden, uploadState != .transferring && uploadState != .uploading)
|
||||
XCTAssertEqual(progressView.progress, Float(item.progress) / 100, accuracy: 0.001)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testWiredTransferRetouchBadgeShowsStateColorsAndKeepsCircularThumbnailLayout() throws {
|
||||
let cases: [(TravelAlbumAutoRetouchState, String, UIColor)] = [
|
||||
(.pendingSubmission, "修图中", UIColor(hex: 0x7C3AED)),
|
||||
(.submitting, "修图中", UIColor(hex: 0x7C3AED)),
|
||||
(.processing, "修图中", UIColor(hex: 0x7C3AED)),
|
||||
(.completed, "修图成功", UIColor(hex: 0x047857)),
|
||||
(.failed, "修图失败", AppColor.danger),
|
||||
]
|
||||
let cell = WiredTransferPhotoCell(frame: CGRect(x: 0, y: 0, width: 300, height: 66))
|
||||
let badge = try XCTUnwrap(cell.findSubview {
|
||||
$0.accessibilityIdentifier == "wiredTransfer.retouchBadge"
|
||||
} as? UILabel)
|
||||
let thumbnail = try XCTUnwrap(cell.findSubview {
|
||||
$0.accessibilityIdentifier == "wiredTransfer.thumbnail"
|
||||
})
|
||||
|
||||
for (state, expectedStatus, expectedColor) in cases {
|
||||
cell.apply(item: makeWiredTransferPhotoItem(state: state), selectionMode: false, selected: false)
|
||||
cell.layoutIfNeeded()
|
||||
|
||||
XCTAssertFalse(badge.isHidden)
|
||||
XCTAssertEqual(badge.text, "修")
|
||||
XCTAssertEqual(badge.backgroundColor?.travelAlbumTestHexRGB, expectedColor.travelAlbumTestHexRGB)
|
||||
XCTAssertEqual(badge.textColor, .white)
|
||||
XCTAssertEqual(badge.frame.size, CGSize(width: 16, height: 16))
|
||||
// Auto Layout 对齐 alignmentRect,系统符号占位图的 frame 可能带有留白。
|
||||
let badgeAlignmentRect = badge.alignmentRect(forFrame: badge.frame)
|
||||
let thumbnailAlignmentRect = thumbnail.alignmentRect(forFrame: thumbnail.frame)
|
||||
XCTAssertEqual(badgeAlignmentRect.minY, thumbnailAlignmentRect.minY + 2, accuracy: 0.1)
|
||||
XCTAssertEqual(badgeAlignmentRect.maxX, thumbnailAlignmentRect.maxX - 2, accuracy: 0.1)
|
||||
XCTAssertEqual(badge.layer.cornerRadius, 8)
|
||||
XCTAssertEqual(badge.accessibilityLabel, expectedStatus)
|
||||
XCTAssertTrue(cell.accessibilityLabel?.contains("修图状态:\(expectedStatus)") == true)
|
||||
}
|
||||
|
||||
cell.prepareForReuse()
|
||||
cell.apply(item: makeWiredTransferPhotoItem(state: .none), selectionMode: false, selected: false)
|
||||
XCTAssertTrue(badge.isHidden)
|
||||
XCTAssertNil(badge.accessibilityLabel)
|
||||
XCTAssertFalse(cell.accessibilityLabel?.contains("修图状态:") == true)
|
||||
}
|
||||
|
||||
func testWiredTransferUploadLabelIgnoresAllRetouchStates() throws {
|
||||
let cell = WiredTransferPhotoCell(frame: CGRect(x: 0, y: 0, width: 300, height: 66))
|
||||
let label = try XCTUnwrap(cell.findSubview {
|
||||
$0.accessibilityIdentifier == "wiredTransfer.uploadStatus"
|
||||
} as? UILabel)
|
||||
let successColor = UIColor(hex: 0x16A34A)
|
||||
let cases: [(TravelAlbumOTGUploadStatus, String, UIColor, UIColor)] = [
|
||||
(.pending, "待上传", AppColor.textSecondary, AppColor.pageBackground),
|
||||
(.transferring, "传输中", AppColor.primary, AppColor.primary.withAlphaComponent(0.12)),
|
||||
(.uploading, "上传中", AppColor.primary, AppColor.primary.withAlphaComponent(0.12)),
|
||||
(.uploaded, "已上传", successColor, successColor.withAlphaComponent(0.12)),
|
||||
(.failed, "上传失败", AppColor.danger, AppColor.danger.withAlphaComponent(0.12)),
|
||||
]
|
||||
let retouchStates: [TravelAlbumAutoRetouchState] = [
|
||||
.none, .pendingSubmission, .submitting, .processing, .completed, .failed,
|
||||
]
|
||||
|
||||
for (uploadStatus, text, foreground, background) in cases {
|
||||
for retouchState in retouchStates {
|
||||
var item = makeWiredTransferPhotoItem(state: retouchState)
|
||||
item.status = uploadStatus
|
||||
cell.apply(item: item, selectionMode: false, selected: false)
|
||||
|
||||
XCTAssertFalse(label.isHidden)
|
||||
XCTAssertEqual(label.text, text)
|
||||
XCTAssertEqual(label.textColor, foreground)
|
||||
XCTAssertEqual(label.backgroundColor, background)
|
||||
XCTAssertTrue(cell.accessibilityLabel?.hasPrefix("photo.JPG,\(text)") == true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func makeWiredTransferPhotoItem(state: TravelAlbumAutoRetouchState) -> TravelAlbumOTGPhotoItem {
|
||||
TravelAlbumOTGPhotoItem(
|
||||
id: "photo", sourceId: "photo", fileName: "photo.JPG", thumbnailURL: nil,
|
||||
capturedAt: "2026-08-27 12:00:00", fileSizeText: "1 KB", fileSizeBytes: 1024,
|
||||
status: .uploaded, progress: 100, errorMessage: nil, localPath: "", remoteUrl: "",
|
||||
autoRetouchState: state
|
||||
)
|
||||
}
|
||||
|
||||
private func waitUntil(_ condition: @escaping () -> Bool) async {
|
||||
for _ in 0 ..< 200 {
|
||||
if condition() { return }
|
||||
|
||||
Reference in New Issue
Block a user