Skip to content

Commit 286f20b

Browse files
authored
Merge pull request #1 from SwiftRex/SampleApp
Sample app
2 parents 0bfbbf1 + 905bf87 commit 286f20b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1872
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// Counter.h
3+
// Counter
4+
//
5+
// Created by Stephen Celis on 9/8/19.
6+
// Copyright © 2019 Point-Free. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
//! Project version number for Counter.
12+
FOUNDATION_EXPORT double CounterVersionNumber;
13+
14+
//! Project version string for Counter.
15+
FOUNDATION_EXPORT const unsigned char CounterVersionString[];
16+
17+
// In this header, you should import all the public headers of your framework using statements like #import <Counter/PublicHeader.h>
18+
19+
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import Combine
2+
import CombineRex
3+
import PrimeModal
4+
import SwiftRex
5+
import SwiftUI
6+
import Utils
7+
8+
public enum CounterAction: Equatable {
9+
case decrTapped
10+
case incrTapped
11+
case nthPrimeButtonTapped
12+
case nthPrimeResponse(Int?)
13+
case alertDismissButtonTapped
14+
case isPrimeButtonTapped
15+
case primeModalDismissed
16+
}
17+
18+
public typealias CounterState = (
19+
alertNthPrime: PrimeAlert?,
20+
count: Int,
21+
isNthPrimeButtonDisabled: Bool,
22+
isPrimeModalShown: Bool
23+
)
24+
25+
public final class CounterMiddleware: Middleware {
26+
public typealias InputActionType = CounterAction
27+
public typealias OutputActionType = CounterAction
28+
public typealias StateType = Int
29+
30+
private var output: AnyActionHandler<OutputActionType>?
31+
private var getState: GetState<StateType>!
32+
private var cancellables = Set<AnyCancellable>()
33+
34+
public init() {
35+
}
36+
37+
public func receiveContext(getState: @escaping GetState<StateType>, output: AnyActionHandler<OutputActionType>) {
38+
self.getState = getState
39+
self.output = output
40+
}
41+
42+
public func handle(action: InputActionType, from dispatcher: ActionSource, afterReducer: inout AfterReducer) {
43+
switch action {
44+
case .nthPrimeButtonTapped:
45+
afterReducer = .do {
46+
Current.nthPrime(self.getState())
47+
.map(CounterAction.nthPrimeResponse)
48+
.receive(on: DispatchQueue.main)
49+
.sink { [weak self] action in
50+
self?.output?.dispatch(action)
51+
}.store(in: &self.cancellables)
52+
}
53+
54+
case .decrTapped,
55+
.incrTapped,
56+
.nthPrimeResponse,
57+
.alertDismissButtonTapped,
58+
.isPrimeButtonTapped,
59+
.primeModalDismissed:
60+
break
61+
}
62+
}
63+
}
64+
65+
let counterReducer = Reducer<CounterAction, CounterState> { action, state in
66+
var state = state
67+
switch action {
68+
case .decrTapped:
69+
state.count -= 1
70+
return state
71+
case .incrTapped:
72+
state.count += 1
73+
return state
74+
case .nthPrimeButtonTapped:
75+
state.isNthPrimeButtonDisabled = true
76+
return state
77+
case let .nthPrimeResponse(prime):
78+
state.alertNthPrime = prime.map(PrimeAlert.init(prime:))
79+
state.isNthPrimeButtonDisabled = false
80+
return state
81+
case .alertDismissButtonTapped:
82+
state.alertNthPrime = nil
83+
return state
84+
case .isPrimeButtonTapped:
85+
state.isPrimeModalShown = true
86+
return state
87+
case .primeModalDismissed:
88+
state.isPrimeModalShown = false
89+
return state
90+
}
91+
}
92+
93+
struct CounterEnvironment {
94+
var nthPrime: (Int) -> AnyPublisher<Int?, Never>
95+
}
96+
97+
extension CounterEnvironment {
98+
static let live = CounterEnvironment(nthPrime: Counter.nthPrime)
99+
}
100+
101+
var Current = CounterEnvironment.live
102+
103+
extension CounterEnvironment {
104+
static let mock = CounterEnvironment(nthPrime: { _ in .sync { 17 }})
105+
}
106+
107+
import CasePaths
108+
109+
public let counterViewReducer: Reducer<CounterViewAction, CounterViewState> =
110+
counterReducer.lift(
111+
actionGetter: /CounterViewAction.counter,
112+
stateGetter: { $0.counter },
113+
stateSetter: setter(\CounterViewState.counter)
114+
) <> primeModalReducer.lift(
115+
actionGetter: /CounterViewAction.primeModal,
116+
stateGetter: { $0.primeModal },
117+
stateSetter: setter(\CounterViewState.primeModal)
118+
)
119+
120+
public struct PrimeAlert: Equatable, Identifiable {
121+
let prime: Int
122+
public var id: Int { self.prime }
123+
}
124+
125+
public struct CounterViewState: Equatable {
126+
public var alertNthPrime: PrimeAlert?
127+
public var count: Int
128+
public var favoritePrimes: [Int]
129+
public var isNthPrimeButtonDisabled: Bool
130+
public var isPrimeModalShown: Bool
131+
132+
public init(
133+
alertNthPrime: PrimeAlert? = nil,
134+
count: Int = 0,
135+
favoritePrimes: [Int] = [],
136+
isNthPrimeButtonDisabled: Bool = false,
137+
isPrimeModalShown: Bool = false
138+
) {
139+
self.alertNthPrime = alertNthPrime
140+
self.count = count
141+
self.favoritePrimes = favoritePrimes
142+
self.isNthPrimeButtonDisabled = isNthPrimeButtonDisabled
143+
self.isPrimeModalShown = isPrimeModalShown
144+
}
145+
146+
var counter: CounterState {
147+
get { (self.alertNthPrime, self.count, self.isNthPrimeButtonDisabled, self.isPrimeModalShown) }
148+
set { (self.alertNthPrime, self.count, self.isNthPrimeButtonDisabled, self.isPrimeModalShown) = newValue }
149+
}
150+
151+
var primeModal: PrimeModalState {
152+
get { (self.count, self.favoritePrimes) }
153+
set { (self.count, self.favoritePrimes) = newValue }
154+
}
155+
}
156+
157+
public enum CounterViewAction: Equatable {
158+
case counter(CounterAction)
159+
case primeModal(PrimeModalAction)
160+
}
161+
162+
public struct CounterView: View {
163+
@ObservedObject var store: ObservableViewModel<CounterViewAction, CounterViewState>
164+
165+
public init(store: ObservableViewModel<CounterViewAction, CounterViewState>) {
166+
self.store = store
167+
}
168+
169+
public var body: some View {
170+
VStack {
171+
HStack {
172+
Button("-") { self.store.dispatch(.counter(.decrTapped)) }
173+
Text("\(self.store.state.count)")
174+
Button("+") { self.store.dispatch(.counter(.incrTapped)) }
175+
}
176+
Button("Is this prime?") { self.store.dispatch(.counter(.isPrimeButtonTapped)) }
177+
Button("What is the \(ordinal(self.store.state.count)) prime?") {
178+
self.store.dispatch(.counter(.nthPrimeButtonTapped))
179+
}
180+
.disabled(self.store.state.isNthPrimeButtonDisabled)
181+
}
182+
.font(.title)
183+
.navigationBarTitle("Counter demo")
184+
.sheet(
185+
isPresented: .constant(self.store.state.isPrimeModalShown),
186+
onDismiss: { self.store.dispatch(.counter(.primeModalDismissed)) }
187+
) {
188+
IsPrimeModalView(
189+
store: self.store.projection(
190+
action: { .primeModal($0) },
191+
state: { ($0.count, $0.favoritePrimes) }
192+
).asObservableViewModel(
193+
initialState: (0, []),
194+
emitsValue: .when { lhs, rhs in
195+
lhs.count != rhs.count || lhs.favoritePrimes != rhs.favoritePrimes
196+
})
197+
)
198+
}
199+
.alert(
200+
item: .constant(self.store.state.alertNthPrime)
201+
) { alert in
202+
Alert(
203+
title: Text("The \(ordinal(self.store.state.count)) prime is \(alert.prime)"),
204+
dismissButton: .default(Text("Ok")) {
205+
self.store.dispatch(.counter(.alertDismissButtonTapped))
206+
}
207+
)
208+
}
209+
}
210+
}
211+
212+
func ordinal(_ n: Int) -> String {
213+
let formatter = NumberFormatter()
214+
formatter.numberStyle = .ordinal
215+
return formatter.string(for: n) ?? ""
216+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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>CFBundleDevelopmentRegion</key>
6+
<string>$(DEVELOPMENT_LANGUAGE)</string>
7+
<key>CFBundleExecutable</key>
8+
<string>$(EXECUTABLE_NAME)</string>
9+
<key>CFBundleIdentifier</key>
10+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11+
<key>CFBundleInfoDictionaryVersion</key>
12+
<string>6.0</string>
13+
<key>CFBundleName</key>
14+
<string>$(PRODUCT_NAME)</string>
15+
<key>CFBundlePackageType</key>
16+
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
17+
<key>CFBundleShortVersionString</key>
18+
<string>1.0</string>
19+
<key>CFBundleVersion</key>
20+
<string>$(CURRENT_PROJECT_VERSION)</string>
21+
</dict>
22+
</plist>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import Combine
2+
import Foundation
3+
import SwiftRex
4+
5+
private let wolframAlphaApiKey = "6H69Q3-828TKQJ4EP"
6+
7+
struct WolframAlphaResult: Decodable {
8+
let queryresult: QueryResult
9+
10+
struct QueryResult: Decodable {
11+
let pods: [Pod]
12+
13+
struct Pod: Decodable {
14+
let primary: Bool?
15+
let subpods: [SubPod]
16+
17+
struct SubPod: Decodable {
18+
let plaintext: String
19+
}
20+
}
21+
}
22+
}
23+
24+
func nthPrime(_ n: Int) -> AnyPublisher<Int?, Never> {
25+
wolframAlpha(query: "prime \(n)").map { result in
26+
result
27+
.flatMap {
28+
$0.queryresult
29+
.pods
30+
.first(where: { $0.primary == .some(true) })?
31+
.subpods
32+
.first?
33+
.plaintext
34+
}
35+
.flatMap(Int.init)
36+
}
37+
.eraseToAnyPublisher()
38+
}
39+
40+
func wolframAlpha(query: String) -> AnyPublisher<WolframAlphaResult?, Never> {
41+
var components = URLComponents(string: "https://api.wolframalpha.com/v2/query")!
42+
components.queryItems = [
43+
URLQueryItem(name: "input", value: query),
44+
URLQueryItem(name: "format", value: "plaintext"),
45+
URLQueryItem(name: "output", value: "JSON"),
46+
URLQueryItem(name: "appid", value: wolframAlphaApiKey),
47+
]
48+
49+
return URLSession.shared
50+
.dataTaskPublisher(for: components.url(relativeTo: nil)!)
51+
.map { data, _ in data }
52+
.decode(type: WolframAlphaResult?.self, decoder: JSONDecoder())
53+
.replaceError(with: nil)
54+
.eraseToAnyPublisher()
55+
}
56+
57+
extension Publisher {
58+
public var hush: Publishers.ReplaceError<Publishers.Map<Self, Optional<Self.Output>>> {
59+
return self.map(Optional.some).replaceError(with: nil)
60+
}
61+
}

0 commit comments

Comments
 (0)