feat: add AI retouch before-after comparison

This commit is contained in:
2026-08-13 17:46:06 +08:00
parent e190b5958b
commit 0f3b26991e
12 changed files with 953 additions and 145 deletions
@@ -48,6 +48,7 @@ final class TravelAlbumPhotoPreviewViewController: UIViewController {
private let variantControls = UIView()
private let variantSegmentedControl = UISegmentedControl()
private let highResolutionButton = UIButton(type: .system)
private let comparisonButton = UIButton(type: .system)
private let actionStack = UIStackView()
private let deleteButton = UIButton(type: .system)
private let refreshButton = UIButton(type: .system)
@@ -193,6 +194,7 @@ final class TravelAlbumPhotoPreviewViewController: UIViewController {
)
actionStack.accessibilityIdentifier = "travelAlbum.previewActionStack"
configureHighResolutionButton()
configureComparisonButton()
configureActions()
let titleStack = UIStackView(arrangedSubviews: [titleLabel, sizeLabel])
@@ -212,6 +214,7 @@ final class TravelAlbumPhotoPreviewViewController: UIViewController {
variantControls.addSubview(variantSegmentedControl)
variantControls.addSubview(highResolutionButton)
bottomChrome.addSubview(actionStack)
view.addSubview(comparisonButton)
topChrome.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
@@ -274,6 +277,11 @@ final class TravelAlbumPhotoPreviewViewController: UIViewController {
make.height.equalTo(70)
make.bottom.equalTo(view.safeAreaLayoutGuide)
}
comparisonButton.snp.makeConstraints { make in
make.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(bottomChrome.snp.top).offset(-12)
make.size.equalTo(48)
}
}
private func configureHighResolutionButton() {
@@ -293,6 +301,27 @@ final class TravelAlbumPhotoPreviewViewController: UIViewController {
highResolutionButton.addTarget(self, action: #selector(highResolutionTapped), for: .touchUpInside)
}
private func configureComparisonButton() {
var configuration = UIButton.Configuration.filled()
configuration.image = UIImage(named: "travel_album_before_after")?.withRenderingMode(.alwaysTemplate)
configuration.contentInsets = .zero
configuration.baseForegroundColor = .white
configuration.baseBackgroundColor = UIColor(hex: 0x111827).withAlphaComponent(0.94)
configuration.background.cornerRadius = 24
configuration.background.strokeColor = UIColor(hex: 0x263244)
configuration.background.strokeWidth = 1
comparisonButton.configuration = configuration
comparisonButton.accessibilityLabel = "前后对比"
comparisonButton.accessibilityHint = "对比当前效果图与原图"
comparisonButton.accessibilityIdentifier = "travelAlbum.previewComparisonButton"
comparisonButton.layer.shadowColor = UIColor.black.cgColor
comparisonButton.layer.shadowOpacity = 0.28
comparisonButton.layer.shadowRadius = 8
comparisonButton.layer.shadowOffset = CGSize(width: 0, height: 3)
comparisonButton.isHidden = true
comparisonButton.addTarget(self, action: #selector(comparisonTapped), for: .touchUpInside)
}
private func configureActions() {
let aiButton = makeActionButton(
title: "AI修图",
@@ -392,9 +421,21 @@ final class TravelAlbumPhotoPreviewViewController: UIViewController {
highResolutionButton.isEnabled = asset != nil
highResolutionButton.alpha = asset == nil ? 0.45 : 1
rebuildTabs()
updateComparisonButton()
loadMoreIfNeeded(projectIndex: node.projectIndex)
}
private func updateComparisonButton() {
let hasComparison = currentProject?.comparisonContent(for: currentAsset?.kind ?? .original) != nil
let shouldShow = chromeVisible && hasComparison
comparisonButton.isHidden = !hasComparison
comparisonButton.isUserInteractionEnabled = shouldShow
comparisonButton.alpha = shouldShow ? 1 : 0
comparisonButton.transform = shouldShow
? .identity
: CGAffineTransform(translationX: 0, y: 10)
}
private func rebuildTabs() {
while variantSegmentedControl.numberOfSegments > 0 {
variantSegmentedControl.removeSegment(at: 0, animated: false)
@@ -581,6 +622,11 @@ final class TravelAlbumPhotoPreviewViewController: UIViewController {
private func toggleChrome() {
chromeVisible.toggle()
let hasComparison = currentProject?.comparisonContent(for: currentAsset?.kind ?? .original) != nil
if chromeVisible && hasComparison {
comparisonButton.isHidden = false
}
comparisonButton.isUserInteractionEnabled = chromeVisible && hasComparison
let reduceMotion = UIAccessibility.isReduceMotionEnabled
let animations = {
self.topChrome.alpha = self.chromeVisible ? 1 : 0
@@ -591,12 +637,20 @@ final class TravelAlbumPhotoPreviewViewController: UIViewController {
self.bottomChrome.transform = self.chromeVisible
? .identity
: CGAffineTransform(translationX: 0, y: self.bottomChrome.bounds.height)
self.comparisonButton.alpha = self.chromeVisible && hasComparison ? 1 : 0
self.comparisonButton.transform = self.chromeVisible && hasComparison
? .identity
: CGAffineTransform(translationX: 0, y: 10)
}
UIView.animate(
withDuration: reduceMotion ? 0.12 : 0.22,
delay: 0,
options: reduceMotion ? [.curveEaseOut] : [.curveEaseOut, .beginFromCurrentState],
animations: animations
animations: animations,
completion: { [weak self] _ in
guard let self else { return }
self.comparisonButton.isHidden = !self.chromeVisible || !hasComparison
}
)
}
@@ -632,6 +686,17 @@ final class TravelAlbumPhotoPreviewViewController: UIViewController {
toggleChrome()
}
@objc private func comparisonTapped() {
guard presentedViewController == nil,
let project = currentProject,
let content = project.comparisonContent(for: currentAsset?.kind ?? .original)
else { return }
let controller = BeforeAfterComparisonViewController(
viewModel: BeforeAfterComparisonViewModel(content: content)
)
present(controller, animated: true)
}
@objc private func aiTapped() {
guard presentedViewController == nil else { return }
guard let project = currentProject else { return }