From 8f9ff393fdac5e77bf456c42b864f5ff5c17025e Mon Sep 17 00:00:00 2001 From: Casper Zandbergen Date: Sat, 1 Jun 2024 13:40:14 +0200 Subject: [PATCH 1/3] fixed stored @State properties on App --- Sources/Adwaita/Model/User Interface/App/App.swift | 10 +++++++--- Sources/Adwaita/Model/User Interface/App/GTUIApp.swift | 3 +-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Sources/Adwaita/Model/User Interface/App/App.swift b/Sources/Adwaita/Model/User Interface/App/App.swift index 61077e8..7528846 100644 --- a/Sources/Adwaita/Model/User Interface/App/App.swift +++ b/Sources/Adwaita/Model/User Interface/App/App.swift @@ -24,7 +24,8 @@ public protocol App { /// The app's application ID. - var id: String { get } + static var id: String { get } + /// The app's windows. @SceneBuilder var scene: Scene { get } // swiftlint:disable implicitly_unwrapped_optional @@ -39,8 +40,12 @@ public protocol App { extension App { + @available(*, deprecated, message: "The 'id' property is removed. Please use the new static id instead.") + var id: String { Self.id } + /// The application's entry point. public static func main() { + GTUIApp.appID = Self.id let app = setupApp() app.run() } @@ -50,7 +55,7 @@ extension App { /// To run the app, call the ``GTUIApp/run(automaticSetup:manualSetup:)`` function. public static func setupApp() -> GTUIApp { var appInstance = self.init() - appInstance.app = GTUIApp(appInstance.id) { appInstance } + appInstance.app = GTUIApp(Self.id) { appInstance } GTUIApp.updateHandlers.append { force in var removeIndices: [Int] = [] for (index, window) in appInstance.app.sceneStorage.enumerated() { @@ -64,7 +69,6 @@ extension App { appInstance.app.sceneStorage.remove(at: index) } } - GTUIApp.appID = appInstance.id return appInstance.app } diff --git a/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift b/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift index 3fc9530..5ae3c10 100644 --- a/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift +++ b/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift @@ -13,8 +13,7 @@ public class GTUIApp { /// The handlers which are called when a state changes. static var updateHandlers: [(Bool) -> Void] = [] /// The app's id for the file name for storing the data. - static var appID = "temporary" - + static var appID: String! /// The pointer to the application. public var pointer: UnsafeMutablePointer? /// Fields for additional information. From 7a52948e0aae244da4ca205b8ff44668b8af26d1 Mon Sep 17 00:00:00 2001 From: Casper Zandbergen Date: Sat, 1 Jun 2024 13:41:57 +0200 Subject: [PATCH 2/3] Update Contributors.md --- Contributors.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Contributors.md b/Contributors.md index f7c4a7d..a8d29d2 100644 --- a/Contributors.md +++ b/Contributors.md @@ -4,3 +4,4 @@ - [Greg Cotten](https://github.com/gregcotten) - [Zev Eisenberg](https://github.com/ZevEisenberg) - [Jay Wren](https://github.com/jrwren) +- [Amzd](https://github.com/amzd) From 15cafb85f47588c1580edc38aaf9dbed0dd6adfc Mon Sep 17 00:00:00 2001 From: david-swift Date: Tue, 11 Jun 2024 14:29:46 +0200 Subject: [PATCH 3/3] Move to approach without any breaking changes Read the file's value as soon as the state value is accessed for the first time --- Sources/Adwaita/Model/Data Flow/State.swift | 59 +++++++++++++------ .../Model/User Interface/App/App.swift | 9 +-- .../Model/User Interface/App/GTUIApp.swift | 2 +- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/Sources/Adwaita/Model/Data Flow/State.swift b/Sources/Adwaita/Model/Data Flow/State.swift index c47f92b..842b9b2 100644 --- a/Sources/Adwaita/Model/Data Flow/State.swift +++ b/Sources/Adwaita/Model/Data Flow/State.swift @@ -37,7 +37,13 @@ public struct State: StateProtocol { /// Get and set the value without updating the views. public var rawValue: Value { get { - content.storage.value as! Value + if let readValue, !content.fetched { + let newValue = readValue() + content.storage.value = newValue + content.fetched = true + return newValue + } + return content.storage.value as! Value } nonmutating set { content.storage.value = newValue @@ -54,6 +60,8 @@ public struct State: StateProtocol { /// The function for updating the value in the settings file. private var writeValue: (() -> Void)? + /// The function for reading the value in the settings file. + var readValue: (() -> Value)? /// The value with an erased type. public var value: Any { @@ -97,6 +105,8 @@ public struct State: StateProtocol { var internalStorage: Storage? /// The initial value. private var initialValue: Value + /// Whether the value has already been fetched from the file stored on the system. + var fetched = false /// Initialize the content. /// - Parameter storage: The storage. @@ -159,15 +169,18 @@ public struct State: StateProtocol { /// Get the settings directory path. /// - Returns: The path. - private func dirPath() -> URL { - Self.userDataDir() - .appendingPathComponent(content.storage.folder ?? GTUIApp.appID, isDirectory: true) + private func dirPath() -> URL? { + guard let folder = content.storage.folder ?? GTUIApp.appID else { + return nil + } + return Self.userDataDir() + .appendingPathComponent(folder, isDirectory: true) } /// Get the settings file path. /// - Returns: The path. - private func filePath() -> URL { - dirPath().appendingPathComponent("\(content.storage.key ?? "temporary").json") + private func filePath() -> URL? { + dirPath()?.appendingPathComponent("\(content.storage.key ?? "temporary").json") } } @@ -187,32 +200,41 @@ extension State where Value: Codable { self.forceUpdates = forceUpdates content.storage.key = key content.storage.folder = folder - checkFile() - readValue() + let value = self + self.readValue = { + value.checkFile() + return value.readActualValue() ?? wrappedValue + } self.writeValue = writeCodableValue } /// Check whether the settings file exists, and, if not, create it. private func checkFile() { let fileManager = FileManager.default - if !fileManager.fileExists(atPath: dirPath().path) { + guard let dirPath = dirPath(), let filePath = filePath() else { + print("Stored state value accessed before initializing app id.") + return + } + if !fileManager.fileExists(atPath: dirPath.path) { try? fileManager.createDirectory( - at: .init(fileURLWithPath: dirPath().path), + at: .init(fileURLWithPath: dirPath.path), withIntermediateDirectories: true, attributes: nil ) } - if !fileManager.fileExists(atPath: filePath().path) { - fileManager.createFile(atPath: filePath().path, contents: .init(), attributes: nil) + if !fileManager.fileExists(atPath: filePath.path) { + fileManager.createFile(atPath: filePath.path, contents: .init(), attributes: nil) } } /// Update the local value with the value from the file. - private func readValue() { - let data = try? Data(contentsOf: filePath()) - if let data, let value = try? JSONDecoder().decode(Value.self, from: data) { - rawValue = value + private func readActualValue() -> Value? { + if let filePath = filePath(), + let data = try? Data(contentsOf: filePath), + let value = try? JSONDecoder().decode(Value.self, from: data) { + return value } + return nil } /// Update the value on the file with the local value. @@ -220,7 +242,10 @@ extension State where Value: Codable { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted let data = try? encoder.encode(rawValue) - try? data?.write(to: filePath()) + guard let filePath = filePath() else { + return + } + try? data?.write(to: filePath) } } diff --git a/Sources/Adwaita/Model/User Interface/App/App.swift b/Sources/Adwaita/Model/User Interface/App/App.swift index 7528846..208d503 100644 --- a/Sources/Adwaita/Model/User Interface/App/App.swift +++ b/Sources/Adwaita/Model/User Interface/App/App.swift @@ -24,7 +24,7 @@ public protocol App { /// The app's application ID. - static var id: String { get } + var id: String { get } /// The app's windows. @SceneBuilder var scene: Scene { get } @@ -40,12 +40,8 @@ public protocol App { extension App { - @available(*, deprecated, message: "The 'id' property is removed. Please use the new static id instead.") - var id: String { Self.id } - /// The application's entry point. public static func main() { - GTUIApp.appID = Self.id let app = setupApp() app.run() } @@ -55,7 +51,7 @@ extension App { /// To run the app, call the ``GTUIApp/run(automaticSetup:manualSetup:)`` function. public static func setupApp() -> GTUIApp { var appInstance = self.init() - appInstance.app = GTUIApp(Self.id) { appInstance } + appInstance.app = GTUIApp(appInstance.id) { appInstance } GTUIApp.updateHandlers.append { force in var removeIndices: [Int] = [] for (index, window) in appInstance.app.sceneStorage.enumerated() { @@ -69,6 +65,7 @@ extension App { appInstance.app.sceneStorage.remove(at: index) } } + GTUIApp.appID = appInstance.id return appInstance.app } diff --git a/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift b/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift index 5ae3c10..1007d1b 100644 --- a/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift +++ b/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift @@ -13,7 +13,7 @@ public class GTUIApp { /// The handlers which are called when a state changes. static var updateHandlers: [(Bool) -> Void] = [] /// The app's id for the file name for storing the data. - static var appID: String! + static var appID: String? /// The pointer to the application. public var pointer: UnsafeMutablePointer? /// Fields for additional information.