Skip to content

Commit 0f5b969

Browse files
committed
Add POC for LocationCacheStore
1 parent 9b3df0f commit 0f5b969

6 files changed

Lines changed: 229 additions & 15 deletions

File tree

Package.resolved

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,32 @@ import PackageDescription
55

66
let package = Package(
77
name: "LocationCacheStore",
8+
platforms: [
9+
.iOS(.v14),
10+
.macOS(.v11),
11+
.watchOS(.v7)
12+
],
813
products: [
914
// Products define the executables and libraries a package produces, and make them visible to other packages.
1015
.library(
1116
name: "LocationCacheStore",
12-
targets: ["LocationCacheStore"]),
17+
targets: ["LocationCacheStore"]
18+
)
1319
],
1420
dependencies: [
1521
// Dependencies declare other packages that this package depends on.
16-
// .package(url: /* package url */, from: "1.0.0"),
22+
.package(url: "https://github.com/0xOpenBytes/CacheStore", from: "3.2.0")
1723
],
1824
targets: [
1925
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
2026
// Targets can depend on other targets in this package, and on products in packages this package depends on.
2127
.target(
2228
name: "LocationCacheStore",
23-
dependencies: []),
29+
dependencies: ["CacheStore"]
30+
),
2431
.testTarget(
2532
name: "LocationCacheStoreTests",
26-
dependencies: ["LocationCacheStore"]),
33+
dependencies: ["LocationCacheStore"]
34+
)
2735
]
2836
)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# LocationCacheStore
22

3-
A description of this package.
3+
## 🚧

Sources/LocationCacheStore/LocationCacheStore.swift

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import CacheStore
2+
import CoreLocation
3+
4+
/// `LocationStore` Keys
5+
/// - location: CLLocation
6+
public enum LocationStoreKey {
7+
case location // CLLocation
8+
}
9+
10+
/// `LocationStore` Actions
11+
public enum LocationStoreAction {
12+
case updateLocation
13+
case locationUpdated(CLLocation?)
14+
}
15+
16+
/// `LocationStore` Dependency
17+
public struct LocationStoreDependency {
18+
var updateLocation: () async -> CLLocation?
19+
}
20+
21+
public extension LocationStoreDependency {
22+
/// Mock Dependency
23+
static func mock(location: CLLocation) -> LocationStoreDependency {
24+
LocationStoreDependency(
25+
updateLocation: { location }
26+
)
27+
}
28+
29+
/// Live Dependency using CLLocationManager and requestWhenInUseAuthorization if authorizationStatus == .notDetermined
30+
static var live: LocationStoreDependency {
31+
LocationStoreDependency(
32+
updateLocation: {
33+
let locationManager = CLLocationManager()
34+
35+
if locationManager.authorizationStatus == .notDetermined {
36+
locationManager.requestWhenInUseAuthorization()
37+
}
38+
39+
return locationManager.location
40+
}
41+
)
42+
}
43+
}
44+
45+
/// Higher-order StoreActionHandler
46+
public func locationStoreActionHandler(
47+
withActionHandler actionHandler: StoreActionHandler<LocationStoreKey, LocationStoreAction, LocationStoreDependency>? = nil
48+
) -> StoreActionHandler<LocationStoreKey, LocationStoreAction, LocationStoreDependency> {
49+
StoreActionHandler<LocationStoreKey, LocationStoreAction, LocationStoreDependency> { cacheStore, action, dependency in
50+
switch action {
51+
case .updateLocation:
52+
struct UpdateLocationActionEffectID: Hashable { }
53+
54+
return ActionEffect(id: UpdateLocationActionEffectID()) {
55+
.locationUpdated(await dependency.updateLocation())
56+
}
57+
58+
case let .locationUpdated(location):
59+
cacheStore.set(value: location, forKey: .location)
60+
}
61+
62+
guard
63+
let actionHandler = actionHandler,
64+
let nextAction = actionHandler.handle(store: &cacheStore, action: action, dependency: dependency)
65+
else {
66+
struct RepeatUpdateLocationActionEffectID: Hashable { }
67+
68+
return ActionEffect(id: RepeatUpdateLocationActionEffectID()) {
69+
sleep(1)
70+
return .updateLocation
71+
}
72+
}
73+
74+
return nextAction
75+
}
76+
}
77+
78+
/// LocationStore that updates the user's location
79+
public class LocationStore: Store<LocationStoreKey, LocationStoreAction, LocationStoreDependency> {
80+
/// Create a LocationStore with an ActionHandler and Dependency
81+
public init(
82+
withActionHandler actionHandler: StoreActionHandler<LocationStoreKey, LocationStoreAction, LocationStoreDependency>? = nil,
83+
dependency: LocationStoreDependency = .live
84+
) {
85+
super.init(
86+
initialValues: [:],
87+
actionHandler: locationStoreActionHandler(withActionHandler: actionHandler),
88+
dependency: dependency
89+
)
90+
91+
handle(action: .updateLocation)
92+
}
93+
94+
/// Create a LocationStore using the Store init
95+
public required init(
96+
initialValues: [LocationStoreKey: Any],
97+
actionHandler: StoreActionHandler<LocationStoreKey, LocationStoreAction, LocationStoreDependency>,
98+
dependency: LocationStoreDependency
99+
) {
100+
super.init(
101+
initialValues: initialValues,
102+
actionHandler: locationStoreActionHandler(withActionHandler: actionHandler),
103+
dependency: dependency
104+
)
105+
106+
handle(action: .updateLocation)
107+
}
108+
}
Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,65 @@
1+
import CacheStore
2+
import CoreLocation
3+
import t
14
import XCTest
25
@testable import LocationCacheStore
36

47
final class LocationCacheStoreTests: XCTestCase {
58
func testExample() throws {
6-
// This is an example of a functional test case.
7-
// Use XCTAssert and related functions to verify your tests produce the correct
8-
// results.
9-
XCTAssertEqual(LocationCacheStore().text, "Hello, World!")
9+
let initLocation = CLLocation()
10+
11+
let locationStore = LocationStore(
12+
initialValues: [:],
13+
actionHandler: locationStoreActionHandler(),
14+
dependency: .mock(location: initLocation)
15+
)
16+
17+
enum ExampleKey {
18+
case location
19+
}
20+
21+
struct ExampleStoreContent: StoreContent {
22+
var store: Store<ExampleKey, Void, Void>
23+
24+
var location: CLLocation {
25+
store.resolve(.location)
26+
}
27+
}
28+
29+
var exampleStore: TestStore<ExampleKey, Void, Void> {
30+
TestStore(
31+
store: locationStore.actionlessScope(
32+
keyTransformation: (
33+
from: { parent in
34+
switch parent {
35+
case .location: return .location
36+
default: return nil
37+
}
38+
},
39+
to: { child in
40+
switch child {
41+
case .location: return .location
42+
default: return nil
43+
}
44+
}
45+
),
46+
dependencyTransformation: { _ in () }
47+
)
48+
)
49+
}
50+
51+
// MARK: - Test
52+
exampleStore.content { (content: ExampleStoreContent) in
53+
try t.assert(content.location.coordinate.latitude, isEqualTo: initLocation.coordinate.latitude)
54+
try t.assert(content.location.coordinate.latitude, isEqualTo: initLocation.coordinate.latitude)
55+
}
56+
57+
let newLocation = CLLocation(latitude: 1, longitude: 1)
58+
locationStore.handle(action: .locationUpdated(newLocation))
59+
60+
exampleStore.content { (content: ExampleStoreContent) in
61+
try t.assert(content.location.coordinate.latitude, isEqualTo: newLocation.coordinate.latitude)
62+
try t.assert(content.location.coordinate.latitude, isEqualTo: newLocation.coordinate.latitude)
63+
}
1064
}
1165
}

0 commit comments

Comments
 (0)