// // 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 "未连接相机" } 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 } }