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

Register from Home

parent 4197d79d
No related branches found
No related tags found
2 merge requests!102Release 1.0.0,!57Update messenger example
......@@ -69,6 +69,7 @@ let package = Package(
dependencies: [
.target(name: "AppCore"),
.target(name: "HomeFeature"),
.target(name: "RegisterFeature"),
.target(name: "RestoreFeature"),
.target(name: "WelcomeFeature"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
......@@ -89,7 +90,9 @@ let package = Package(
name: "HomeFeature",
dependencies: [
.target(name: "AppCore"),
.target(name: "RegisterFeature"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ComposablePresentation", package: "swift-composable-presentation"),
.product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
],
swiftSettings: swiftSettings
......
......@@ -34,7 +34,14 @@ extension AppEnvironment {
HomeEnvironment(
messenger: messenger,
mainQueue: mainQueue,
bgQueue: bgQueue
bgQueue: bgQueue,
register: {
RegisterEnvironment(
messenger: messenger,
mainQueue: mainQueue,
bgQueue: bgQueue
)
}
)
}
)
......
import Combine
import ComposableArchitecture
import ComposablePresentation
import Foundation
import RegisterFeature
import XXClient
import XXMessengerClient
public struct HomeState: Equatable {
public init() {}
public init(
username: String? = nil,
failure: String? = nil,
register: RegisterState? = nil
) {
self.username = username
self.failure = failure
self.register = register
}
@BindableState public var username: String?
@BindableState public var failure: String?
@BindableState public var register: RegisterState?
}
public enum HomeAction: Equatable {
public enum HomeAction: Equatable, BindableAction {
case start
case binding(BindingAction<HomeState>)
case register(RegisterAction)
}
public struct HomeEnvironment {
public init(
messenger: Messenger,
mainQueue: AnySchedulerOf<DispatchQueue>,
bgQueue: AnySchedulerOf<DispatchQueue>
bgQueue: AnySchedulerOf<DispatchQueue>,
register: @escaping () -> RegisterEnvironment
) {
self.messenger = messenger
self.mainQueue = mainQueue
self.bgQueue = bgQueue
self.register = register
}
public var messenger: Messenger
public var mainQueue: AnySchedulerOf<DispatchQueue>
public var bgQueue: AnySchedulerOf<DispatchQueue>
public var register: () -> RegisterEnvironment
}
extension HomeEnvironment {
public static let unimplemented = HomeEnvironment(
messenger: .unimplemented,
mainQueue: .unimplemented,
bgQueue: .unimplemented
bgQueue: .unimplemented,
register: { .unimplemented }
)
}
......@@ -39,6 +60,51 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
{ state, action, env in
switch action {
case .start:
return .run { subscriber in
do {
try env.messenger.start()
if env.messenger.isConnected() == false {
try env.messenger.connect()
}
if env.messenger.isLoggedIn() == false {
if try env.messenger.isRegistered() == false {
subscriber.send(.set(\.$register, RegisterState()))
subscriber.send(completion: .finished)
return AnyCancellable {}
}
try env.messenger.logIn()
}
if let contact = env.messenger.e2e()?.getContact(),
let facts = try? contact.getFacts(),
let username = facts.first(where: { $0.type == 0 })?.fact {
subscriber.send(.set(\.$username, username))
}
} catch {
subscriber.send(.set(\.$failure, error.localizedDescription))
}
subscriber.send(completion: .finished)
return AnyCancellable {}
}
.subscribe(on: env.bgQueue)
.receive(on: env.mainQueue)
.eraseToEffect()
case .register(.finished):
state.register = nil
return Effect(value: .start)
case .binding(_), .register(_):
return .none
}
}
.binding()
.presenting(
registerReducer,
state: .keyPath(\.register),
id: .notNil(),
action: /HomeAction.register,
environment: { $0.register() }
)
import ComposableArchitecture
import ComposablePresentation
import RegisterFeature
import SwiftUI
public struct HomeView: View {
......@@ -9,19 +11,54 @@ public struct HomeView: View {
let store: Store<HomeState, HomeAction>
struct ViewState: Equatable {
init(state: HomeState) {}
var username: String?
var failure: String?
init(state: HomeState) {
username = state.username
failure = state.failure
}
}
public var body: some View {
WithViewStore(store.scope(state: ViewState.init)) { viewStore in
NavigationView {
Form {
if let username = viewStore.username {
Section {
Text(username)
} header: {
Text("Username")
}
}
if let failure = viewStore.failure {
Section {
Text(failure)
Button {
viewStore.send(.start)
} label: {
Text("Retry")
}
} header: {
Text("Error")
}
}
}
.navigationTitle("Home")
}
.navigationViewStyle(.stack)
.task { viewStore.send(.start) }
.fullScreenCover(
store.scope(
state: \.register,
action: HomeAction.register
),
onDismiss: {
viewStore.send(.set(\.$register, nil))
},
content: RegisterView.init(store:)
)
}
}
}
......
import ComposableArchitecture
import RegisterFeature
import XCTest
import XXClient
import XXMessengerClient
@testable import HomeFeature
@MainActor
final class HomeFeatureTests: XCTestCase {
func testStart() async throws {
func testStartUnregistered() {
let store = TestStore(
initialState: HomeState(),
reducer: homeReducer,
environment: .unimplemented
)
await store.send(.start)
let bgQueue = DispatchQueue.test
let mainQueue = DispatchQueue.test
var messengerDidStartWithTimeout: [Int] = []
var messengerDidConnect = 0
store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
store.environment.messenger.isConnected.run = { false }
store.environment.messenger.connect.run = { messengerDidConnect += 1 }
store.environment.messenger.isLoggedIn.run = { false }
store.environment.messenger.isRegistered.run = { false }
store.send(.start)
bgQueue.advance()
XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
XCTAssertNoDifference(messengerDidConnect, 1)
mainQueue.advance()
store.receive(.set(\.$register, RegisterState())) {
$0.register = RegisterState()
}
}
func testStartRegistered() {
let store = TestStore(
initialState: HomeState(),
reducer: homeReducer,
environment: .unimplemented
)
let username = "test_username"
let bgQueue = DispatchQueue.test
let mainQueue = DispatchQueue.test
var messengerDidStartWithTimeout: [Int] = []
var messengerDidConnect = 0
var messengerDidLogIn = 0
store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
store.environment.messenger.isConnected.run = { false }
store.environment.messenger.connect.run = { messengerDidConnect += 1 }
store.environment.messenger.isLoggedIn.run = { false }
store.environment.messenger.isRegistered.run = { true }
store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
store.environment.messenger.e2e.get = {
var e2e = E2E.unimplemented
e2e.getContact.run = {
var contact = Contact.unimplemented(Data())
contact.getFactsFromContact.run = { _ in [Fact(fact: username, type: 0)] }
return contact
}
return e2e
}
store.send(.start)
bgQueue.advance()
XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
XCTAssertNoDifference(messengerDidConnect, 1)
XCTAssertNoDifference(messengerDidLogIn, 1)
mainQueue.advance()
store.receive(.set(\.$username, username)) {
$0.username = username
}
}
func testRegisterFinished() {
let store = TestStore(
initialState: HomeState(
register: RegisterState()
),
reducer: homeReducer,
environment: .unimplemented
)
let username = "test_username"
let bgQueue = DispatchQueue.test
let mainQueue = DispatchQueue.test
var messengerDidStartWithTimeout: [Int] = []
var messengerDidLogIn = 0
store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
store.environment.messenger.isConnected.run = { true }
store.environment.messenger.isLoggedIn.run = { false }
store.environment.messenger.isRegistered.run = { true }
store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
store.environment.messenger.e2e.get = {
var e2e = E2E.unimplemented
e2e.getContact.run = {
var contact = Contact.unimplemented(Data())
contact.getFactsFromContact.run = { _ in [Fact(fact: username, type: 0)] }
return contact
}
return e2e
}
store.send(.register(.finished)) {
$0.register = nil
}
store.receive(.start)
bgQueue.advance()
XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
XCTAssertNoDifference(messengerDidLogIn, 1)
mainQueue.advance()
store.receive(.set(\.$username, username)) {
$0.username = username
}
}
func testStartMessengerStartFailure() {
let store = TestStore(
initialState: HomeState(),
reducer: homeReducer,
environment: .unimplemented
)
struct Failure: Error {}
let error = Failure()
store.environment.bgQueue = .immediate
store.environment.mainQueue = .immediate
store.environment.messenger.start.run = { _ in throw error }
store.send(.start)
store.receive(.set(\.$failure, error.localizedDescription)) {
$0.failure = error.localizedDescription
}
}
func testStartMessengerConnectFailure() {
let store = TestStore(
initialState: HomeState(),
reducer: homeReducer,
environment: .unimplemented
)
struct Failure: Error {}
let error = Failure()
store.environment.bgQueue = .immediate
store.environment.mainQueue = .immediate
store.environment.messenger.start.run = { _ in }
store.environment.messenger.isConnected.run = { false }
store.environment.messenger.connect.run = { throw error }
store.send(.start)
store.receive(.set(\.$failure, error.localizedDescription)) {
$0.failure = error.localizedDescription
}
}
func testStartMessengerIsRegisteredFailure() {
let store = TestStore(
initialState: HomeState(),
reducer: homeReducer,
environment: .unimplemented
)
struct Failure: Error {}
let error = Failure()
store.environment.bgQueue = .immediate
store.environment.mainQueue = .immediate
store.environment.messenger.start.run = { _ in }
store.environment.messenger.isConnected.run = { true }
store.environment.messenger.isLoggedIn.run = { false }
store.environment.messenger.isRegistered.run = { throw error }
store.send(.start)
store.receive(.set(\.$failure, error.localizedDescription)) {
$0.failure = error.localizedDescription
}
}
func testStartMessengerLogInFailure() {
let store = TestStore(
initialState: HomeState(),
reducer: homeReducer,
environment: .unimplemented
)
struct Failure: Error {}
let error = Failure()
store.environment.bgQueue = .immediate
store.environment.mainQueue = .immediate
store.environment.messenger.start.run = { _ in }
store.environment.messenger.isConnected.run = { true }
store.environment.messenger.isLoggedIn.run = { false }
store.environment.messenger.isRegistered.run = { true }
store.environment.messenger.logIn.run = { throw error }
store.send(.start)
store.receive(.set(\.$failure, error.localizedDescription)) {
$0.failure = error.localizedDescription
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment