重构相册详情为信息卡片、Tab 筛选、排序、批量删除与底部上传栏;修复网格重叠、禁用按钮蒙层,并支持点击预览大图。同步扩展素材列表 API 与 ViewModel 分页逻辑,并优化有线传图缩略图与传输性能。 Co-authored-by: Cursor <cursoragent@cursor.com>
154 lines
6.0 KiB
Swift
154 lines
6.0 KiB
Swift
//
|
||
// TravelAlbumDetailPhotoManageCard.swift
|
||
// suixinkan
|
||
//
|
||
// 旅拍相册详情照片管理卡片,对齐 Android PhotoManageCard。
|
||
//
|
||
|
||
import SwiftUI
|
||
|
||
/// 旅拍相册详情照片管理区域。
|
||
struct TravelAlbumDetailPhotoManageCard: View {
|
||
let materials: [TravelAlbumMaterial]
|
||
let allPhotoCount: Int
|
||
let selectedTab: Int
|
||
let orderBy: Int
|
||
let isSelectionMode: Bool
|
||
let selectedMaterialIDs: Set<Int>
|
||
let onTabSelect: (Int) -> Void
|
||
let onOrderBySelect: (Int) -> Void
|
||
let onToggleSelectionMode: () -> Void
|
||
let onMaterialClick: (TravelAlbumMaterial) -> Void
|
||
let onMaterialPreview: (TravelAlbumMaterial) -> Void
|
||
let onLoadMore: (Int) -> Void
|
||
|
||
private let gridColumns = [
|
||
GridItem(.flexible(), spacing: TravelAlbumDetailDesign.gridHorizontalSpacing),
|
||
GridItem(.flexible(), spacing: TravelAlbumDetailDesign.gridHorizontalSpacing),
|
||
GridItem(.flexible())
|
||
]
|
||
|
||
var body: some View {
|
||
VStack(spacing: 0) {
|
||
tabToolbar
|
||
Divider().overlay(TravelAlbumDetailDesign.borderLight)
|
||
photoGrid
|
||
}
|
||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||
.background(Color.white)
|
||
.clipShape(RoundedRectangle(cornerRadius: TravelAlbumDetailDesign.cardCornerRadius))
|
||
.shadow(color: TravelAlbumDetailDesign.cardShadow, radius: TravelAlbumDetailDesign.cardShadowRadius, x: 0, y: 1)
|
||
}
|
||
|
||
private var tabToolbar: some View {
|
||
HStack(spacing: 8) {
|
||
HStack(spacing: 8) {
|
||
tabPill(
|
||
text: "全部照片\(allPhotoCount)",
|
||
selected: selectedTab == TravelAlbumDetailViewModel.tabAll
|
||
) {
|
||
onTabSelect(TravelAlbumDetailViewModel.tabAll)
|
||
}
|
||
tabPill(
|
||
text: "已购照片",
|
||
selected: selectedTab == TravelAlbumDetailViewModel.tabPurchased
|
||
) {
|
||
onTabSelect(TravelAlbumDetailViewModel.tabPurchased)
|
||
}
|
||
}
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
|
||
sortMenu
|
||
|
||
if selectedTab == TravelAlbumDetailViewModel.tabAll {
|
||
Button(action: onToggleSelectionMode) {
|
||
Circle()
|
||
.fill(isSelectionMode ? TravelAlbumDetailDesign.lightBlue : TravelAlbumDetailDesign.selectionInactiveBackground)
|
||
.frame(width: TravelAlbumDetailDesign.actionCircleSize, height: TravelAlbumDetailDesign.actionCircleSize)
|
||
.overlay {
|
||
Image(systemName: isSelectionMode ? "checkmark.circle.fill" : "circle")
|
||
.font(.system(size: 18))
|
||
.foregroundStyle(isSelectionMode ? AppDesign.primary : TravelAlbumDetailDesign.text999)
|
||
}
|
||
}
|
||
.buttonStyle(.plain)
|
||
.accessibilityLabel("选择")
|
||
}
|
||
}
|
||
.padding(.horizontal, 12)
|
||
.padding(.vertical, 12)
|
||
}
|
||
|
||
private var sortMenu: some View {
|
||
Menu {
|
||
ForEach(TravelAlbumDetailViewModel.sortOptions, id: \.value) { option in
|
||
Button {
|
||
onOrderBySelect(option.value)
|
||
} label: {
|
||
if orderBy == option.value {
|
||
Label(option.label, systemImage: "checkmark")
|
||
} else {
|
||
Text(option.label)
|
||
}
|
||
}
|
||
}
|
||
} label: {
|
||
Circle()
|
||
.fill(TravelAlbumDetailDesign.lightBlue)
|
||
.frame(width: TravelAlbumDetailDesign.actionCircleSize, height: TravelAlbumDetailDesign.actionCircleSize)
|
||
.overlay {
|
||
Image(systemName: "line.3.horizontal.decrease")
|
||
.font(.system(size: 16))
|
||
.foregroundStyle(AppDesign.primary)
|
||
}
|
||
}
|
||
.accessibilityLabel("筛选")
|
||
}
|
||
|
||
private var photoGrid: some View {
|
||
ScrollView {
|
||
if materials.isEmpty {
|
||
Text("暂无照片")
|
||
.font(.system(size: 14))
|
||
.foregroundStyle(TravelAlbumDetailDesign.text999)
|
||
.frame(maxWidth: .infinity, minHeight: 180)
|
||
} else {
|
||
LazyVGrid(
|
||
columns: gridColumns,
|
||
spacing: TravelAlbumDetailDesign.gridVerticalSpacing
|
||
) {
|
||
ForEach(Array(materials.enumerated()), id: \.element.id) { index, material in
|
||
TravelAlbumDetailMaterialGridItem(
|
||
material: material,
|
||
isSelectionMode: isSelectionMode,
|
||
isSelected: selectedMaterialIDs.contains(material.id),
|
||
onSelect: { onMaterialClick(material) },
|
||
onPreview: { onMaterialPreview(material) }
|
||
)
|
||
.onAppear {
|
||
onLoadMore(index)
|
||
}
|
||
}
|
||
}
|
||
.padding(TravelAlbumDetailDesign.gridPadding)
|
||
}
|
||
}
|
||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||
.clipped()
|
||
}
|
||
|
||
/// Tab 胶囊按钮。
|
||
private func tabPill(text: String, selected: Bool, action: @escaping () -> Void) -> some View {
|
||
Button(action: action) {
|
||
Text(text)
|
||
.font(.system(size: 13, weight: selected ? .medium : .regular))
|
||
.foregroundStyle(selected ? Color.white : AppDesign.primary)
|
||
.padding(.horizontal, 14)
|
||
.padding(.vertical, 7)
|
||
.background(selected ? AppDesign.primary : TravelAlbumDetailDesign.lightBlue)
|
||
.clipShape(RoundedRectangle(cornerRadius: TravelAlbumDetailDesign.tabPillCornerRadius))
|
||
}
|
||
.buttonStyle(.plain)
|
||
}
|
||
}
|