对齐有线传图页 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,58 @@
//
// DeviceStorageHelper.swift
// suixinkan
//
// Android DeviceStorageHelper
//
import Foundation
import UIKit
/// 线
struct DeviceStorageSnapshot: Equatable {
let modelName: String
let availableGbText: String
}
///
enum DeviceStorageHelper {
/// Documents
static func readStorageSnapshot() -> DeviceStorageSnapshot {
let availableBytes = availableDocumentsBytes()
let availableGb = Double(availableBytes) / 1024.0 / 1024.0 / 1024.0
return DeviceStorageSnapshot(
modelName: buildModelName(),
availableGbText: String(format: "还剩 %.1fGB 可用", availableGb)
)
}
private static func buildModelName() -> String {
var systemInfo = utsname()
uname(&systemInfo)
let machine = withUnsafePointer(to: &systemInfo.machine) {
$0.withMemoryRebound(to: CChar.self, capacity: 1) {
String(cString: $0)
}
}
let marketingName = UIDevice.current.name.trimmingCharacters(in: .whitespacesAndNewlines)
if !marketingName.isEmpty, marketingName != "iPhone", !marketingName.hasPrefix("iPad") {
return marketingName
}
if !machine.isEmpty { return machine }
return UIDevice.current.model
}
private static func availableDocumentsBytes() -> Int64 {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
guard let documentsURL else { return 0 }
let values = try? documentsURL.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
if let capacity = values?.volumeAvailableCapacityForImportantUsage {
return Int64(capacity)
}
if let attributes = try? FileManager.default.attributesOfFileSystem(forPath: documentsURL.path),
let freeSize = attributes[.systemFreeSize] as? NSNumber {
return freeSize.int64Value
}
return 0
}
}

View File

@ -60,20 +60,44 @@ final class WiredTransferPhotoStore {
let records = try? JSONDecoder().decode([WiredTransferPhotoRecord].self, from: data)
else { return [] }
return records
.filter { record in
!deletedIDs.contains(record.id)
&& record.userId == userID
&& record.albumId == albumID
&& record.isDisplayable
}
.map { $0.normalizeInterruptedTransfer() }
let filteredRecords = records.filter { record in
!deletedIDs.contains(record.id)
&& record.userId == userID
&& record.albumId == albumID
}
let normalizedRecords = filteredRecords.map { $0.normalizeInterruptedTransfer() }
let migratedRecords = normalizeStoredPaths(normalizedRecords, albumID: albumID)
return migratedRecords
.filter(\.isDisplayable)
.sorted { $0.capturedAt > $1.capturedAt }
.also { items in
items.forEach { bindPhotoToAlbum(photoID: $0.id, albumID: albumID) }
}
}
///
private func normalizeStoredPaths(
_ records: [WiredTransferPhotoRecord],
albumID: Int
) -> [WiredTransferPhotoRecord] {
var migrated = records
var needsPersist = false
for index in migrated.indices {
guard let normalized = migrated[index].withNormalizedStoredPaths(),
normalized != migrated[index] else { continue }
migrated[index] = normalized
needsPersist = true
}
if needsPersist {
save(albumID: albumID, records: migrated)
}
return migrated
}
///
func save(albumID: Int, records: [WiredTransferPhotoRecord]) {
guard albumID > 0 else { return }
@ -142,7 +166,7 @@ final class WiredTransferPhotoStore {
private func deleteFileIfExists(_ path: String) {
guard !path.isEmpty else { return }
let url = URL(fileURLWithPath: path)
guard let url = CameraDownloadStorage.resolveLocalURL(from: path) else { return }
if FileManager.default.fileExists(atPath: url.path) {
try? FileManager.default.removeItem(at: url)
}
@ -181,7 +205,43 @@ final class WiredTransferPhotoStore {
private extension WiredTransferPhotoRecord {
var isDisplayable: Bool {
!localPath.isEmpty || !remoteURL.isEmpty
if !remoteURL.isEmpty { return true }
if let url = CameraDownloadStorage.resolveLocalURL(from: localPath),
FileManager.default.fileExists(atPath: url.path) {
return true
}
if let url = CameraDownloadStorage.resolveLocalURL(from: thumbnailPath),
FileManager.default.fileExists(atPath: url.path) {
return true
}
return false
}
/// localPath/thumbnailPath
func withNormalizedStoredPaths() -> WiredTransferPhotoRecord? {
let migratedLocalPath = localPath.isEmpty ? localPath : (CameraDownloadStorage.migrateStoredPath(localPath) ?? localPath)
let migratedThumbnailPath = thumbnailPath.isEmpty
? thumbnailPath
: (CameraDownloadStorage.migrateStoredPath(thumbnailPath) ?? thumbnailPath)
guard migratedLocalPath != localPath || migratedThumbnailPath != thumbnailPath else { return nil }
return WiredTransferPhotoRecord(
id: id,
sourceId: sourceId,
fileName: fileName,
localPath: migratedLocalPath,
thumbnailPath: migratedThumbnailPath,
capturedAt: capturedAt,
fileSizeBytes: fileSizeBytes,
status: status,
progress: progress,
errorMessage: errorMessage,
albumId: albumId,
userId: userId,
remoteURL: remoteURL,
updatedAt: updatedAt
)
}
func normalizeInterruptedTransfer() -> WiredTransferPhotoRecord {