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