Files
suixinkan_ios_new/suixinkan/Features/TravelAlbum/Views/TravelAlbumDetail/TravelAlbumDetailPhotoManageCard.swift
汉秋 5e620623eb 对齐旅拍相册详情页 Android UI,并修复网格预览与布局问题。
重构相册详情为信息卡片、Tab 筛选、排序、批量删除与底部上传栏;修复网格重叠、禁用按钮蒙层,并支持点击预览大图。同步扩展素材列表 API 与 ViewModel 分页逻辑,并优化有线传图缩略图与传输性能。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 13:53:59 +08:00

154 lines
6.0 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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)
}
}