Skip to content

Commit d951598

Browse files
committed
contactsView to ChatView
- update composable helper - improve CoreDataClient - Rename from ConversationView to ConversationsView
1 parent 1f7e326 commit d951598

37 files changed

+1373
-704
lines changed

Addame/Addame.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import ComposableArchitecture
99
import SwiftUI
1010
import EventView
11-
import ConversationView
11+
import ConversationsView
1212
import ProfileView
1313
import TabsView
1414
import AuthenticationView

Addame/Addame/Package.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ let package = Package(
4848
// Views
4949
.library(name: "AuthenticationView", targets: ["AuthenticationView"]),
5050
.library(name: "ChatView", targets: ["ChatView"]),
51-
.library(name: "ConversationView", targets: ["ConversationView"]),
51+
.library(name: "ConversationsView", targets: ["ConversationsView"]),
5252
.library(name: "ContactsView", targets: ["ContactsView"]),
5353
.library(name: "EventView", targets: ["EventView"]),
5454
.library(name: "EventForm", targets: ["EventForm"]),
@@ -82,7 +82,7 @@ let package = Package(
8282
"AuthClient", "AuthClientLive", "AttachmentClient", "ChatClient", "ContactClient",
8383
"ConversationClient", "EventClient", "UserClient", "WebsocketClient",
8484

85-
"EventView", "ConversationView", "ProfileView", "TabsView", "AuthenticationView",
85+
"EventView", "ConversationsView", "ProfileView", "TabsView", "AuthenticationView",
8686
"SettingsView",
8787
]),
8888

@@ -104,7 +104,12 @@ let package = Package(
104104
"KeychainService", "FoundationExtension"
105105
]
106106
),
107-
.target(name: "CoreDataStore"),
107+
.target(
108+
name: "CoreDataStore",
109+
dependencies: [
110+
"SharedModels", "FoundationExtension"
111+
]
112+
),
108113
.target(name: "FoundationExtension"),
109114

110115
// Client
@@ -224,7 +229,7 @@ let package = Package(
224229
"AuthClient", "AuthClientLive", "UserClient", "UserClientLive",
225230
"EventClient", "EventClientLive", "AttachmentClient", "AttachmentClientLive",
226231
"PathMonitorClient", "PathMonitorClientLive", "ConversationClient", "ConversationClientLive",
227-
"EventView", "ConversationView", "ProfileView",
232+
"EventView", "ConversationsView", "ProfileView",
228233
"SwiftUIExtension"
229234
]),
230235

@@ -241,14 +246,15 @@ let package = Package(
241246
),
242247

243248
.target(
244-
name: "ConversationView",
249+
name: "ConversationsView",
245250
dependencies: [
246251
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
247252
"SharedModels", "InfoPlist", "KeychainService",
248-
"WebsocketClient", "ConversationClient", "ChatClient",
253+
"WebsocketClient", "ChatClient", "ChatClientLive",
249254
"SwiftUIExtension", "FoundationExtension", "AsyncImageLoder",
250-
"HttpRequest", "ChatClientLive", "ConversationClientLive",
251-
"WebsocketClientLive", "ChatView", "ComposableArchitectureHelpers"
255+
"HttpRequest", "ConversationClient", "ConversationClientLive",
256+
"WebsocketClientLive", "ChatView", "ComposableArchitectureHelpers",
257+
"ContactClient", "ContactClientLive", "ContactsView", "CoreDataClient"
252258
]
253259
),
254260

@@ -257,8 +263,10 @@ let package = Package(
257263
dependencies: [
258264
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
259265
"SharedModels", "AsyncImageLoder", "HttpRequest",
260-
"ContactClient", "ContactClientLive",
266+
"ContactClient", "ContactClientLive", "WebsocketClient",
267+
"WebsocketClientLive", "ChatClient", "ChatClientLive",
261268
"CoreDataStore", "CoreDataClient",
269+
"ChatView", "ComposableArchitectureHelpers"
262270
]),
263271

264272
.target(

Addame/Addame/Sources/AppFeature/AppView.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import ComposableArchitecture
99
import SwiftUI
1010
import EventView
11-
import ConversationView
11+
import ConversationsView
1212
import ProfileView
1313
import TabsView
1414
import AuthenticationView
@@ -22,6 +22,11 @@ public struct AppView: View {
2222
@AppStorage("isAuthorized")
2323
public var isAuthorized: Bool = false
2424

25+
static let tabsEnv = TabsEnvironment(
26+
backgroundQueue: .main,
27+
mainQueue: .main
28+
)
29+
2530
static let tabsState = TabsState(
2631
selectedTab: .event,
2732
event: EventsState(),
@@ -32,7 +37,7 @@ public struct AppView: View {
3237
let tabsStore = Store(
3338
initialState: tabsState,
3439
reducer: tabsReducer.debug(),
35-
environment: ()
40+
environment: tabsEnv
3641
)
3742

3843
static let environment = AuthenticationEnvironment(

Addame/Addame/Sources/ChatView/ChatView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public struct ChatView: View {
6767
.onAppear {
6868
viewStore.send(.onAppear)
6969
}
70+
.navigationBarTitle(viewStore.state.conversation?.title ?? "", displayMode: .automatic)
7071
}
7172
.alert(self.store.scope(state: { $0.alert }), dismiss: .alertDismissed)
7273
}

Addame/Addame/Sources/ComposableArchitectureHelpers/NavigationLink+Store.swift

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -80,27 +80,3 @@ extension View {
8080
)
8181
}
8282
}
83-
84-
/// Converts a closure `(A) -> B?` to a closure that returns last non-`nil` `B` returned by the input closure.
85-
///
86-
/// Based on a code from [Thomvis/Construct repository](https://github.com/Thomvis/Construct/blob/f165fd005cd939560c1a4eb8d6ee55075a52685d/Construct/Foundation/Memoize.swift)
87-
///
88-
/// - Parameter inputClosure: The input closure.
89-
/// - Returns: Modified closure.
90-
func replayNonNil<A, B>(_ inputClosure: @escaping (A) -> B?) -> (A) -> B? {
91-
var lastNonNilOutput: B? = nil
92-
return { inputValue in
93-
guard let outputValue = inputClosure(inputValue) else {
94-
return lastNonNilOutput
95-
}
96-
lastNonNilOutput = outputValue
97-
return outputValue
98-
}
99-
}
100-
101-
/// Creates a closure (T?) -> T? that returns last non-`nil` T passed to it.
102-
///
103-
/// - Returns: The closure.
104-
func replayNonNil<T>() -> (T?) -> T? {
105-
replayNonNil { $0 }
106-
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// Reducer+CaptureState.swift
3+
//
4+
//
5+
// Created by Saroar Khandoker on 13.06.2021.
6+
//
7+
8+
import ComposableArchitecture
9+
10+
extension Reducer {
11+
/// Captures reduced state.
12+
///
13+
/// - Parameter capture: A closure called when a state is reduced.
14+
/// - Parameter state: The reduced state value.
15+
/// - Returns: A reducer that works on `State`, `Action`, `Environment`.
16+
func captureState(_ capture: @escaping (_ state: State) -> Void) -> Self {
17+
.init { state, action, environment in
18+
capture(state)
19+
return run(&state, action, environment)
20+
}
21+
}
22+
}
23+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//
2+
// Reducer+Presents.swift
3+
//
4+
//
5+
// Created by Saroar Khandoker on 13.06.2021.
6+
//
7+
8+
import ComposableArchitecture
9+
10+
extension Reducer {
11+
/// Combines the reducer with a reducer that works on optionally presented `LocalState`.
12+
///
13+
/// - All effects returned by the local reducer are canceled when `LocalState` is `nil`.
14+
/// - If `LocalAction` is sent when `LocalState` is `nil`:
15+
/// - The last non-`nil` state value is passed to the local reducer (if available).
16+
/// - The `LocalState` is unchanged (it stays `nil`).
17+
/// - All effects returned by the local reducer are immediately canceled.
18+
///
19+
/// Based on [Reducer.presents function](https://github.com/pointfreeco/swift-composable-architecture/blob/9ec4b71e5a84f448dedb063a21673e4696ce135f/Sources/ComposableArchitecture/Reducer.swift#L549-L572) from `iso` branch of `swift-composable-architecture` repository.
20+
///
21+
/// - Parameters:
22+
/// - localReducer: A reducer that works on `LocalState`, `LocalAction`, `LocalEnvironment`.
23+
/// - toLocalState: A key path that can get/set `LocalState?` inside `State`.
24+
/// - toLocalAction: A case path that can extract/embed `LocalAction` from `Action`.
25+
/// - toLocalEnvironment: A function that transforms `Environment` into `LocalEnvironment`.
26+
/// - Returns: A reducer that works on `State`, `Action`, `Environment`.
27+
public func presents<LocalState, LocalAction, LocalEnvironment>(
28+
_ localReducer: Reducer<LocalState, LocalAction, LocalEnvironment>,
29+
state toLocalState: WritableKeyPath<State, LocalState?>,
30+
action toLocalAction: CasePath<Action, LocalAction>,
31+
environment toLocalEnvironment: @escaping (Environment) -> LocalEnvironment
32+
) -> Self {
33+
let localEffectsId = UUID()
34+
var lastNonNilLocalState: LocalState?
35+
return Self { state, action, environment in
36+
let localEffects = localReducer
37+
.optional()
38+
.replaceNilState(with: lastNonNilLocalState)
39+
.captureState { lastNonNilLocalState = $0 ?? lastNonNilLocalState }
40+
.pullback(state: toLocalState, action: toLocalAction, environment: toLocalEnvironment)
41+
.run(&state, action, environment)
42+
.cancellable(id: localEffectsId)
43+
let globalEffects = run(&state, action, environment)
44+
let hasLocalState = state[keyPath: toLocalState] != nil
45+
return .merge(
46+
localEffects,
47+
globalEffects,
48+
hasLocalState ? .none : .cancel(id: localEffectsId)
49+
)
50+
}
51+
}
52+
}
53+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// Reducer+ReplaceNilState.swift
3+
//
4+
//
5+
// Created by Saroar Khandoker on 13.06.2021.
6+
//
7+
8+
import ComposableArchitecture
9+
10+
extension Reducer {
11+
/// Replaces `nil` state with provided value.
12+
///
13+
/// - If reducer is run with non-`nil` state its behavior is unchanged.
14+
/// - If reducer is run with a `nil` state, replacement state is used instead.
15+
/// - When replacement state is used, the original state wont be mutated!
16+
///
17+
/// - Parameter replacement: The replacement state value.
18+
/// - Returns: A reducer that works on `State`, `Action`, `Environment`.
19+
func replaceNilState<S>(
20+
with replacement: @escaping @autoclosure () -> S?
21+
) -> Self where State == Optional<S> {
22+
.init { state, action, environment in
23+
guard state != nil else {
24+
var replacedState = replacement()
25+
return run(&replacedState, action, environment)
26+
}
27+
return run(&state, action, environment)
28+
}
29+
}
30+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// ReplayNonNil.swift
3+
//
4+
//
5+
// Created by Saroar Khandoker on 13.06.2021.
6+
//
7+
8+
/// Converts a closure `(A) -> B?` to a closure that returns last non-`nil` `B` returned by the input closure.
9+
///
10+
/// Based on a code from [Thomvis/Construct repository](https://github.com/Thomvis/Construct/blob/f165fd005cd939560c1a4eb8d6ee55075a52685d/Construct/Foundation/Memoize.swift)
11+
///
12+
/// - Parameter inputClosure: The input closure.
13+
/// - Returns: Modified closure.
14+
func replayNonNil<A, B>(_ inputClosure: @escaping (A) -> B?) -> (A) -> B? {
15+
var lastNonNilOutput: B? = nil
16+
return { inputValue in
17+
guard let outputValue = inputClosure(inputValue) else {
18+
return lastNonNilOutput
19+
}
20+
lastNonNilOutput = outputValue
21+
return outputValue
22+
}
23+
}
24+
25+
/// Creates a closure (T?) -> T? that returns last non-`nil` T passed to it.
26+
///
27+
/// - Returns: The closure.
28+
func replayNonNil<T>() -> (T?) -> T? {
29+
replayNonNil { $0 }
30+
}
31+

Addame/Addame/Sources/ContactClientLive/Live.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ func token() -> AnyPublisher<String, HTTPError> {
3333
public struct ContactAPI {
3434

3535
public static let build = Self ()
36-
private var baseURL: URL { EnvironmentKeys.rootURL.appendingPathComponent("/contacts/") }
36+
private var baseURL: URL {
37+
EnvironmentKeys.rootURL.appendingPathComponent("/contacts/")
38+
}
3739

3840
private let contactStore = CNContactStore()
3941
private let phoneNumberKit = PhoneNumberKit()

0 commit comments

Comments
 (0)