- GalleryItem: unified model with phone/nas/synced source, sync state enum - GalleryViewModel: off-actor PHFetchResult + manifest merge, Phone/NAS/All tab filtering, date section grouping, Combine-driven reactivity - GalleryView: sticky date section headers, 3-col grid, shimmer placeholders, sync dots (green/orange/blue), video duration badge, search bar, context menu (Share, Save to Photos, Delete from Device) - GalleryDetailView: full-screen swipeable photo viewer (TabView page mode), pinch-to-zoom, double-tap zoom toggle, bottom metadata sheet with file info and actions - BackupStatusService: expose lastManifest for gallery deduplication without extra NAS connection - ThumbnailCache: add decodeOffThread static helper for off-actor JPEG decode - BackupManifest: Sendable conformance for safe Task.detached capture - ThumbnailCache: synced items load from PHImageManager (instant) instead of NAS Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
48 lines
1.3 KiB
Swift
48 lines
1.3 KiB
Swift
import Foundation
|
|
|
|
struct GalleryItem: Identifiable, Sendable {
|
|
let id: String
|
|
let filename: String
|
|
let creationDate: Date?
|
|
let duration: TimeInterval? // non-nil for video
|
|
let fileSize: Int64?
|
|
let mediaType: MediaType
|
|
let source: ItemSource
|
|
|
|
enum MediaType: Sendable { case photo, video, unknown }
|
|
|
|
enum ItemSource: Sendable {
|
|
case phone(localIdentifier: String)
|
|
case nas(path: String)
|
|
case synced(localIdentifier: String, nasPath: String)
|
|
|
|
var localIdentifier: String? {
|
|
switch self { case .phone(let id), .synced(let id, _): id; case .nas: nil }
|
|
}
|
|
var nasPath: String? {
|
|
switch self { case .nas(let p), .synced(_, let p): p; case .phone: nil }
|
|
}
|
|
}
|
|
|
|
enum SyncState: Sendable {
|
|
case phoneOnly // orange — backed up soon
|
|
case nasOnly // blue — on NAS, not this device
|
|
case synced // green — confirmed both places
|
|
}
|
|
|
|
var syncState: SyncState {
|
|
switch source {
|
|
case .phone: .phoneOnly
|
|
case .nas: .nasOnly
|
|
case .synced: .synced
|
|
}
|
|
}
|
|
}
|
|
|
|
struct GallerySection: Identifiable {
|
|
var id: String // "2026-05-17" or "unknown"
|
|
var date: Date
|
|
var title: String
|
|
var items: [GalleryItem]
|
|
}
|