对齐有线传图页 Android UI,并改用相对路径持久化本地照片。

还原顶栏、浮动卡片、时间侧栏与底部交互;照片路径改为 CameraDownloads 相对路径并兼容旧数据;格式 Chip 固定 JPG 且不可点击。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-30 11:16:28 +08:00
parent e91a46c315
commit c71b45bdfd
29 changed files with 2097 additions and 256 deletions

View File

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