Newer
Older
messageListenerFailure: String? = nil,
networkNodesReport: NodeRegistrationReport? = nil,
alert: AlertState<HomeAction>? = nil,
self.messageListenerFailure = messageListenerFailure
self.isNetworkHealthy = isNetworkHealthy
self.isDeletingAccount = isDeletingAccount
self.alert = alert
self.register = register
public var messageListenerFailure: String?
public var isNetworkHealthy: Bool?
public var networkNodesReport: NodeRegistrationReport?
public var alert: AlertState<HomeAction>?
public var register: RegisterState?
public enum HomeAction: Equatable {
public enum Messenger: Equatable {
case start
case didStartRegistered
case didStartUnregistered
case failure(NSError)
}
public enum AuthHandler: Equatable {
case start
case stop
case failure(NSError)
case failureDismissed
public enum MessageListener: Equatable {
case start
case stop
case failure(NSError)
case failureDismissed
}
public enum NetworkMonitor: Equatable {
case start
case stop
case health(Bool)
case nodes(NodeRegistrationReport)
public enum DeleteAccount: Equatable {
case buttonTapped
case confirmed
case success
case failure(NSError)
}
case messenger(Messenger)
case messageListener(MessageListener)
case networkMonitor(NetworkMonitor)
case deleteAccount(DeleteAccount)
case didDismissAlert
case didDismissRegister
case userSearchButtonTapped
case didDismissUserSearch
case contactsButtonTapped
case didDismissContacts
}
public struct HomeEnvironment {
public init(
authHandler: AuthCallbackHandler,
messageListener: MessageListenerHandler,
mainQueue: AnySchedulerOf<DispatchQueue>,
register: @escaping () -> RegisterEnvironment,
contacts: @escaping () -> ContactsEnvironment,
userSearch: @escaping () -> UserSearchEnvironment
self.messageListener = messageListener
self.mainQueue = mainQueue
self.bgQueue = bgQueue
public var authHandler: AuthCallbackHandler
public var messageListener: MessageListenerHandler
public var mainQueue: AnySchedulerOf<DispatchQueue>
public var bgQueue: AnySchedulerOf<DispatchQueue>
public var contacts: () -> ContactsEnvironment
public var userSearch: () -> UserSearchEnvironment
}
extension HomeEnvironment {
public static let unimplemented = HomeEnvironment(
)
}
public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
{ state, action, env in
Effect(value: .authHandler(.start)),
Effect(value: .messageListener(.start)),
Effect(value: .networkMonitor(.stop)),
Effect.result {
do {
try env.messenger.start()
if env.messenger.isConnected() == false {
try env.messenger.connect()
try env.messenger.listenForMessages()
if env.messenger.isLoggedIn() == false {
if try env.messenger.isRegistered() == false {
return .success(.messenger(.didStartUnregistered))
}
try env.messenger.logIn()
return .success(.messenger(.didStartRegistered))
} catch {
return .success(.messenger(.failure(error as NSError)))
}
.subscribe(on: env.bgQueue)
.receive(on: env.mainQueue)
.eraseToEffect()
case .messenger(.didStartUnregistered):
state.register = RegisterState()
return .none
case .messenger(.didStartRegistered):
return Effect(value: .networkMonitor(.start))
case .messenger(.failure(let error)):
state.failure = error.localizedDescription
return .none
let cancellable = env.authHandler(onError: { error in
subscriber.send(.authHandler(.failure(error as NSError)))
})
return AnyCancellable { cancellable.cancel() }
}
.subscribe(on: env.bgQueue)
.receive(on: env.mainQueue)
.eraseToEffect()
.cancellable(id: AuthCallbacksEffectId.self, cancelInFlight: true)
return .cancel(id: AuthCallbacksEffectId.self)
case .authHandler(.failure(let error)):
state.authFailure = error.localizedDescription
return .none
case .authHandler(.failureDismissed):
state.authFailure = nil
case .messageListener(.start):
return Effect.run { subscriber in
let cancellable = env.messageListener(onError: { error in
subscriber.send(.messageListener(.failure(error as NSError)))
})
return AnyCancellable { cancellable.cancel() }
}
.subscribe(on: env.bgQueue)
.receive(on: env.mainQueue)
.eraseToEffect()
.cancellable(id: MessageListenerEffectId.self, cancelInFlight: true)
case .messageListener(.stop):
return .cancel(id: MessageListenerEffectId.self)
case .messageListener(.failure(let error)):
state.messageListenerFailure = error.localizedDescription
return .none
case .messageListener(.failureDismissed):
state.messageListenerFailure = nil
return .none
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),
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
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
state.alert = .confirmAccountDeletion()
return .none
state.isDeletingAccount = true
do {
let contactId = try env.messenger.e2e.tryGet().getContact().getId()
let contact = try env.dbManager.getDB().fetchContacts(.init(id: [contactId])).first
if let username = contact?.username {
let ud = try env.messenger.ud.tryGet()
try ud.permanentDeleteAccount(username: Fact(type: .username, value: username))
}
try env.messenger.destroy()
}
}
.subscribe(on: env.bgQueue)
.receive(on: env.mainQueue)
.eraseToEffect()
state.isDeletingAccount = false
return .none
case .deleteAccount(.failure(let error)):
state.isDeletingAccount = false
state.alert = .accountDeletionFailed(error)
return .none
case .didDismissAlert:
state.alert = nil
return .none
case .didDismissRegister:
state.register = nil
return .none
case .userSearchButtonTapped:
state.userSearch = UserSearchState()
return .none
case .didDismissUserSearch:
state.userSearch = nil
return .none
case .contactsButtonTapped:
state.contacts = ContactsState()
return .none
case .didDismissContacts:
state.contacts = nil
return .none
case .register(.finished):
state.register = nil
case .register(_), .contacts(_), .userSearch(_):
.presenting(
registerReducer,
state: .keyPath(\.register),
id: .notNil(),
action: /HomeAction.register,
environment: { $0.register() }
)
.presenting(
contactsReducer,
state: .keyPath(\.contacts),
id: .notNil(),
action: /HomeAction.contacts,
environment: { $0.contacts() }
)
.presenting(
userSearchReducer,
state: .keyPath(\.userSearch),
id: .notNil(),
action: /HomeAction.userSearch,
environment: { $0.userSearch() }
)