Files
suixinkan_ios_new/suixinkan/Features/TravelAlbum/Views/WiredTransfer/WiredTransferControlCard.swift
汉秋 c71b45bdfd 对齐有线传图页 Android UI,并改用相对路径持久化本地照片。
还原顶栏、浮动卡片、时间侧栏与底部交互;照片路径改为 CameraDownloads 相对路径并兼容旧数据;格式 Chip 固定 JPG 且不可点击。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 11:16:28 +08:00

239 lines
8.4 KiB
Swift
Raw 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.

//
// WiredTransferControlCard.swift
// suixinkan
//
import SwiftUI
/// 线 + + Tab
struct WiredTransferControlCard: View {
let scenicSpotLabel: String?
let deviceStorageInfo: DeviceStorageSnapshot
let isCameraConnected: Bool
let isRefreshingCameraFiles: Bool
let cameraDeviceName: String
let transferModeOption: String
let tabTitles: [String]
let tabCounts: [Int]
let selectedTabIndex: Int
let onDeviceUsageClick: () -> Void
let onRefreshCameraFiles: () -> Void
let onHelpClick: () -> Void
let onTransferModeSelected: (String) -> Void
let onTabSelected: (Int) -> Void
private let modeOptions = [
WiredCameraTransferViewModel.modeLiveCapture,
WiredCameraTransferViewModel.modePostShootTransfer
]
var body: some View {
VStack(spacing: 0) {
VStack(spacing: 12) {
connectionStatusRow
filterChipRow
}
.padding(.horizontal, 14)
.padding(.vertical, 14)
Divider().background(WiredTransferDesign.borderLight)
WiredTransferStatsTabRow(
tabTitles: tabTitles,
counts: tabCounts,
selectedTabIndex: selectedTabIndex,
onTabSelected: onTabSelected
)
}
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: WiredTransferDesign.cardCornerRadius))
.shadow(color: WiredTransferDesign.cardShadow, radius: 6, x: 0, y: 2)
.padding(.horizontal, WiredTransferDesign.cardHorizontalPadding)
.offset(y: -WiredTransferDesign.cardOverlap)
}
private var connectionStatusRow: some View {
HStack(alignment: .top, spacing: 10) {
deviceUsagePanel
VStack(alignment: .leading, spacing: 4) {
HStack(alignment: .center, spacing: 8) {
connectionChip
Spacer(minLength: 4)
refreshButton
}
if !isCameraConnected {
helpDocLine
}
if let scenicSpotLabel, !scenicSpotLabel.isEmpty {
Text(scenicSpotLabel)
.font(.system(size: 10))
.foregroundStyle(WiredTransferDesign.text999)
.lineLimit(1)
}
}
}
}
private var deviceUsagePanel: some View {
Button(action: onDeviceUsageClick) {
VStack(spacing: 6) {
Text(deviceStorageInfo.modelName)
.font(.system(size: 11, weight: .medium))
.foregroundStyle(WiredTransferDesign.text333)
.multilineTextAlignment(.center)
.lineLimit(2)
.frame(maxWidth: 88)
Text(deviceStorageInfo.availableGbText)
.font(.system(size: 10))
.foregroundStyle(AppDesign.primary)
.lineLimit(1)
}
.frame(minWidth: WiredTransferDesign.statusRingMinWidth)
.padding(.horizontal, 8)
.padding(.vertical, 10)
.overlay {
RoundedRectangle(cornerRadius: 12)
.stroke(WiredTransferDesign.borderLight, lineWidth: 3)
}
}
.buttonStyle(.plain)
}
private var connectionChip: some View {
Text(connectionChipText)
.font(.system(size: 11, weight: .medium))
.foregroundStyle(isCameraConnected ? AppDesign.primary : WiredTransferDesign.danger)
.lineLimit(1)
.padding(.horizontal, 8)
.padding(.vertical, 2)
.background((isCameraConnected ? AppDesign.primary : WiredTransferDesign.danger).opacity(0.1))
.clipShape(RoundedRectangle(cornerRadius: 4))
}
private var connectionChipText: String {
if isCameraConnected {
let name = cameraDeviceName.isEmpty ? "已连接相机" : cameraDeviceName
return name
}
return "未连接 USB"
}
private var refreshButton: some View {
Button(action: onRefreshCameraFiles) {
Text(refreshButtonTitle)
.font(.system(size: 12))
.foregroundStyle(.white)
.padding(.horizontal, 10)
.frame(minWidth: 64)
.frame(height: 34)
.background(refreshButtonColor)
.clipShape(RoundedRectangle(cornerRadius: 8))
}
.buttonStyle(.plain)
.disabled(!isCameraConnected || isRefreshingCameraFiles)
.opacity(isCameraConnected && !isRefreshingCameraFiles ? 1 : 0.85)
}
private var refreshButtonTitle: String {
if isRefreshingCameraFiles { return "读取中" }
if isCameraConnected { return "刷新" }
return "等待连接"
}
private var refreshButtonColor: Color {
if !isCameraConnected { return WiredTransferDesign.danger }
if isRefreshingCameraFiles { return WiredTransferDesign.text999 }
return AppDesign.primary
}
private var helpDocLine: some View {
Button(action: onHelpClick) {
HStack(spacing: 0) {
Text("未连接,查看")
.foregroundStyle(WiredTransferDesign.text666)
Text("《帮助文档》")
.foregroundStyle(AppDesign.primary)
}
.font(.system(size: 10))
.frame(maxWidth: .infinity, alignment: .leading)
}
.buttonStyle(.plain)
}
private var filterChipRow: some View {
GeometryReader { geometry in
let totalWeight: CGFloat = 1 + 0.72 + 1.15
let spacing: CGFloat = 6
let available = geometry.size.width - spacing * 2
HStack(spacing: spacing) {
WiredTransferFilterChip(label: "不修图", options: [], enabled: false, onSelected: { _ in })
.frame(width: available * 1 / totalWeight)
WiredTransferFilterChip(
label: WiredCameraTransferViewModel.formatJPG,
options: [],
enabled: false,
onSelected: { _ in }
)
.frame(width: available * 0.72 / totalWeight)
WiredTransferFilterChip(
label: transferModeOption,
options: modeOptions,
onSelected: onTransferModeSelected
)
.frame(width: available * 1.15 / totalWeight)
}
}
.frame(height: WiredTransferDesign.filterChipHeight)
}
}
/// Tab Android TransferStatsTabRow
struct WiredTransferStatsTabRow: View {
let tabTitles: [String]
let counts: [Int]
let selectedTabIndex: Int
let onTabSelected: (Int) -> Void
var body: some View {
HStack(spacing: 0) {
ForEach(Array(tabTitles.enumerated()), id: \.offset) { index, title in
if index > 0 {
Divider()
.frame(height: 32)
.background(WiredTransferDesign.borderLight)
}
tabItem(index: index, title: title)
}
}
.frame(height: WiredTransferDesign.tabRowHeight)
}
private func tabItem(index: Int, title: String) -> some View {
Button {
onTabSelected(index)
} label: {
VStack(spacing: 2) {
Text("\(counts.indices.contains(index) ? counts[index] : 0)")
.font(.system(size: 18, weight: .semibold))
.foregroundStyle(countColor(for: index))
Text(title)
.font(.system(size: 13))
.foregroundStyle(selectedTabIndex == index ? AppDesign.primary : WiredTransferDesign.text666)
RoundedRectangle(cornerRadius: 1)
.fill(selectedTabIndex == index ? AppDesign.primary : Color.clear)
.frame(width: 24, height: 2)
.padding(.top, 2)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 8)
}
.buttonStyle(.plain)
}
private func countColor(for index: Int) -> Color {
if selectedTabIndex == index { return AppDesign.primary }
if index == 2 { return WiredTransferDesign.danger }
return WiredTransferDesign.text333
}
}