对齐旅拍相册详情页 Android UI,并修复网格预览与布局问题。

重构相册详情为信息卡片、Tab 筛选、排序、批量删除与底部上传栏;修复网格重叠、禁用按钮蒙层,并支持点击预览大图。同步扩展素材列表 API 与 ViewModel 分页逻辑,并优化有线传图缩略图与传输性能。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-30 13:53:59 +08:00
parent c71b45bdfd
commit 5e620623eb
23 changed files with 2000 additions and 288 deletions

View File

@ -0,0 +1,153 @@
//
// 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)
}
}