对齐有线传图页 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,120 @@
//
// WiredTransferPhotoRow.swift
// suixinkan
//
import SwiftUI
import UIKit
/// 线
struct WiredTransferPhotoRow: View {
let photo: WiredTransferPhotoItem
let selectUploadMode: Bool
let selected: Bool
let onToggleSelection: () -> Void
let onPhotoClick: () -> Void
let onRetry: () -> Void
let onDelete: () -> Void
private var canSelect: Bool { photo.canSelectForSpecifyUpload }
private var canRetry: Bool { photo.status == .failed || photo.status == .pending }
private var rowOpacity: Double { selectUploadMode && !canSelect ? 0.45 : 1 }
var body: some View {
VStack(spacing: 0) {
HStack(alignment: .center, spacing: 8) {
if selectUploadMode {
Button(action: onToggleSelection) {
WiredTransferSelectIndicator(selected: selected, enabled: canSelect)
}
.buttonStyle(.plain)
.disabled(!canSelect)
}
thumbnailView
.frame(width: WiredTransferDesign.thumbnailSize, height: WiredTransferDesign.thumbnailSize)
.clipShape(RoundedRectangle(cornerRadius: WiredTransferDesign.thumbnailCornerRadius))
.opacity(rowOpacity)
.onTapGesture {
if !selectUploadMode { onPhotoClick() }
}
VStack(alignment: .leading, spacing: 3) {
HStack(spacing: 4) {
Text(photo.fileName)
.font(.system(size: 12, weight: .medium))
.foregroundStyle(WiredTransferDesign.text333.opacity(rowOpacity))
.lineLimit(1)
Spacer(minLength: 0)
WiredTransferStatusBadge(status: photo.status)
.opacity(rowOpacity)
}
Text(metaText)
.font(.system(size: 10))
.foregroundStyle(WiredTransferDesign.text999.opacity(rowOpacity))
.lineLimit(1)
}
if !selectUploadMode {
Menu {
Button("重传") { onRetry() }
.disabled(!canRetry)
Button("删除", role: .destructive) { onDelete() }
} label: {
Image(systemName: "ellipsis")
.font(.system(size: 16))
.foregroundStyle(WiredTransferDesign.text666)
.frame(width: 32, height: 32)
.rotationEffect(.degrees(90))
}
}
}
.padding(.horizontal, 10)
.padding(.vertical, 8)
.contentShape(Rectangle())
.onTapGesture {
if selectUploadMode, canSelect { onToggleSelection() }
}
if photo.status == .transferring || photo.status == .uploading {
ProgressView(value: Double(photo.progress), total: 100)
.tint(AppDesign.primary)
.frame(height: WiredTransferDesign.progressBarHeight)
.padding(.horizontal, 10)
.padding(.bottom, 8)
}
}
}
private var metaText: String {
if photo.resolutionText.isEmpty { return photo.fileSizeText }
return "\(photo.fileSizeText) \(photo.resolutionText)"
}
@ViewBuilder
private var thumbnailView: some View {
if photo.thumbnailURL.hasPrefix("file://"), let url = URL(string: photo.thumbnailURL) {
if let uiImage = UIImage(contentsOfFile: url.path) {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
} else {
thumbnailPlaceholder
}
} else if !photo.thumbnailURL.isEmpty {
RemoteImage(urlString: photo.thumbnailURL) {
thumbnailPlaceholder
}
} else {
thumbnailPlaceholder
}
}
private var thumbnailPlaceholder: some View {
WiredTransferDesign.pageBackground
.overlay {
Image(systemName: "photo")
.foregroundStyle(WiredTransferDesign.text999)
}
}
}