Skip to content

Commit 88928d2

Browse files
committed
init poc
0 parents  commit 88928d2

File tree

9 files changed

+322
-0
lines changed

9 files changed

+322
-0
lines changed

.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Binary file not shown.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>SchemeUserState</key>
6+
<dict>
7+
<key>ScreenDataNavigation.xcscheme_^#shared#^_</key>
8+
<dict>
9+
<key>orderHint</key>
10+
<integer>0</integer>
11+
</dict>
12+
</dict>
13+
<key>SuppressBuildableAutocreation</key>
14+
<dict>
15+
<key>ScreenDataNavigation</key>
16+
<dict>
17+
<key>primary</key>
18+
<true/>
19+
</dict>
20+
<key>ScreenDataNavigationTests</key>
21+
<dict>
22+
<key>primary</key>
23+
<true/>
24+
</dict>
25+
</dict>
26+
</dict>
27+
</plist>

Package.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// swift-tools-version:5.3
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "ScreenDataNavigation",
8+
platforms: [
9+
.iOS(.v13),
10+
.macOS(.v10_15)
11+
],
12+
products: [
13+
// Products define the executables and libraries a package produces, and make them visible to other packages.
14+
.library(
15+
name: "ScreenDataNavigation",
16+
targets: ["ScreenDataNavigation"]),
17+
],
18+
dependencies: [
19+
// Dependencies declare other packages that this package depends on.
20+
// .package(url: /* package url */, from: "1.0.0"),
21+
.package(name: "ScreenData", url: "https://github.com/ServerDriven/ScreenData-swift", .branch("main"))
22+
],
23+
targets: [
24+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
25+
// Targets can depend on other targets in this package, and on products in packages this package depends on.
26+
.target(
27+
name: "ScreenDataNavigation",
28+
dependencies: [
29+
"ScreenData"
30+
]),
31+
.testTarget(
32+
name: "ScreenDataNavigationTests",
33+
dependencies: [
34+
"ScreenDataNavigation",
35+
"ScreenData"
36+
]),
37+
]
38+
)

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# ScreenDataNavigation
2+
3+
Handling ScreenData's Destinations
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import ScreenData
2+
import Combine
3+
import Foundation
4+
5+
// MARK: ScreenProviding
6+
7+
public protocol ScreenProviding {
8+
func screen(forID id: String) -> Future<SomeScreen, Error>
9+
}
10+
11+
// MARK: ScreenProviding Basic Implementation
12+
13+
public struct MockScreenProvider: ScreenProviding {
14+
public var mockScreen: SomeScreen
15+
16+
public init(mockScreen: SomeScreen) {
17+
self.mockScreen = mockScreen
18+
}
19+
20+
public func screen(forID id: String) -> Future<SomeScreen, Error> {
21+
Future { promise in
22+
var screen = mockScreen
23+
screen.id = id
24+
promise(.success(screen))
25+
}
26+
}
27+
}
28+
29+
public struct URLScreenProvider: ScreenProviding {
30+
public enum URLScreenProviderError: Error {
31+
case noResponse
32+
case noData
33+
}
34+
35+
public var baseURL: URL
36+
37+
public init(baseURL: URL) {
38+
self.baseURL = baseURL
39+
}
40+
41+
public func screen(forID id: String) -> Future<SomeScreen, Error> {
42+
Future { promise in
43+
URLSession.shared.dataTask(with: baseURL.appendingPathComponent(id)) { (data, response, error) in
44+
if let error = error {
45+
promise(.failure(error))
46+
}
47+
48+
guard let _ = response else {
49+
promise(.failure(URLScreenProviderError.noResponse))
50+
return
51+
}
52+
53+
guard let data = data else {
54+
promise(.failure(URLScreenProviderError.noData))
55+
return
56+
}
57+
58+
do {
59+
promise(.success(try JSONDecoder().decode(SomeScreen.self, from: data)))
60+
} catch {
61+
promise(.failure(error))
62+
}
63+
}
64+
}
65+
}
66+
}
67+
68+
public struct UserDefaultScreenProvider: ScreenProviding {
69+
public enum UserDefaultScreenProviderError: Error {
70+
case noData
71+
}
72+
73+
public var baseKey: String
74+
75+
public init(baseKey: String) {
76+
self.baseKey = baseKey
77+
}
78+
79+
public func screen(forID id: String) -> Future<SomeScreen, Error> {
80+
Future { promise in
81+
guard let data = UserDefaults.standard.data(forKey: baseKey + id) else {
82+
promise(.failure(UserDefaultScreenProviderError.noData))
83+
return
84+
}
85+
86+
do {
87+
promise(.success(try JSONDecoder().decode(SomeScreen.self, from: data)))
88+
} catch {
89+
promise(.failure(error))
90+
}
91+
}
92+
}
93+
}
94+
95+
// MARK: ScreenStoring
96+
public protocol ScreenStoring {
97+
func store(screens: [SomeScreen]) -> Future<Void, Error>
98+
}
99+
100+
// MARK: ScreenStoring Basic Implementation
101+
102+
public struct UserDefaultScreenStorer: ScreenStoring {
103+
public var baseKey: String
104+
105+
public init(baseKey: String) {
106+
self.baseKey = baseKey
107+
}
108+
109+
public func store(screens: [SomeScreen]) -> Future<Void, Error> {
110+
Future { promise in
111+
do {
112+
try screens.forEach { screen in
113+
let data = try JSONEncoder().encode(screen)
114+
let key = baseKey + (screen.id ?? "")
115+
UserDefaults.standard.set(data,
116+
forKey: key)
117+
}
118+
promise(.success(()))
119+
} catch {
120+
promise(.failure(error))
121+
}
122+
}
123+
}
124+
}
125+
126+
// MARK: ScreenLoading
127+
public protocol ScreenLoading {
128+
func load(withProvider provider: ScreenProviding) -> Future<[SomeScreen], Error>
129+
}
130+
131+
// MARK: ScreenLoading Basic Implementation [WIP]
132+
//
133+
//extension SomeScreen: ScreenLoading {
134+
//
135+
// func load(withProvider provider: ScreenProviding) -> Future<[SomeScreen], Error> {
136+
// Future { promise in
137+
// let headerViewDestinations = headerView?.destinations ?? []
138+
// let footerViewDestinations = footerView?.destinations ?? []
139+
// let destinations = headerViewDestinations +
140+
// someView.destinations +
141+
// footerViewDestinations
142+
//
143+
// let task = Publishers.MergeMany(
144+
// destinations.filter { destination in
145+
// destination.type == .screen
146+
// }
147+
// .map { destination in
148+
// provider.screen(forID: destination.toID).eraseToAnyPublisher()
149+
// }
150+
// .publisher
151+
// .collect()
152+
// )
153+
//
154+
//
155+
//
156+
//
157+
// }
158+
// }
159+
//}
160+
161+
public extension SomeView {
162+
var destinations: [Destination] {
163+
guard let container = container else {
164+
if let someLabel = someLabel,
165+
let destination = someLabel.destination {
166+
return [destination]
167+
} else if let someImage = someImage,
168+
let destination = someImage.destination {
169+
return [destination]
170+
} else if let someLabeledImage = someLabeledImage {
171+
return [someLabeledImage.destination,
172+
someLabeledImage.someImage.destination]
173+
.compactMap { $0 }
174+
} else if let someCustomView = someCustomView {
175+
let destinations = [someCustomView.destination,
176+
someCustomView.someImage?.destination]
177+
.compactMap { $0 }
178+
let subViewDestinations = someCustomView.views
179+
.map(\.destinations)
180+
.reduce([], +)
181+
182+
return destinations + subViewDestinations
183+
}
184+
185+
186+
return []
187+
}
188+
189+
return container.views
190+
.map(\.destinations)
191+
.reduce([], +)
192+
}
193+
}
194+
195+
public extension SomeScreen {
196+
var destinations: [Destination] {
197+
let headerViewDestinations = headerView?.destinations ?? []
198+
let footerViewDestinations = footerView?.destinations ?? []
199+
200+
return headerViewDestinations +
201+
someView.destinations +
202+
footerViewDestinations
203+
}
204+
}

Tests/LinuxMain.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import XCTest
2+
3+
import ScreenDataNavigationTests
4+
5+
var tests = [XCTestCaseEntry]()
6+
tests += ScreenDataNavigationTests.allTests()
7+
XCTMain(tests)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import XCTest
2+
import ScreenData
3+
@testable import ScreenDataNavigation
4+
5+
final class ScreenDataNavigationTests: XCTestCase {
6+
func testExample() {
7+
let destinations = [
8+
Destination(type: .url, toID: "https://github.com/ServerDriven/ScreenData"),
9+
Destination(type: .screen, toID: "/some/data/5")
10+
]
11+
12+
let screen = SomeScreen(title: "Title",
13+
subtitle: nil,
14+
backgroundColor: SomeColor(red: 0, green: 0, blue: 0),
15+
headerView: SomeView(type: .container, container: SomeContainerView(axis: .vertical, views: [
16+
SomeView(type: .label, someLabel: SomeLabel(title: "Hello World", subtitle: nil, style: nil, destination: Destination(type: .url, toID: "https://github.com/ServerDriven/ScreenData")))
17+
], style: nil)),
18+
someView: SomeView(type: .label, someLabel: SomeLabel(title: "Hello World", subtitle: nil, style: nil, destination: Destination(type: .screen, toID: "/some/data/5"))))
19+
20+
XCTAssertEqual(destinations.map { $0.toID },
21+
screen.destinations.map { $0.toID })
22+
}
23+
24+
static var allTests = [
25+
("testExample", testExample),
26+
]
27+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import XCTest
2+
3+
#if !canImport(ObjectiveC)
4+
public func allTests() -> [XCTestCaseEntry] {
5+
return [
6+
testCase(ScreenDataNavigationTests.allTests),
7+
]
8+
}
9+
#endif

0 commit comments

Comments
 (0)