forked from aparoksha/adwaita-swift
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)
|
- [MenuItem](extensions/MenuItem.md)
|
||||||
- [MenuItemGroup](extensions/MenuItemGroup.md)
|
- [MenuItemGroup](extensions/MenuItemGroup.md)
|
||||||
- [NativeWidgetPeer](extensions/NativeWidgetPeer.md)
|
- [NativeWidgetPeer](extensions/NativeWidgetPeer.md)
|
||||||
|
- [State](extensions/State.md)
|
||||||
- [String](extensions/String.md)
|
- [String](extensions/String.md)
|
||||||
- [View](extensions/View.md)
|
- [View](extensions/View.md)
|
||||||
- [Widget](extensions/Widget.md)
|
- [Widget](extensions/Widget.md)
|
||||||
|
|||||||
@ -9,6 +9,10 @@ The GTUI application.
|
|||||||
|
|
||||||
The handlers which are called when a state changes.
|
The handlers which are called when a state changes.
|
||||||
|
|
||||||
|
### `appID`
|
||||||
|
|
||||||
|
The app's id for the file name for storing the data.
|
||||||
|
|
||||||
### `body`
|
### `body`
|
||||||
|
|
||||||
The app's content.
|
The app's content.
|
||||||
|
|||||||
@ -9,6 +9,10 @@ A class storing the value.
|
|||||||
|
|
||||||
The stored value.
|
The stored value.
|
||||||
|
|
||||||
|
### `key`
|
||||||
|
|
||||||
|
The storage key.
|
||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
### `init(value:)`
|
### `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:)`
|
### `getModified(modifiers:)`
|
||||||
|
|
||||||
|
### `inspectOnAppear(_:)`
|
||||||
|
|
||||||
|
Run a function on the widget when it appears for the first time.
|
||||||
|
- Parameter closure: The function.
|
||||||
|
- Returns: A view.
|
||||||
|
|
||||||
### `onAppear(_:)`
|
### `onAppear(_:)`
|
||||||
|
|
||||||
Run a function when the view appears for the first time.
|
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.
|
The stored value.
|
||||||
|
|
||||||
|
### `writeValue`
|
||||||
|
|
||||||
|
The function for updating the value in the settings file.
|
||||||
|
|
||||||
### `value`
|
### `value`
|
||||||
|
|
||||||
The value with an erased type.
|
The value with an erased type.
|
||||||
@ -35,3 +39,13 @@ Initialize a property representing a state in the view.
|
|||||||
### `updateViews()`
|
### `updateViews()`
|
||||||
|
|
||||||
Update all of the views.
|
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 {
|
nonmutating set {
|
||||||
content.storage.value = newValue
|
content.storage.value = newValue
|
||||||
|
writeValue?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// swiftlint:enable force_cast
|
// swiftlint:enable force_cast
|
||||||
@ -46,6 +47,9 @@ public struct State<Value>: StateProtocol {
|
|||||||
/// The stored value.
|
/// The stored value.
|
||||||
public let content: State<Any>.Content
|
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.
|
/// The value with an erased type.
|
||||||
public var value: Any {
|
public var value: Any {
|
||||||
get {
|
get {
|
||||||
@ -84,6 +88,8 @@ public struct State<Value>: StateProtocol {
|
|||||||
|
|
||||||
/// The stored value.
|
/// The stored value.
|
||||||
public var value: Any
|
public var value: Any
|
||||||
|
/// The storage key.
|
||||||
|
public var key: String?
|
||||||
|
|
||||||
/// Initialize the storage.
|
/// Initialize the storage.
|
||||||
/// - Parameters:
|
/// - 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)
|
appInstance.app.sceneStorage.remove(at: index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
GTUIApp.appID = appInstance.id
|
||||||
appInstance.app.run()
|
appInstance.app.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,8 @@ public class GTUIApp: Application {
|
|||||||
|
|
||||||
/// The handlers which are called when a state changes.
|
/// The handlers which are called when a state changes.
|
||||||
static var updateHandlers: [() -> Void] = []
|
static var updateHandlers: [() -> Void] = []
|
||||||
|
/// The app's id for the file name for storing the data.
|
||||||
|
static var appID = "temporary"
|
||||||
|
|
||||||
/// The app's content.
|
/// The app's content.
|
||||||
var body: () -> App
|
var body: () -> App
|
||||||
|
|||||||
@ -12,7 +12,8 @@ import Libadwaita
|
|||||||
|
|
||||||
struct CounterDemo: View {
|
struct CounterDemo: View {
|
||||||
|
|
||||||
@State private var count = 0
|
@State("count")
|
||||||
|
private var count = 0
|
||||||
|
|
||||||
var view: Body {
|
var view: Body {
|
||||||
VStack {
|
VStack {
|
||||||
|
|||||||
@ -55,9 +55,11 @@ struct Demo: App {
|
|||||||
|
|
||||||
struct DemoContent: View {
|
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 toast: Signal = .init()
|
||||||
@State private var sidebarVisible = true
|
@State("sidebar-visible")
|
||||||
|
private var sidebarVisible = true
|
||||||
var window: GTUIApplicationWindow
|
var window: GTUIApplicationWindow
|
||||||
var app: GTUIApp!
|
var app: GTUIApp!
|
||||||
|
|
||||||
@ -75,8 +77,12 @@ struct Demo: App {
|
|||||||
HeaderBar.end {
|
HeaderBar.end {
|
||||||
menu
|
menu
|
||||||
}
|
}
|
||||||
|
.headerBarTitle {
|
||||||
|
Text("Demo")
|
||||||
|
.style("heading")
|
||||||
|
.transition(.crossfade)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Demo")
|
|
||||||
} content: {
|
} content: {
|
||||||
StatusPage(
|
StatusPage(
|
||||||
selection.label,
|
selection.label,
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
import Adwaita
|
import Adwaita
|
||||||
import Libadwaita
|
import Libadwaita
|
||||||
|
|
||||||
enum Page: String, Identifiable, CaseIterable {
|
enum Page: String, Identifiable, CaseIterable, Codable {
|
||||||
|
|
||||||
case welcome
|
case welcome
|
||||||
case counter
|
case counter
|
||||||
|
|||||||
@ -133,3 +133,12 @@ struct ChangeTextView: View {
|
|||||||
|
|
||||||
Whenever you modify a state property (directly or indirectly through bindings),
|
Whenever you modify a state property (directly or indirectly through bindings),
|
||||||
the user interface gets automatically updated to reflect that change.
|
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