-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
750 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
Sources/AppState/Application/Types/Helper/Application+CloudStateViewModel.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
#if !os(Linux) && !os(Windows) | ||
import Cache | ||
import Combine | ||
import Foundation | ||
import SwiftUI | ||
|
||
extension Application { | ||
public class CloudStateViewModel<Value: Codable & Equatable>: ObservableObject { | ||
@AppDependency(\.icloudDocumentStore) private var cloudDocumentStore: CloudStateStore | ||
|
||
@Published var value: Value? | ||
|
||
private let scope: Scope | ||
private var filePresenter: FilePresenter<CloudStateViewModel<Value>>? | ||
|
||
init(scope: Scope) { | ||
self.scope = scope | ||
Task { | ||
self.filePresenter = await cloudDocumentStore.startMonitoringFile(scope: scope) | ||
self.filePresenter?.observedObject = self | ||
} | ||
|
||
Task { | ||
guard | ||
let viewModel = await cloudDocumentStore.viewModels[scope.key] as? Application.CloudStateViewModel<Value> | ||
else { | ||
await cloudDocumentStore.update(viewModel: self, forKey: scope.key) | ||
|
||
return | ||
} | ||
|
||
await MainActor.run { | ||
value = viewModel.value | ||
} | ||
} | ||
} | ||
|
||
deinit { | ||
guard let filePresenter else { return } | ||
|
||
NSFileCoordinator.removeFilePresenter(filePresenter) | ||
|
||
self.filePresenter = nil | ||
} | ||
|
||
func getValue(cachedValue: Value?) { | ||
Task { | ||
do { | ||
let cloudStoreValue: Value = try await cloudDocumentStore.get(scope) | ||
guard cachedValue != cloudStoreValue else { return } | ||
await MainActor.run { | ||
objectWillChange.send() | ||
|
||
shared.cache.set( | ||
value: Application.State( | ||
type: .cloud, | ||
initial: cloudStoreValue, | ||
scope: scope | ||
), | ||
forKey: scope.key | ||
) | ||
|
||
var hasExternalChangesState: State<Bool> = Application.state(\.hasExternalChanges) | ||
hasExternalChangesState.value = false | ||
|
||
print("HERE: False") | ||
} | ||
} catch { | ||
log( | ||
error: error, | ||
message: "☁️ CloudState Fetching", | ||
fileID: #fileID, | ||
function: #function, | ||
line: #line, | ||
column: #column | ||
) | ||
} | ||
} | ||
} | ||
|
||
func setValue(newValue: Value) { | ||
Task { | ||
do { | ||
try await cloudDocumentStore.set(scope, value: newValue) | ||
} catch { | ||
log( | ||
error: error, | ||
message: "☁️ CloudState Saving", | ||
fileID: #fileID, | ||
function: #function, | ||
line: #line, | ||
column: #column | ||
) | ||
} | ||
} | ||
} | ||
|
||
func removeValue() { | ||
Task { | ||
do { | ||
try await cloudDocumentStore.remove(scope) | ||
} catch { | ||
log( | ||
error: error, | ||
message: "☁️ CloudState Deleting", | ||
fileID: #fileID, | ||
function: #function, | ||
line: #line, | ||
column: #column | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
#endif |
96 changes: 96 additions & 0 deletions
96
Sources/AppState/Application/Types/State/Application+CloudState.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
#if !os(Linux) && !os(Windows) | ||
import Foundation | ||
import SwiftUI | ||
|
||
extension Application { | ||
var icloudDocumentStore: Dependency<CloudStateStore> { | ||
dependency(CloudStateStore()) | ||
} | ||
|
||
var hasExternalChanges: State<Bool> { | ||
state(initial: true) | ||
} | ||
|
||
/// `CloudDocumentState` ... | ||
public struct CloudState<Value: Codable & Equatable>: MutableApplicationState { | ||
public static var emoji: Character { "☁️" } | ||
|
||
@ObservedObject private var viewModel: CloudStateViewModel<Value> | ||
|
||
/// The initial value of the state. | ||
private var initial: () -> Value | ||
|
||
/// The current state value. | ||
public var value: Value { | ||
get { | ||
let cachedValue = shared.cache.get( | ||
scope.key, | ||
as: State<Value>.self | ||
) | ||
|
||
if shared.value(keyPath: \.hasExternalChanges).value { | ||
viewModel.getValue(cachedValue: cachedValue?.value) | ||
} | ||
|
||
if let cachedValue { | ||
return cachedValue.value | ||
} | ||
|
||
return initial() | ||
} | ||
set { | ||
let mirror = Mirror(reflecting: newValue) | ||
|
||
if mirror.displayStyle == .optional, | ||
mirror.children.isEmpty { | ||
shared.cache.remove(scope.key) | ||
|
||
viewModel.removeValue() | ||
} else { | ||
shared.cache.set( | ||
value: Application.State( | ||
type: .cloud, | ||
initial: newValue, | ||
scope: scope | ||
), | ||
forKey: scope.key | ||
) | ||
|
||
viewModel.setValue(newValue: newValue) | ||
} | ||
} | ||
} | ||
|
||
/// The scope in which this state exists. | ||
let scope: Scope | ||
|
||
let isBase64Encoded: Bool | ||
|
||
var path: String { scope.name } | ||
var filename: String { scope.id } | ||
|
||
/** | ||
Creates a new state within a given scope initialized with the provided value. | ||
|
||
- Parameters: | ||
- value: The initial value of the state | ||
- scope: The scope in which the state exists | ||
*/ | ||
init( | ||
initial: @escaping @autoclosure () -> Value, | ||
scope: Scope, | ||
isBase64Encoded: Bool | ||
) { | ||
self.initial = initial | ||
self.scope = scope | ||
self.isBase64Encoded = isBase64Encoded | ||
self.viewModel = CloudStateViewModel(scope: scope) | ||
} | ||
|
||
/// Resets the value to the inital value. If the inital value was `nil`, then the value will be removed from `FileManager` | ||
public mutating func reset() { | ||
value = initial() | ||
} | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.