Skip to content
Snippets Groups Projects
Commit b927f507 authored by Dariusz Rybicki's avatar Dariusz Rybicki
Browse files

Monitor node registration in HomeFeature

parent cd58c0a6
No related branches found
No related tags found
2 merge requests!102Release 1.0.0,!63Messenger example - network status
......@@ -11,6 +11,7 @@ public struct HomeState: Equatable {
public init(
failure: String? = nil,
isNetworkHealthy: Bool? = nil,
networkNodesReport: NodeRegistrationReport? = nil,
isDeletingAccount: Bool = false,
alert: AlertState<HomeAction>? = nil,
register: RegisterState? = nil
......@@ -24,6 +25,7 @@ public struct HomeState: Equatable {
public var failure: String?
public var isNetworkHealthy: Bool?
public var networkNodesReport: NodeRegistrationReport?
public var isDeletingAccount: Bool
public var alert: AlertState<HomeAction>?
public var register: RegisterState?
......@@ -41,6 +43,7 @@ public enum HomeAction: Equatable {
case start
case stop
case health(Bool)
case nodes(NodeRegistrationReport)
}
public enum DeleteAccount: Equatable {
......@@ -93,6 +96,7 @@ extension HomeEnvironment {
public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
{ state, action, env in
enum NetworkHealthEffectId {}
enum NetworkNodesEffectId {}
switch action {
case .messenger(.start):
......@@ -135,26 +139,44 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
return .none
case .networkMonitor(.start):
return .run { subscriber in
return .merge(
Effect.run { subscriber in
let callback = HealthCallback { isHealthy in
subscriber.send(.networkMonitor(.health(isHealthy)))
}
let cancellable = env.messenger.cMix()?.addHealthCallback(callback)
return AnyCancellable { cancellable?.cancel() }
}
.cancellable(id: NetworkHealthEffectId.self, cancelInFlight: true)
.cancellable(id: NetworkHealthEffectId.self, cancelInFlight: true),
Effect.timer(
id: NetworkNodesEffectId.self,
every: .seconds(2),
on: env.bgQueue
)
.compactMap { _ in try? env.messenger.cMix()?.getNodeRegistrationStatus() }
.map { HomeAction.networkMonitor(.nodes($0)) }
.eraseToEffect()
)
.subscribe(on: env.bgQueue)
.receive(on: env.mainQueue)
.eraseToEffect()
case .networkMonitor(.stop):
state.isNetworkHealthy = nil
return .cancel(id: NetworkHealthEffectId.self)
state.networkNodesReport = nil
return .merge(
.cancel(id: NetworkHealthEffectId.self),
.cancel(id: NetworkNodesEffectId.self)
)
case .networkMonitor(.health(let isHealthy)):
state.isNetworkHealthy = isHealthy
return .none
case .networkMonitor(.nodes(let report)):
state.networkNodesReport = report
return .none
case .deleteAccount(.buttonTapped):
state.alert = .confirmAccountDeletion()
return .none
......
......@@ -2,6 +2,7 @@ import ComposableArchitecture
import ComposablePresentation
import RegisterFeature
import SwiftUI
import XXClient
public struct HomeView: View {
public init(store: Store<HomeState, HomeAction>) {
......@@ -13,12 +14,14 @@ public struct HomeView: View {
struct ViewState: Equatable {
var failure: String?
var isNetworkHealthy: Bool?
var networkNodesReport: NodeRegistrationReport?
var isDeletingAccount: Bool
init(state: HomeState) {
failure = state.failure
isNetworkHealthy = state.isNetworkHealthy
isDeletingAccount = state.isDeletingAccount
networkNodesReport = state.networkNodesReport
}
}
......@@ -26,15 +29,17 @@ public struct HomeView: View {
WithViewStore(store.scope(state: ViewState.init)) { viewStore in
NavigationView {
Form {
if let failure = viewStore.failure {
Section {
if let failure = viewStore.failure {
Text(failure)
Button {
viewStore.send(.messenger(.start))
} label: {
Text("Retry")
}
}
} header: {
if viewStore.failure != nil {
Text("Error")
}
}
......@@ -57,6 +62,26 @@ public struct HomeView: View {
.foregroundColor(.gray)
}
}
ProgressView(
value: viewStore.networkNodesReport?.ratio ?? 0,
label: {
Text("Node registration")
},
currentValueLabel: {
if let report = viewStore.networkNodesReport {
HStack {
Text("\(Int((report.ratio * 100).rounded(.down)))%")
Spacer()
Text("\(report.registered) / \(report.total)")
}
} else {
Text("Unknown")
}
}
)
.tint((viewStore.networkNodesReport?.ratio ?? 0) >= 0.8 ? .green : .orange)
.animation(.default, value: viewStore.networkNodesReport?.ratio)
} header: {
Text("Network")
}
......
......@@ -58,6 +58,10 @@ final class HomeFeatureTests: XCTestCase {
store.environment.messenger.cMix.get = {
var cMix: CMix = .unimplemented
cMix.addHealthCallback.run = { _ in Cancellable {} }
cMix.getNodeRegistrationStatus.run = {
struct Unimplemented: Error {}
throw Unimplemented()
}
return cMix
}
......@@ -96,6 +100,10 @@ final class HomeFeatureTests: XCTestCase {
store.environment.messenger.cMix.get = {
var cMix: CMix = .unimplemented
cMix.addHealthCallback.run = { _ in Cancellable {} }
cMix.getNodeRegistrationStatus.run = {
struct Unimplemented: Error {}
throw Unimplemented()
}
return cMix
}
......@@ -219,41 +227,82 @@ final class HomeFeatureTests: XCTestCase {
environment: .unimplemented
)
let bgQueue = DispatchQueue.test
let mainQueue = DispatchQueue.test
var cMixDidAddHealthCallback: [HealthCallback] = []
var healthCallbackDidCancel = 0
var nodeRegistrationStatusIndex = 0
let nodeRegistrationStatus: [NodeRegistrationReport] = [
.init(registered: 0, total: 10),
.init(registered: 1, total: 11),
.init(registered: 2, total: 12),
]
store.environment.bgQueue = .immediate
store.environment.mainQueue = .immediate
store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
store.environment.messenger.cMix.get = {
var cMix: CMix = .unimplemented
cMix.addHealthCallback.run = { callback in
cMixDidAddHealthCallback.append(callback)
return Cancellable { healthCallbackDidCancel += 1 }
}
cMix.getNodeRegistrationStatus.run = {
defer { nodeRegistrationStatusIndex += 1 }
return nodeRegistrationStatus[nodeRegistrationStatusIndex]
}
return cMix
}
store.send(.networkMonitor(.start))
bgQueue.advance()
XCTAssertNoDifference(cMixDidAddHealthCallback.count, 1)
cMixDidAddHealthCallback.first?.handle(true)
mainQueue.advance()
store.receive(.networkMonitor(.health(true))) {
$0.isNetworkHealthy = true
}
cMixDidAddHealthCallback.first?.handle(false)
mainQueue.advance()
store.receive(.networkMonitor(.health(false))) {
$0.isNetworkHealthy = false
}
bgQueue.advance(by: 2)
mainQueue.advance()
store.receive(.networkMonitor(.nodes(nodeRegistrationStatus[0]))) {
$0.networkNodesReport = nodeRegistrationStatus[0]
}
bgQueue.advance(by: 2)
mainQueue.advance()
store.receive(.networkMonitor(.nodes(nodeRegistrationStatus[1]))) {
$0.networkNodesReport = nodeRegistrationStatus[1]
}
bgQueue.advance(by: 2)
mainQueue.advance()
store.receive(.networkMonitor(.nodes(nodeRegistrationStatus[2]))) {
$0.networkNodesReport = nodeRegistrationStatus[2]
}
store.send(.networkMonitor(.stop)) {
$0.isNetworkHealthy = nil
$0.networkNodesReport = nil
}
XCTAssertNoDifference(healthCallbackDidCancel, 1)
mainQueue.advance()
}
func testAccountDeletion() {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment