@@ -11,19 +11,22 @@ import UIKit
final class TravelAlbumAIRetouchTemplateViewController : BaseViewController {
// / 页 面 使 用 的 c o l l e c t i o n s e c t i o n 。
private enum Section : Hashable {
case overview
case templates ( TravelAlbumAIRetouchTemplateCategory )
case mode
}
// / 页 面 使 用 的 d i f f a b l e i t e m 。
private enum Item : Hashable {
case overview
case template ( TravelAlbumAIRetouchTemplateCategory , TravelAlbumAIRetouchTemplate )
case mode
}
private let viewModel : TravelAlbumAIRetouchTemplateViewModel
private let api : any TravelAlbumServing
private let onSubmitted : ( ) -> Void
private let titleLabel = UILabel ( )
private lazy var collectionView = UICollectionView ( frame : . zero , collectionViewLayout : makeLayout ( ) )
private var dataSource : UICollectionViewDiffableDataSource < Section , Item > !
private let statusContainer = UIView ( )
@@ -33,7 +36,6 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
private let bottomBar = UIView ( )
private let bottomDivider = UIView ( )
private let footerStack = UIStackView ( )
private let quotaLabel = UILabel ( )
private let validationLabel = UILabel ( )
private let actionStack = UIStackView ( )
private let cancelButton = UIButton ( type : . system )
@@ -62,12 +64,6 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
view . backgroundColor = AIRetouchTemplateStyle . pageBackground
view . accessibilityIdentifier = " travelAlbum.aiRetouchTemplateSheet "
titleLabel . text = " AI修图 "
titleLabel . textColor = AIRetouchTemplateStyle . textPrimary
titleLabel . font = . systemFont ( ofSize : 20 , weight : . semibold )
titleLabel . textAlignment = . center
titleLabel . accessibilityTraits = . header
collectionView . backgroundColor = . clear
collectionView . showsVerticalScrollIndicator = false
collectionView . alwaysBounceVertical = true
@@ -77,6 +73,14 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
TravelAlbumAIRetouchTemplateCell . self ,
forCellWithReuseIdentifier : TravelAlbumAIRetouchTemplateCell . reuseIdentifier
)
collectionView . register (
TravelAlbumAIRetouchOverviewCell . self ,
forCellWithReuseIdentifier : TravelAlbumAIRetouchOverviewCell . reuseIdentifier
)
collectionView . register (
TravelAlbumAIRetouchModeCell . self ,
forCellWithReuseIdentifier : TravelAlbumAIRetouchModeCell . reuseIdentifier
)
collectionView . register (
TravelAlbumAIRetouchSectionHeader . self ,
forSupplementaryViewOfKind : UICollectionView . elementKindSectionHeader ,
@@ -104,10 +108,6 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
footerStack . axis = . vertical
footerStack . spacing = 8
footerStack . alignment = . fill
quotaLabel . font = . monospacedDigitSystemFont ( ofSize : 14 , weight : . medium )
quotaLabel . textColor = AIRetouchTemplateStyle . primary
quotaLabel . textAlignment = . center
quotaLabel . accessibilityIdentifier = " travelAlbum.aiRetouchRemainingQuotaLabel "
actionStack . axis = . horizontal
actionStack . spacing = 12
actionStack . distribution = . fillEqually
@@ -116,11 +116,9 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
configureConfirmButton ( )
actionStack . addArrangedSubview ( cancelButton )
actionStack . addArrangedSubview ( confirmButton )
footerStack . addArrangedSubview ( quotaLabel )
footerStack . addArrangedSubview ( validationLabel )
footerStack . addArrangedSubview ( actionStack )
view . addSubview ( titleLabel )
view . addSubview ( collectionView )
view . addSubview ( statusContainer )
statusContainer . addSubview ( statusIndicator )
@@ -132,11 +130,6 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
}
override func setupConstraints ( ) {
titleLabel . snp . makeConstraints { make in
make . top . equalTo ( view . safeAreaLayoutGuide ) . offset ( 12 )
make . leading . trailing . equalToSuperview ( ) . inset ( 18 )
make . height . equalTo ( 30 )
}
bottomBar . snp . makeConstraints { make in
make . leading . trailing . bottom . equalToSuperview ( )
}
@@ -150,10 +143,10 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
make . bottom . equalTo ( view . safeAreaLayoutGuide ) . offset ( - 12 )
}
actionStack . snp . makeConstraints { make in
make . height . equalTo ( 48 )
make . height . equalTo ( 56 )
}
collectionView . snp . makeConstraints { make in
make . top . equalTo ( titleLabel . snp . bottom ) . offset ( 8 )
make . top . equalTo ( view . safeAreaLayoutGuide )
make . leading . trailing . equalToSuperview ( )
make . bottom . equalTo ( bottomBar . snp . top )
}
@@ -212,9 +205,9 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
var configuration = UIButton . Configuration . filled ( )
configuration . title = " 取消 "
configuration . baseBackgroundColor = . white
configuration . baseForegroundColor = AIRetouchTemplateStyle . primary
configuration . background . cornerRadius = 14
configuration . background . strokeColor = AIRetouchTemplateStyle . primary . withAlphaComponent ( 0.55 )
configuration . baseForegroundColor = AIRetouchTemplateStyle . textPrimary
configuration . background . cornerRadius = 12
configuration . background . strokeColor = UIColor ( hex : 0xD1D1D6 )
configuration . background . strokeWidth = 1
configuration . titleTextAttributesTransformer = buttonTitleTransformer
cancelButton . configuration = configuration
@@ -226,7 +219,7 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
configuration . title = " 确定 "
configuration . baseBackgroundColor = AIRetouchTemplateStyle . primary
configuration . baseForegroundColor = . white
configuration . background . cornerRadius = 14
configuration . background . cornerRadius = 12
configuration . titleTextAttributesTransformer = buttonTitleTransformer
confirmButton . configuration = configuration
confirmButton . accessibilityIdentifier = " travelAlbum.aiRetouchConfirmButton "
@@ -245,6 +238,16 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
[ weak self ] collectionView , indexPath , item in
guard let self else { return nil }
switch item {
case . overview :
let cell = collectionView . dequeueReusableCell (
withReuseIdentifier : TravelAlbumAIRetouchOverviewCell . reuseIdentifier ,
for : indexPath
) as ! TravelAlbumAIRetouchOverviewCell
cell . apply (
selectedPhotoCount : self . viewModel . selectedPhotoCount ,
tips : self . viewModel . shouldShowInitialTips ? self . viewModel . initialTipsText : nil
)
return cell
case . template ( let category , let template ) :
let cell = collectionView . dequeueReusableCell (
withReuseIdentifier : TravelAlbumAIRetouchTemplateCell . reuseIdentifier ,
@@ -254,22 +257,49 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
template : template ,
selected : self . viewModel . selectedTemplateId ( for : category ) = = template . id
)
cell . onPreviewTapped = { [ weak self ] in
self ? . showPreview ( for : template )
}
return cell
case . mode :
let cell = collectionView . dequeueReusableCell (
withReuseIdentifier : TravelAlbumAIRetouchModeCell . reuseIdentifier ,
for : indexPath
) as ! TravelAlbumAIRetouchModeCell
cell . apply (
remainingQuota : self . viewModel . remainingQuota ,
insufficient : self . viewModel . remainingQuota . map {
self . viewModel . requiredQuota > $0
} ? ? false
)
return cell
}
}
dataSource . supplementaryViewProvider = { [ weak self ] collectionView , kind , indexPath in
guard kind = = UICollectionView . elementKindSectionHeader ,
let self ,
self . dataSource . snapshot ( ) . sectionIdentifiers . indices . contains ( indexPath . section ) ,
case . templates ( let category ) = self . dataSource . snapshot ( ) . sectionIdentifiers [ indexPath . section ] else {
self . dataSource . snapshot ( ) . sectionIdentifiers . indices . contains ( indexPath . section ) else {
return nil
}
let section = self . dataSource . snapshot ( ) . sectionIdentifiers [ indexPath . section ]
let header = collectionView . dequeueReusableSupplementaryView (
ofKind : kind ,
withReuseIdentifier : TravelAlbumAIRetouchSectionHeader . reuseIdentifier ,
for : indexPath
) as ! TravelAlbumAIRetouchSectionHeader
header . apply ( title : category . title , optional : self . viewModel . isOptional ( category ) )
switch section {
case . templates ( let category ) :
header . apply (
title : category . title ,
badge : category = = . cover
? . gift
: ( self . viewModel . isOptional ( category ) ? . optional : nil )
)
case . mode :
header . apply ( title : " 修图模式 " , badge : nil )
case . overview :
return nil
}
return header
}
}
@@ -281,6 +311,16 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
self . dataSource . snapshot ( ) . sectionIdentifiers . indices . contains ( sectionIndex ) else {
return nil
}
switch self . dataSource . snapshot ( ) . sectionIdentifiers [ sectionIndex ] {
case . overview :
let size = NSCollectionLayoutSize (
widthDimension : . fractionalWidth ( 1 ) ,
heightDimension : . absolute ( self . viewModel . shouldShowInitialTips ? 160 : 74 )
)
let item = NSCollectionLayoutItem ( layoutSize : size )
let group = NSCollectionLayoutGroup . vertical ( layoutSize : size , subitems : [ item ] )
return NSCollectionLayoutSection ( group : group )
case . templates :
let itemSize = NSCollectionLayoutSize (
widthDimension : . absolute ( 118 ) ,
heightDimension : . absolute ( 154 )
@@ -290,7 +330,12 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
let section = NSCollectionLayoutSection ( group : group )
section . orthogonalScrollingBehavior = . continuousGroupLeadingBoundary
section . interGroupSpacing = 12
section . contentInsets = NSDirectionalEdgeInsets ( top : 4 , leading : 18 , bottom : 12 , trailing : 18 )
section . contentInsets = NSDirectionalEdgeInsets (
top : 4 ,
leading : 18 ,
bottom : 12 ,
trailing : 18
)
section . boundarySupplementaryItems = [
NSCollectionLayoutBoundarySupplementaryItem (
layoutSize : NSCollectionLayoutSize (
@@ -302,6 +347,32 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
) ,
]
return section
case . mode :
let itemSize = NSCollectionLayoutSize (
widthDimension : . fractionalWidth ( 1 ) ,
heightDimension : . absolute ( 64 )
)
let item = NSCollectionLayoutItem ( layoutSize : itemSize )
let group = NSCollectionLayoutGroup . vertical ( layoutSize : itemSize , subitems : [ item ] )
let section = NSCollectionLayoutSection ( group : group )
section . contentInsets = NSDirectionalEdgeInsets (
top : 4 ,
leading : 18 ,
bottom : 18 ,
trailing : 18
)
section . boundarySupplementaryItems = [
NSCollectionLayoutBoundarySupplementaryItem (
layoutSize : NSCollectionLayoutSize (
widthDimension : . fractionalWidth ( 1 ) ,
heightDimension : . absolute ( 42 )
) ,
elementKind : UICollectionView . elementKindSectionHeader ,
alignment : . top
) ,
]
return section
}
}
}
@@ -326,17 +397,6 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
validationLabel . text = viewModel . validationMessage
validationLabel . isHidden = viewModel . validationMessage = = nil
if let remainingQuota = viewModel . remainingQuota {
quotaLabel . text = " 剩余 \( remainingQuota ) 次 "
quotaLabel . accessibilityLabel = " 剩余 \( remainingQuota ) 次修图额度 "
quotaLabel . textColor = viewModel . requiredQuota > remainingQuota
? AIRetouchTemplateStyle . danger
: AIRetouchTemplateStyle . primary
} else {
quotaLabel . text = " 剩余 -- 次 "
quotaLabel . accessibilityLabel = " 剩余修图额度暂不可用 "
quotaLabel . textColor = AIRetouchTemplateStyle . textSecondary
}
cancelButton . isEnabled = ! viewModel . isSubmitting
confirmButton . isEnabled = viewModel . canSubmit
confirmButton . alpha = viewModel . canSubmit ? 1 : 0.45
@@ -350,9 +410,13 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
private func applySnapshot ( ) {
var snapshot = NSDiffableDataSourceSnapshot < Section , Item > ( )
snapshot . appendSections ( [ . overview ] )
snapshot . appendItems ( [ . overview ] , toSection : . overview )
for category in viewModel . visibleCategories {
appendTemplates ( category , to : & snapshot )
}
snapshot . appendSections ( [ . mode ] )
snapshot . appendItems ( [ . mode ] , toSection : . mode )
snapshot . reconfigureItems ( snapshot . itemIdentifiers )
dataSource . apply ( snapshot , animatingDifferences : true )
}
@@ -385,16 +449,104 @@ final class TravelAlbumAIRetouchTemplateViewController: BaseViewController {
@objc private func retryTapped ( ) {
Task { await viewModel . loadTemplates ( api : api ) }
}
private func showPreview ( for template : TravelAlbumAIRetouchTemplate ) {
let controller = TravelAlbumAIRetouchTemplatePreviewViewController ( template : template )
controller . modalPresentationStyle = . fullScreen
present ( controller , animated : true )
}
}
extension TravelAlbumAIRetouchTemplateViewController : UICollectionViewDelegate {
func collectionView ( _ collectionView : UICollectionView , didSelectItemAt indexPath : IndexPath ) {
guard let item = dataSource . itemIdentifier ( for : indexPath ) ,
case . template ( let category , let template ) = item else { return }
collectionView . scrollToItem (
at : indexPath ,
at : . centeredHorizontally ,
animated : ! UIAccessibility . isReduceMotionEnabled
)
viewModel . toggleTemplate ( id : template . id , category : category )
}
}
// / A I 修 图 模 板 页 顶 部 说 明 卡 , 随 模 板 列 表 一 起 滚 动 。
final class TravelAlbumAIRetouchOverviewCell : UICollectionViewCell {
static let reuseIdentifier = " TravelAlbumAIRetouchOverviewCell "
private let stackView = UIStackView ( )
private let titleLabel = UILabel ( )
private let selectionCountLabel = UILabel ( )
private let tipsContainer = UIView ( )
private let tipsLabel = UILabel ( )
override init ( frame : CGRect ) {
super . init ( frame : frame )
titleLabel . text = " AI修图 "
titleLabel . textColor = AIRetouchTemplateStyle . textPrimary
titleLabel . font = . systemFont ( ofSize : 20 , weight : . semibold )
titleLabel . textAlignment = . center
titleLabel . accessibilityTraits = . header
selectionCountLabel . textColor = AIRetouchTemplateStyle . textSecondary
selectionCountLabel . font = UIFontMetrics ( forTextStyle : . body ) . scaledFont (
for : . systemFont ( ofSize : 17 , weight : . regular )
)
selectionCountLabel . adjustsFontForContentSizeCategory = true
selectionCountLabel . textAlignment = . center
selectionCountLabel . accessibilityIdentifier = " travelAlbum.aiRetouchSelectionCountLabel "
tipsContainer . backgroundColor = AIRetouchTemplateStyle . tipsBackground
tipsContainer . layer . cornerRadius = 10
tipsContainer . accessibilityIdentifier = " travelAlbum.aiRetouchTipsContainer "
tipsLabel . textColor = AIRetouchTemplateStyle . tipsText
tipsLabel . font = UIFontMetrics ( forTextStyle : . footnote ) . scaledFont (
for : . systemFont ( ofSize : 13 , weight : . regular )
)
tipsLabel . adjustsFontForContentSizeCategory = true
tipsLabel . numberOfLines = 0
tipsLabel . accessibilityIdentifier = " travelAlbum.aiRetouchTipsLabel "
stackView . axis = . vertical
stackView . alignment = . fill
stackView . spacing = 4
stackView . addArrangedSubview ( titleLabel )
stackView . addArrangedSubview ( selectionCountLabel )
stackView . addArrangedSubview ( tipsContainer )
stackView . setCustomSpacing ( 12 , after : selectionCountLabel )
contentView . addSubview ( stackView )
tipsContainer . addSubview ( tipsLabel )
stackView . snp . makeConstraints { make in
make . top . equalToSuperview ( ) . offset ( 10 )
make . leading . trailing . equalToSuperview ( ) . inset ( 18 )
make . bottom . lessThanOrEqualToSuperview ( ) . offset ( - 10 )
}
titleLabel . snp . makeConstraints { make in
make . height . equalTo ( 30 )
}
selectionCountLabel . snp . makeConstraints { make in
make . height . equalTo ( 24 )
}
tipsLabel . snp . makeConstraints { make in
make . edges . equalToSuperview ( ) . inset ( UIEdgeInsets ( top : 10 , left : 12 , bottom : 10 , right : 12 ) )
}
}
@ available ( * , unavailable )
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
// / 更 新 选 中 照 片 数 量 和 当 前 工 作 流 说 明 。
func apply ( selectedPhotoCount : Int , tips : String ? ) {
selectionCountLabel . text = " 已选择 \( selectedPhotoCount ) 张照片 "
tipsLabel . text = tips
tipsContainer . isHidden = tips = = nil
tipsLabel . isHidden = tips = = nil
}
}
// / A I 修 图 模 板 卡 片 , 展 示 模 板 预 览 、 名 称 和 明 确 选 中 态 。
final class TravelAlbumAIRetouchTemplateCell : UICollectionViewCell {
static let reuseIdentifier = " TravelAlbumAIRetouchTemplateCell "
@@ -402,6 +554,9 @@ final class TravelAlbumAIRetouchTemplateCell: UICollectionViewCell {
private let previewImageView = UIImageView ( )
private let nameLabel = UILabel ( )
private let checkView = UIImageView ( )
private let previewButton = AIRetouchTemplatePreviewButton ( type : . system )
var onPreviewTapped : ( ( ) -> Void ) ?
override init ( frame : CGRect ) {
super . init ( frame : frame )
@@ -421,10 +576,26 @@ final class TravelAlbumAIRetouchTemplateCell: UICollectionViewCell {
checkView . tintColor = AIRetouchTemplateStyle . primary
checkView . backgroundColor = . white
checkView . layer . cornerRadius = 10
var previewConfiguration = UIButton . Configuration . filled ( )
previewConfiguration . title = " 预览 "
previewConfiguration . baseForegroundColor = . white
previewConfiguration . baseBackgroundColor = UIColor . black . withAlphaComponent ( 0.58 )
previewConfiguration . background . cornerRadius = 15
previewConfiguration . contentInsets = . zero
previewConfiguration . titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer {
attributes in
var attributes = attributes
attributes . font = . systemFont ( ofSize : 13 , weight : . regular )
return attributes
}
previewButton . configuration = previewConfiguration
previewButton . accessibilityIdentifier = " travelAlbum.aiRetouchTemplatePreviewButton "
previewButton . addTarget ( self , action : #selector ( previewTapped ) , for : . touchUpInside )
contentView . addSubview ( previewImageView )
contentView . addSubview ( nameLabel )
contentView . addSubview ( checkView )
contentView . addSubview ( previewButton )
previewImageView . snp . makeConstraints { make in
make . top . leading . trailing . equalToSuperview ( )
make . height . equalTo ( 116 )
@@ -438,6 +609,11 @@ final class TravelAlbumAIRetouchTemplateCell: UICollectionViewCell {
make . top . trailing . equalToSuperview ( ) . inset ( 8 )
make . size . equalTo ( 20 )
}
previewButton . snp . makeConstraints { make in
make . trailing . bottom . equalTo ( previewImageView ) . inset ( 8 )
make . width . equalTo ( 58 )
make . height . equalTo ( 32 )
}
}
@ available ( * , unavailable )
@@ -449,6 +625,7 @@ final class TravelAlbumAIRetouchTemplateCell: UICollectionViewCell {
super . prepareForReuse ( )
previewImageView . kf . cancelDownloadTask ( )
previewImageView . image = nil
onPreviewTapped = nil
}
// / 更 新 模 板 内 容 与 选 中 状 态 。
@@ -478,9 +655,84 @@ final class TravelAlbumAIRetouchTemplateCell: UICollectionViewCell {
accessibilityValue = selected ? " 已选择 " : " 未选择 "
accessibilityTraits = selected ? [ . button , . selected ] : [ . button ]
}
@objc private func previewTapped ( ) {
onPreviewTapped ? ( )
}
}
// / A I 修 图 模 板 分 组 标 题 , 可 附 带 “ 选 填 ” 标 签 。
// / 扩 大 模 板 预 览 按 钮 的 触 摸 区 域 , 同 时 保 持 截 图 中 的 胶 囊 尺 寸 。
private final class AIRetouchTemplatePreviewButton : UIButton {
override func point ( inside point : CGPoint , with event : UIEvent ? ) -> Bool {
bounds . insetBy ( dx : - 6 , dy : - 6 ) . contains ( point )
}
}
// / A I 修 图 计 费 模 式 卡 , 展 示 按 张 收 费 规 则 与 剩 余 额 度 。
final class TravelAlbumAIRetouchModeCell : UICollectionViewCell {
static let reuseIdentifier = " TravelAlbumAIRetouchModeCell "
private let modeLabel = UILabel ( )
private let quotaContainer = UIView ( )
private let quotaLabel = UILabel ( )
override init ( frame : CGRect ) {
super . init ( frame : frame )
contentView . backgroundColor = . white
contentView . layer . cornerRadius = 12
modeLabel . text = " AI精修 · 按张收费 "
modeLabel . textColor = AIRetouchTemplateStyle . textPrimary
modeLabel . font = . systemFont ( ofSize : 16 , weight : . medium )
quotaContainer . backgroundColor = AIRetouchTemplateStyle . primary . withAlphaComponent ( 0.1 )
quotaContainer . layer . cornerRadius = 15
quotaLabel . font = . monospacedDigitSystemFont ( ofSize : 14 , weight : . medium )
quotaLabel . textColor = AIRetouchTemplateStyle . primary
quotaLabel . textAlignment = . center
quotaLabel . accessibilityIdentifier = " travelAlbum.aiRetouchRemainingQuotaLabel "
contentView . addSubview ( modeLabel )
contentView . addSubview ( quotaContainer )
quotaContainer . addSubview ( quotaLabel )
modeLabel . snp . makeConstraints { make in
make . leading . equalToSuperview ( ) . offset ( 16 )
make . centerY . equalToSuperview ( )
make . trailing . lessThanOrEqualTo ( quotaContainer . snp . leading ) . offset ( - 12 )
}
quotaContainer . snp . makeConstraints { make in
make . trailing . equalToSuperview ( ) . offset ( - 16 )
make . centerY . equalToSuperview ( )
make . width . equalTo ( 96 )
make . height . equalTo ( 30 )
}
quotaLabel . snp . makeConstraints { make in
make . edges . equalToSuperview ( )
}
}
@ available ( * , unavailable )
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
// / 更 新 剩 余 可 精 修 张 数 , 并 在 额 度 不 足 时 切 换 为 警 示 色 。
func apply ( remainingQuota : Int ? , insufficient : Bool ) {
quotaLabel . text = remainingQuota . map { " 剩余 \( $0 ) 张 " } ? ? " 剩余 -- 张 "
quotaLabel . accessibilityLabel = remainingQuota . map { " 剩余 \( $0 ) 张修图额度 " }
? ? " 剩余修图额度暂不可用 "
quotaLabel . textColor = insufficient
? AIRetouchTemplateStyle . danger
: ( remainingQuota = = nil ? AIRetouchTemplateStyle . textSecondary : AIRetouchTemplateStyle . primary )
quotaContainer . backgroundColor = quotaLabel . textColor . withAlphaComponent ( 0.1 )
}
}
// / 模 板 分 组 标 题 右 侧 的 业 务 标 记 。
fileprivate enum AIRetouchTemplateSectionBadge {
case optional
case gift
}
// / A I 修 图 模 板 分 组 标 题 , 可 附 带 选 填 或 赠 送 标 记 。
final class TravelAlbumAIRetouchSectionHeader : UICollectionReusableView {
static let reuseIdentifier = " TravelAlbumAIRetouchSectionHeader "
@@ -491,12 +743,9 @@ final class TravelAlbumAIRetouchSectionHeader: UICollectionReusableView {
super . init ( frame : frame )
titleLabel . font = . systemFont ( ofSize : 17 , weight : . semibold )
titleLabel . textColor = AIRetouchTemplateStyle . textPrimary
optionalLabel . text = " 选填 "
optionalLabel . font = . systemFont ( ofSize : 11 , weight : . medium )
optionalLabel . textColor = AIRetouchTemplateStyle . primary
optionalLabel . textAlignment = . center
optionalLabel . backgroundColor = AIRetouchTemplateStyle . primary . withAlphaComponent ( 0.1 )
optionalLabel . layer . cornerRadius = 8
optionalLabel . layer . cornerRadius = 10
optionalLabel . clipsToBounds = true
addSubview ( titleLabel )
@@ -508,7 +757,7 @@ final class TravelAlbumAIRetouchSectionHeader: UICollectionReusableView {
optionalLabel . snp . makeConstraints { make in
make . leading . equalTo ( titleLabel . snp . trailing ) . offset ( 8 )
make . centerY . equalTo ( titleLabel )
make . width . equalTo ( 38 )
make . width . greaterThanOrEqualTo ( 38 )
make . height . equalTo ( 20 )
make . trailing . lessThanOrEqualToSuperview ( ) . offset ( - 18 )
}
@@ -519,21 +768,163 @@ final class TravelAlbumAIRetouchSectionHeader: UICollectionReusableView {
fatalError ( " init(coder:) has not been implemented " )
}
// / 更 新 分 组 标 题 与 选 填 标 签 。
func apply ( title : String , optional : Bool ) {
// / 更 新 分 组 标 题 与 业 务 标 记 。
fileprivate func apply ( title : String , badge : AIRetouchTemplateSectionBadge ? ) {
titleLabel . text = title
optionalLabel . isHidden = ! optional
accessibilityLabel = optional ? " \( title ) ,选填 " : title
optionalLabel . isHidden = badge = = nil
switch badge {
case . optional :
optionalLabel . text = " 选填 "
optionalLabel . textColor = AIRetouchTemplateStyle . primary
optionalLabel . backgroundColor = AIRetouchTemplateStyle . primary . withAlphaComponent ( 0.1 )
case . gift :
optionalLabel . text = " 赠送 · 不占精修张数 "
optionalLabel . textColor = AIRetouchTemplateStyle . gift
optionalLabel . backgroundColor = AIRetouchTemplateStyle . giftBackground
case nil :
optionalLabel . text = nil
}
let badgeText = optionalLabel . text ? . trimmingCharacters ( in : . whitespaces )
accessibilityLabel = badgeText . map { " \( title ) , \( $0 ) " } ? ? title
accessibilityTraits = . header
}
}
// / A I 修 图 模 板 全 屏 预 览 , 支 持 双 指 缩 放 查 看 模 板 细 节 。
final class TravelAlbumAIRetouchTemplatePreviewViewController : UIViewController {
private let template : TravelAlbumAIRetouchTemplate
private let backButton = UIButton ( type : . system )
private let titleLabel = UILabel ( )
private let subtitleLabel = UILabel ( )
private let scrollView = UIScrollView ( )
private let imageView = UIImageView ( )
// / 创 建 指 定 模 板 的 全 屏 预 览 页 。
init ( template : TravelAlbumAIRetouchTemplate ) {
self . template = template
super . init ( nibName : nil , bundle : nil )
modalPresentationStyle = . fullScreen
}
@ available ( * , unavailable )
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
override func viewDidLoad ( ) {
super . viewDidLoad ( )
setupUI ( )
setupConstraints ( )
loadPreview ( )
}
private func setupUI ( ) {
view . backgroundColor = . black
view . accessibilityIdentifier = " travelAlbum.aiRetouchTemplatePreview "
var backConfiguration = UIButton . Configuration . filled ( )
backConfiguration . image = UIImage ( systemName : " chevron.left " )
backConfiguration . baseForegroundColor = . white
backConfiguration . baseBackgroundColor = UIColor . white . withAlphaComponent ( 0.12 )
backConfiguration . background . cornerRadius = 24
backButton . configuration = backConfiguration
backButton . accessibilityLabel = " 返回模板选择 "
backButton . accessibilityIdentifier = " travelAlbum.aiRetouchTemplatePreviewBackButton "
backButton . addTarget ( self , action : #selector ( backTapped ) , for : . touchUpInside )
titleLabel . text = template . name
titleLabel . textColor = . white
titleLabel . font = . systemFont ( ofSize : 20 , weight : . semibold )
titleLabel . textAlignment = . center
titleLabel . lineBreakMode = . byTruncatingTail
subtitleLabel . text = " 双指缩放查看细节 "
subtitleLabel . textColor = UIColor . white . withAlphaComponent ( 0.52 )
subtitleLabel . font = . systemFont ( ofSize : 14 , weight : . regular )
subtitleLabel . textAlignment = . center
scrollView . minimumZoomScale = 1
scrollView . maximumZoomScale = 4
scrollView . delegate = self
scrollView . showsHorizontalScrollIndicator = false
scrollView . showsVerticalScrollIndicator = false
scrollView . accessibilityIdentifier = " travelAlbum.aiRetouchTemplatePreviewScrollView "
imageView . contentMode = . scaleAspectFit
imageView . clipsToBounds = true
imageView . backgroundColor = UIColor ( hex : 0x111111 )
imageView . accessibilityLabel = " \( template . name ) 模板预览图 "
view . addSubview ( backButton )
view . addSubview ( titleLabel )
view . addSubview ( subtitleLabel )
view . addSubview ( scrollView )
scrollView . addSubview ( imageView )
}
private func setupConstraints ( ) {
backButton . snp . makeConstraints { make in
make . top . equalTo ( view . safeAreaLayoutGuide ) . offset ( 20 )
make . leading . equalToSuperview ( ) . offset ( 18 )
make . size . equalTo ( 48 )
}
titleLabel . snp . makeConstraints { make in
make . top . equalTo ( view . safeAreaLayoutGuide ) . offset ( 20 )
make . leading . greaterThanOrEqualTo ( backButton . snp . trailing ) . offset ( 12 )
make . centerX . equalToSuperview ( )
make . trailing . lessThanOrEqualToSuperview ( ) . offset ( - 66 )
make . height . equalTo ( 26 )
}
subtitleLabel . snp . makeConstraints { make in
make . top . equalTo ( titleLabel . snp . bottom ) . offset ( 2 )
make . centerX . equalToSuperview ( )
}
scrollView . snp . makeConstraints { make in
make . top . equalTo ( subtitleLabel . snp . bottom ) . offset ( 18 )
make . leading . trailing . bottom . equalToSuperview ( )
}
imageView . snp . makeConstraints { make in
make . center . equalTo ( scrollView . frameLayoutGuide )
make . width . equalTo ( scrollView . frameLayoutGuide )
make . height . equalTo ( imageView . snp . width ) . multipliedBy ( 0.75 )
}
}
private func loadPreview ( ) {
guard let url = URL ( string : template . previewURL ) , ! template . previewURL . isEmpty else {
imageView . image = UIImage ( systemName : " photo " )
imageView . tintColor = UIColor . white . withAlphaComponent ( 0.45 )
return
}
imageView . kf . setImage (
with : url ,
placeholder : UIImage ( systemName : " photo " ) ? . withTintColor (
UIColor . white . withAlphaComponent ( 0.45 ) ,
renderingMode : . alwaysOriginal
)
)
}
@objc private func backTapped ( ) {
dismiss ( animated : true )
}
}
extension TravelAlbumAIRetouchTemplatePreviewViewController : UIScrollViewDelegate {
func viewForZooming ( in scrollView : UIScrollView ) -> UIView ? {
imageView
}
}
// / A I 修 图 模 板 页 视 觉 常 量 。
private enum AIRetouchTemplateStyle {
static let primary = UIColor ( hex : 0x1677FF )
static let pageBackground = UIColor ( hex : 0xF7F9FC )
static let pageBackground = UIColor ( hex : 0xF7F7F7 )
static let textPrimary = UIColor ( hex : 0x172033 )
static let textSecondary = UIColor ( hex : 0x7F8A9E )
static let textSecondary = UIColor ( hex : 0x6E6E73 )
static let tipsBackground = UIColor ( hex : 0xEAF3FF )
static let tipsText = UIColor ( hex : 0x385778 )
static let gift = UIColor ( hex : 0xFF9418 )
static let giftBackground = UIColor ( hex : 0xFFF1D9 )
static let border = UIColor ( hex : 0xDCE4EF )
static let danger = UIColor ( hex : 0xE53935 )
}