Add option to save state between app launches
This commit is contained in:
parent
350580269f
commit
021f0f231f
@ -71,6 +71,7 @@
|
||||
- [MenuItem](extensions/MenuItem.md)
|
||||
- [MenuItemGroup](extensions/MenuItemGroup.md)
|
||||
- [NativeWidgetPeer](extensions/NativeWidgetPeer.md)
|
||||
- [State](extensions/State.md)
|
||||
- [String](extensions/String.md)
|
||||
- [View](extensions/View.md)
|
||||
- [Widget](extensions/Widget.md)
|
||||
|
||||
@ -9,6 +9,10 @@ The GTUI application.
|
||||
|
||||
The handlers which are called when a state changes.
|
||||
|
||||
### `appID`
|
||||
|
||||
The app's id for the file name for storing the data.
|
||||
|
||||
### `body`
|
||||
|
||||
The app's content.
|
||||
|
||||
@ -9,6 +9,10 @@ A class storing the value.
|
||||
|
||||
The stored value.
|
||||
|
||||
### `key`
|
||||
|
||||
The storage key.
|
||||
|
||||
## Methods
|
||||
### `init(value:)`
|
||||
|
||||
|
||||
23
Documentation/Reference/extensions/State.md
Normal file
23
Documentation/Reference/extensions/State.md
Normal file
@ -0,0 +1,23 @@
|
||||
**EXTENSION**
|
||||
|
||||
# `State`
|
||||
|
||||
## Methods
|
||||
### `init(wrappedValue:_:)`
|
||||
|
||||
Initialize a property representing a state in the view.
|
||||
- Parameters:
|
||||
- key: The unique storage key of the property.
|
||||
- wrappedValue: The wrapped value.
|
||||
|
||||
### `checkFile()`
|
||||
|
||||
Check whether the settings file exists, and, if not, create it.
|
||||
|
||||
### `readValue()`
|
||||
|
||||
Update the local value with the value from the file.
|
||||
|
||||
### `writeCodableValue()`
|
||||
|
||||
Update the value on the file with the local value.
|
||||
@ -24,6 +24,12 @@ Get a storage.
|
||||
|
||||
### `getModified(modifiers:)`
|
||||
|
||||
### `inspectOnAppear(_:)`
|
||||
|
||||
Run a function on the widget when it appears for the first time.
|
||||
- Parameter closure: The function.
|
||||
- Returns: A view.
|
||||
|
||||
### `onAppear(_:)`
|
||||
|
||||
Run a function when the view appears for the first time.
|
||||
|
||||
@ -21,6 +21,10 @@ Get and set the value without updating the views.
|
||||
|
||||
The stored value.
|
||||
|
||||
### `writeValue`
|
||||
|
||||
The function for updating the value in the settings file.
|
||||
|
||||
### `value`
|
||||
|
||||
The value with an erased type.
|
||||
@ -35,3 +39,13 @@ Initialize a property representing a state in the view.
|
||||
### `updateViews()`
|
||||
|
||||
Update all of the views.
|
||||
|
||||
### `dirPath()`
|
||||
|
||||
Get the settings directory path.
|
||||
- Returns: The path.
|
||||
|
||||
### `filePath()`
|
||||
|
||||
Get the settings file path.
|
||||
- Returns: The path.
|
||||
|
||||
@ -39,6 +39,7 @@ public struct State<Value>: StateProtocol {
|
||||
}
|
||||
nonmutating set {
|
||||
content.storage.value = newValue
|
||||
writeValue?()
|
||||
}
|
||||
}
|
||||
// swiftlint:enable force_cast
|
||||
@ -46,6 +47,9 @@ public struct State<Value>: StateProtocol {
|
||||
/// The stored value.
|
||||
public let content: State<Any>.Content
|
||||
|
||||
/// The function for updating the value in the settings file.
|
||||
private var writeValue: (() -> Void)?
|
||||
|
||||
/// The value with an erased type.
|
||||
public var value: Any {
|
||||
get {
|
||||
@ -84,6 +88,8 @@ public struct State<Value>: StateProtocol {
|
||||
|
||||
/// The stored value.
|
||||
public var value: Any
|
||||
/// The storage key.
|
||||
public var key: String?
|
||||
|
||||
/// Initialize the storage.
|
||||
/// - Parameters:
|
||||
@ -101,4 +107,63 @@ public struct State<Value>: StateProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the settings directory path.
|
||||
/// - Returns: The path.
|
||||
private func dirPath() -> String {
|
||||
"\(NSHomeDirectory())/.config/\(GTUIApp.appID)/"
|
||||
}
|
||||
|
||||
/// Get the settings file path.
|
||||
/// - Returns: The path.
|
||||
private func filePath() -> URL {
|
||||
.init(fileURLWithPath: dirPath() + "\(content.storage.key ?? "temporary").json")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension State where Value: Codable {
|
||||
|
||||
/// Initialize a property representing a state in the view.
|
||||
/// - Parameters:
|
||||
/// - key: The unique storage key of the property.
|
||||
/// - wrappedValue: The wrapped value.
|
||||
public init(wrappedValue: Value, _ key: String) {
|
||||
content = .init(storage: .init(value: wrappedValue))
|
||||
content.storage.key = key
|
||||
checkFile()
|
||||
readValue()
|
||||
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()) {
|
||||
try? fileManager.createDirectory(
|
||||
at: .init(fileURLWithPath: dirPath()),
|
||||
withIntermediateDirectories: true,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the value on the file with the local value.
|
||||
private func writeCodableValue() {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
let data = try? encoder.encode(rawValue)
|
||||
try? data?.write(to: filePath())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -58,6 +58,7 @@ extension App {
|
||||
appInstance.app.sceneStorage.remove(at: index)
|
||||
}
|
||||
}
|
||||
GTUIApp.appID = appInstance.id
|
||||
appInstance.app.run()
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,8 @@ public class GTUIApp: Application {
|
||||
|
||||
/// The handlers which are called when a state changes.
|
||||
static var updateHandlers: [() -> Void] = []
|
||||
/// The app's id for the file name for storing the data.
|
||||
static var appID = "temporary"
|
||||
|
||||
/// The app's content.
|
||||
var body: () -> App
|
||||
|
||||
@ -12,7 +12,8 @@ import Libadwaita
|
||||
|
||||
struct CounterDemo: View {
|
||||
|
||||
@State private var count = 0
|
||||
@State("count")
|
||||
private var count = 0
|
||||
|
||||
var view: Body {
|
||||
VStack {
|
||||
|
||||
@ -55,9 +55,11 @@ struct Demo: App {
|
||||
|
||||
struct DemoContent: View {
|
||||
|
||||
@State private var selection: Page = .welcome
|
||||
@State("selection")
|
||||
private var selection: Page = .welcome
|
||||
@State private var toast: Signal = .init()
|
||||
@State private var sidebarVisible = true
|
||||
@State("sidebar-visible")
|
||||
private var sidebarVisible = true
|
||||
var window: GTUIApplicationWindow
|
||||
var app: GTUIApp!
|
||||
|
||||
@ -75,8 +77,12 @@ struct Demo: App {
|
||||
HeaderBar.end {
|
||||
menu
|
||||
}
|
||||
.headerBarTitle {
|
||||
Text("Demo")
|
||||
.style("heading")
|
||||
.transition(.crossfade)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Demo")
|
||||
} content: {
|
||||
StatusPage(
|
||||
selection.label,
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
import Adwaita
|
||||
import Libadwaita
|
||||
|
||||
enum Page: String, Identifiable, CaseIterable {
|
||||
enum Page: String, Identifiable, CaseIterable, Codable {
|
||||
|
||||
case welcome
|
||||
case counter
|
||||
|
||||
@ -133,3 +133,12 @@ struct ChangeTextView: View {
|
||||
|
||||
Whenever you modify a state property (directly or indirectly through bindings),
|
||||
the user interface gets automatically updated to reflect that change.
|
||||
|
||||
## Save State Values Between App Launches
|
||||
It's possible to automatically save a value that conforms to `Codable` whenever it changes to a file.
|
||||
The value in the file is read when the view containing the state value appears for the first time (e.g. when the user launches the app).
|
||||
|
||||
Use the following syntax, where `"text"` is a unique identifier.
|
||||
```swift
|
||||
@State("text") private var text = "world"
|
||||
```
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user