Initial scaffold: context-aware notes app for iOS + macOS
XcodeGen project (Notes / NotesMac / NotesTests), Swift 6 strict concurrency, iCloud-document storage via IndieSync with a SwiftData cache, automatic capture context (location, place, time zone, device) on every note, context-based relevance ranking with a 'Right here, right now' shelf, tags, search, and the square.and.pencil-on-red app icon.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
/// Rebuildable cache row for one note. Holds nothing that isn't also, more
|
||||
/// authoritatively, in the note's JSON file — the metadata observer (via
|
||||
/// `NoteMapper`) is the only writer.
|
||||
@Model
|
||||
final class NoteEntity {
|
||||
@Attribute(.unique) var id: String = ""
|
||||
var text: String = ""
|
||||
var sourceRaw: String = Note.Source.typed.rawValue
|
||||
var tags: [String] = []
|
||||
var createdAt: Date = Date()
|
||||
var modifiedAt: Date = Date()
|
||||
|
||||
/// Back-pointer to the authoritative file, so a "removed" event can find
|
||||
/// the row to delete by path when it lacks the id.
|
||||
var jsonRelativePath: String = ""
|
||||
|
||||
// Flattened CaptureContext — @Model can't store a nested Codable struct
|
||||
// as one column; call sites use the `context` computed property instead.
|
||||
var latitude: Double?
|
||||
var longitude: Double?
|
||||
var horizontalAccuracy: Double?
|
||||
var placeName: String?
|
||||
var thoroughfare: String?
|
||||
var locality: String?
|
||||
var administrativeArea: String?
|
||||
var country: String?
|
||||
var timeZoneID: String?
|
||||
var device: String?
|
||||
|
||||
init(id: String, jsonRelativePath: String) {
|
||||
self.id = id
|
||||
self.jsonRelativePath = jsonRelativePath
|
||||
}
|
||||
}
|
||||
|
||||
extension NoteEntity {
|
||||
var context: CaptureContext {
|
||||
get {
|
||||
CaptureContext(
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
horizontalAccuracy: horizontalAccuracy,
|
||||
placeName: placeName,
|
||||
thoroughfare: thoroughfare,
|
||||
locality: locality,
|
||||
administrativeArea: administrativeArea,
|
||||
country: country,
|
||||
timeZoneID: timeZoneID,
|
||||
device: device
|
||||
)
|
||||
}
|
||||
set {
|
||||
latitude = newValue.latitude
|
||||
longitude = newValue.longitude
|
||||
horizontalAccuracy = newValue.horizontalAccuracy
|
||||
placeName = newValue.placeName
|
||||
thoroughfare = newValue.thoroughfare
|
||||
locality = newValue.locality
|
||||
administrativeArea = newValue.administrativeArea
|
||||
country = newValue.country
|
||||
timeZoneID = newValue.timeZoneID
|
||||
device = newValue.device
|
||||
}
|
||||
}
|
||||
|
||||
var source: Note.Source {
|
||||
Note.Source(rawValue: sourceRaw) ?? .typed
|
||||
}
|
||||
|
||||
/// First line of the text, used as the display title.
|
||||
var title: String {
|
||||
text.split(separator: "\n", omittingEmptySubsequences: true)
|
||||
.first.map(String.init) ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
extension PersistentModel {
|
||||
/// Safe-to-read guard for objects reached through `@Query` or a
|
||||
/// relationship during a rebuild/sync burst. `isDeleted` alone only
|
||||
/// covers the unsaved window.
|
||||
var isLive: Bool { modelContext != nil && !isDeleted }
|
||||
}
|
||||
Reference in New Issue
Block a user