Skip and docs
This commit is contained in:
93
pedi-foods/Sources/PediFoods/ViewModel.swift
Normal file
93
pedi-foods/Sources/PediFoods/ViewModel.swift
Normal file
@@ -0,0 +1,93 @@
|
||||
import Foundation
|
||||
import Observation
|
||||
import SkipFuse
|
||||
|
||||
/// The Observable ViewModel used by the application.
|
||||
@Observable public class ViewModel {
|
||||
var items: [Item] = loadItems() {
|
||||
didSet { saveItems() }
|
||||
}
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
func clear() {
|
||||
items.removeAll()
|
||||
}
|
||||
|
||||
func isUpdated(_ item: Item) -> Bool {
|
||||
item != items.first { i in
|
||||
i.id == item.id
|
||||
}
|
||||
}
|
||||
|
||||
func save(item: Item) {
|
||||
items = items.map { i in
|
||||
i.id == item.id ? item : i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An individual item held by the ViewModel
|
||||
struct Item : Identifiable, Hashable, Codable {
|
||||
let id: UUID
|
||||
var date: Date
|
||||
var favorite: Bool
|
||||
var title: String
|
||||
var notes: String
|
||||
|
||||
init(id: UUID = UUID(), date: Date = .now, favorite: Bool = false, title: String = "", notes: String = "") {
|
||||
self.id = id
|
||||
self.date = date
|
||||
self.favorite = favorite
|
||||
self.title = title
|
||||
self.notes = notes
|
||||
}
|
||||
|
||||
var itemTitle: String {
|
||||
!title.isEmpty ? title : dateString
|
||||
}
|
||||
|
||||
var dateString: String {
|
||||
date.formatted(date: .complete, time: .omitted)
|
||||
}
|
||||
|
||||
var dateTimeString: String {
|
||||
date.formatted(date: .abbreviated, time: .shortened)
|
||||
}
|
||||
}
|
||||
|
||||
/// Utilities for defaulting and persising the items in the list
|
||||
extension ViewModel {
|
||||
private static let savePath = URL.applicationSupportDirectory.appendingPathComponent("appdata.json")
|
||||
|
||||
fileprivate static func loadItems() -> [Item] {
|
||||
do {
|
||||
let start = Date.now
|
||||
let data = try Data(contentsOf: savePath)
|
||||
defer {
|
||||
let end = Date.now
|
||||
logger.info("loaded \(data.count) bytes from \(Self.savePath.path) in \(end.timeIntervalSince(start)) seconds")
|
||||
}
|
||||
return try JSONDecoder().decode([Item].self, from: data)
|
||||
} catch {
|
||||
// perhaps the first launch, or the data could not be read
|
||||
logger.warning("failed to load data from \(Self.savePath), using defaultItems: \(error)")
|
||||
let defaultItems = (1...365).map { Date(timeIntervalSinceNow: Double($0 * 60 * 60 * 24 * -1)) }
|
||||
return defaultItems.map({ Item(date: $0) })
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func saveItems() {
|
||||
do {
|
||||
let start = Date.now
|
||||
let data = try JSONEncoder().encode(items)
|
||||
try FileManager.default.createDirectory(at: URL.applicationSupportDirectory, withIntermediateDirectories: true)
|
||||
try data.write(to: Self.savePath)
|
||||
let end = Date.now
|
||||
logger.info("saved \(data.count) bytes to \(Self.savePath.path) in \(end.timeIntervalSince(start)) seconds")
|
||||
} catch {
|
||||
logger.error("error saving data: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user