Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/clients bootstrap #113

Merged
merged 2 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import network.bisq.mobile.android.node.domain.market_price.NodeMarketPriceServi
import network.bisq.mobile.android.node.domain.offerbook.NodeOfferbookServiceFacade
import network.bisq.mobile.android.node.domain.user_profile.NodeUserProfileServiceFacade
import network.bisq.mobile.android.node.presentation.NodeMainPresenter
import network.bisq.mobile.android.node.presentation.NodeSplashPresenter
import network.bisq.mobile.android.node.presentation.OnBoardingNodePresenter
import network.bisq.mobile.android.node.service.AndroidNodeCatHashService
import network.bisq.mobile.android.node.service.AndroidMemoryReportService
import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.market_price.MarketPriceServiceFacade
import network.bisq.mobile.domain.service.offerbook.OfferbookServiceFacade
import network.bisq.mobile.domain.service.user_profile.UserProfileServiceFacade
import network.bisq.mobile.presentation.MainPresenter
import network.bisq.mobile.presentation.ui.AppPresenter
import network.bisq.mobile.presentation.ui.uicases.startup.IOnboardingPresenter
import network.bisq.mobile.presentation.ui.uicases.startup.SplashPresenter
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.bind
import org.koin.dsl.module
Expand Down Expand Up @@ -43,5 +45,14 @@ val androidNodeModule = module {
// and binding the same obj to 2 different abstractions
single<MainPresenter> { NodeMainPresenter(get(), get(), get(), get(), get(), get()) } bind AppPresenter::class

single<SplashPresenter> {
NodeSplashPresenter(
get(),
get(),
get(),
get(),
)
}

single<IOnboardingPresenter> { OnBoardingNodePresenter(get(), get()) } bind IOnboardingPresenter::class
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import bisq.application.State
import bisq.common.observable.Observable
import bisq.common.observable.Pin
import network.bisq.mobile.android.node.AndroidApplicationService
import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade

class NodeApplicationBootstrapFacade(
private val applicationService: AndroidApplicationService.Provider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package network.bisq.mobile.android.node.presentation
import android.app.Activity
import network.bisq.mobile.android.node.AndroidApplicationService
import network.bisq.mobile.android.node.service.AndroidMemoryReportService
import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.controller.NotificationServiceController
import network.bisq.mobile.domain.service.market_price.MarketPriceServiceFacade
import network.bisq.mobile.domain.service.offerbook.OfferbookServiceFacade
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package network.bisq.mobile.android.node.presentation

import network.bisq.mobile.domain.data.model.Settings
import network.bisq.mobile.domain.data.repository.SettingsRepository
import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.user_profile.UserProfileServiceFacade
import network.bisq.mobile.presentation.MainPresenter
import network.bisq.mobile.presentation.ui.uicases.startup.SplashPresenter

class NodeSplashPresenter(
mainPresenter: MainPresenter,
applicationBootstrapFacade: ApplicationBootstrapFacade,
private val userProfileService: UserProfileServiceFacade,
private val settingsRepository: SettingsRepository
) : SplashPresenter(mainPresenter, applicationBootstrapFacade, userProfileService, settingsRepository) {

/**
* Default implementation in shared is for xClients. Override on node to avoid this.
*/
override fun doCustomNavigationLogic(settings: Settings) {
// do nothin
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
package network.bisq.mobile.android.node.main.bootstrap
package network.bisq.mobile.client.bootstrap

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import network.bisq.mobile.domain.data.BackgroundDispatcher
import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.data.repository.SettingsRepository
import network.bisq.mobile.domain.service.TrustedNodeService
import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade

class ClientApplicationBootstrapFacade() :
class ClientApplicationBootstrapFacade(
private val settingsRepository: SettingsRepository,
private val trustedNodeService: TrustedNodeService) :
ApplicationBootstrapFacade() {

private val backgroundScope = CoroutineScope(BackgroundDispatcher)
override fun activate() {
setState("Dummy state 1")
// TODO all texts here shoul use the translation module
setState("Bootstrapping..")
setProgress(0f)

// just dummy loading simulation, might be that there is no loading delay at the end...
CoroutineScope(BackgroundDispatcher).launch {
delay(50L)
setState("Dummy state 2")
setProgress(0.25f)
backgroundScope.launch {
settingsRepository.fetch()
val url = settingsRepository.data.value?.bisqApiUrl
log.d { "Settings url $url" }

delay(50L)
setState("Dummy state 3")
setProgress(0.5f)

delay(50L)
setState("Dummy state 4")
setProgress(1f)
// TODO this is validated elsewhere, need to unify it or get
// rid of this facade otherwise
// Main issue is that the Trusted node setup screen is not
// if (url == null) {
// setState("Trusted node not configured")
// setProgress(0f)
// } else {
setProgress(0.5f)
setState("Connecting to Trusted Node..")
if (!trustedNodeService.isConnected()) {
try {
trustedNodeService.connect()
setState("Connected to Trusted Node")
setProgress(1.0f)
} catch (e: Exception) {
log.e(e) { "Failed to connect to trusted node" }
setState("No connectivity")
setProgress(1.0f)
}
} else {
setState("Connected to Trusted Node")
setProgress(1.0f)
}
// }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual
import kotlinx.serialization.modules.polymorphic
import network.bisq.mobile.android.node.main.bootstrap.ClientApplicationBootstrapFacade
import network.bisq.mobile.client.bootstrap.ClientApplicationBootstrapFacade
import network.bisq.mobile.client.market.ClientMarketPriceServiceFacade
import network.bisq.mobile.client.market.MarketPriceApiGateway
import network.bisq.mobile.client.offerbook.ClientOfferbookServiceFacade
import network.bisq.mobile.client.offerbook.offer.OfferbookApiGateway
import network.bisq.mobile.client.shared.BuildConfig
import network.bisq.mobile.client.websocket.WebSocketClient
import network.bisq.mobile.client.websocket.api_proxy.WebSocketApiClient
import network.bisq.mobile.client.websocket.messages.SubscriptionRequest
Expand All @@ -28,7 +27,8 @@ import network.bisq.mobile.client.websocket.messages.WebSocketRestApiResponse
import network.bisq.mobile.client.user_profile.ClientUserProfileServiceFacade
import network.bisq.mobile.client.user_profile.UserProfileApiGateway
import network.bisq.mobile.domain.data.EnvironmentController
import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.TrustedNodeService
import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.market_price.MarketPriceServiceFacade
import network.bisq.mobile.domain.service.offerbook.OfferbookServiceFacade
import network.bisq.mobile.domain.service.user_profile.UserProfileServiceFacade
Expand Down Expand Up @@ -68,7 +68,7 @@ val clientModule = module {
}
}

single<ApplicationBootstrapFacade> { ClientApplicationBootstrapFacade() }
single<ApplicationBootstrapFacade> { ClientApplicationBootstrapFacade(get(), get()) }

single { EnvironmentController() }
single(named("ApiHost")) { get<EnvironmentController>().getApiHost() }
Expand All @@ -84,6 +84,9 @@ val clientModule = module {
get(named("WebsocketApiPort"))
)
}

single { TrustedNodeService(get()) }

// single { WebSocketHttpClient(get()) }
single {
println("Running on simulator: ${get<EnvironmentController>().isSimulator()}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class WebSocketClient(

private val webSocketUrl: String = "ws://$host:$port/websocket"
private var session: DefaultClientWebSocketSession? = null
private var isConnected = false
var isConnected = false
private val webSocketEventObservers = ConcurrentMap<String, WebSocketEventObserver>()
private val requestResponseHandlers = mutableMapOf<String, RequestResponseHandler>()
private var connectionReady = CompletableDeferred<Boolean>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package network.bisq.mobile.domain.service

import kotlinx.coroutines.CoroutineScope
import network.bisq.mobile.client.websocket.WebSocketClient
import network.bisq.mobile.domain.data.BackgroundDispatcher
import network.bisq.mobile.utils.Logging

/**
* This service allows to interact with the underlaying connectivity system
* against the trusted node for the client.
*/
class TrustedNodeService(private val websocketClient: WebSocketClient): Logging {
private val backgroundScope = CoroutineScope(BackgroundDispatcher)

// TODO websocketClient.isConnected should be observable so that we emit
// events when disconnected and UI can react
fun isConnected() = websocketClient.isConnected

/**
* Connects to the trusted node, throws an exception if connection fails
*/
suspend fun connect() {
runCatching {
websocketClient.connect()
}.onSuccess {
log.d { "Connected to trusted node" }
}.onFailure {
log.e { "ERROR: FAILED to connect to trusted node - details above" }
throw it
}
}

suspend fun disconnect() {
// TODO
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package network.bisq.mobile.domain.data.repository.main.bootstrap
package network.bisq.mobile.domain.service.bootstrap

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import network.bisq.mobile.domain.LifeCycleAware
import network.bisq.mobile.utils.Logging

abstract class ApplicationBootstrapFacade: LifeCycleAware {
abstract class ApplicationBootstrapFacade: LifeCycleAware, Logging {
private val _state = MutableStateFlow("")
val state: StateFlow<String> = _state
fun setState(value: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package network.bisq.mobile.client

import kotlinx.coroutines.launch
import network.bisq.mobile.client.websocket.WebSocketClient
import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.TrustedNodeService
import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.controller.NotificationServiceController
import network.bisq.mobile.domain.service.market_price.MarketPriceServiceFacade
import network.bisq.mobile.domain.service.offerbook.OfferbookServiceFacade
Expand All @@ -11,26 +12,14 @@ import network.bisq.mobile.presentation.MainPresenter
class ClientMainPresenter(
notificationServiceController: NotificationServiceController,
private val applicationBootstrapFacade: ApplicationBootstrapFacade,
private val webSocketClient: WebSocketClient,
private val trustedNodeService: TrustedNodeService,
private val offerbookServiceFacade: OfferbookServiceFacade,
private val marketPriceServiceFacade: MarketPriceServiceFacade
) : MainPresenter(notificationServiceController) {

override fun onViewAttached() {
super.onViewAttached()
runCatching {
backgroundScope.launch {
runCatching {
webSocketClient.connect()
}.onSuccess {
log.d { "Connected to trusted node" }
}.onFailure {
// TODO give user feedback (we could have a general error screen covering usual
// issues like connection issues and potential solutions)
log.e { "ERROR: FAILED to connect to trusted node - details above" }
}
}

applicationBootstrapFacade.activate()
offerbookServiceFacade.activate()
marketPriceServiceFacade.activate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ val presentationModule = module {

single<TopBarPresenter> { TopBarPresenter(get(), get()) } bind ITopBarPresenter::class

single {
single<SplashPresenter> {
SplashPresenter(
get(),
get(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import network.bisq.mobile.domain.data.BackgroundDispatcher
import network.bisq.mobile.domain.data.model.Settings
import network.bisq.mobile.domain.data.repository.SettingsRepository
import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.user_profile.UserProfileServiceFacade
import network.bisq.mobile.presentation.BasePresenter
import network.bisq.mobile.presentation.MainPresenter
import network.bisq.mobile.presentation.ui.navigation.Routes

open class SplashPresenter(
mainPresenter: MainPresenter,
private val applicationBootstrapFacade: ApplicationBootstrapFacade,
applicationBootstrapFacade: ApplicationBootstrapFacade,
private val userProfileService: UserProfileServiceFacade,
private val settingsRepository: SettingsRepository
) : BasePresenter(mainPresenter) {
Expand Down Expand Up @@ -44,41 +43,53 @@ open class SplashPresenter(
CoroutineScope(Dispatchers.Main).launch {

settingsRepository.fetch()
val settings: Settings = settingsRepository.data.value ?: Settings()
val settings: Settings? = settingsRepository.data.value

if (userProfileService.hasUserProfile()) {
// rootNavigator.navigate(Routes.TrustedNodeSetup.name) {
// [DONE] For androidNode, goto TabContainer
rootNavigator.navigate(Routes.TabContainer.name) {
if (settings == null) {
rootNavigator.navigate(Routes.TrustedNodeSetup.name) {
popUpTo(Routes.Splash.name) { inclusive = true }
}

// TODO: This is only for xClient.
// How to handle between xClient and androidNode
if (settings.bisqApiUrl.isEmpty()) {
// Test if the Bisq remote instance is up and responding
// If yes, goto TabContainer screen.
// If no, goto TrustedNodeSetupScreen
} else {
// If no, goto TrustedNodeSetupScreen
}

} else {
// If firstTimeApp launch, goto Onboarding[clientMode] (androidNode / xClient)
// If not, goto CreateProfile
if (settings.firstLaunch) {
rootNavigator.navigate(Routes.Onboarding.name) {
if (userProfileService.hasUserProfile()) {
// rootNavigator.navigate(Routes.TrustedNodeSetup.name) {
// [DONE] For androidNode, goto TabContainer
rootNavigator.navigate(Routes.TabContainer.name) {
popUpTo(Routes.Splash.name) { inclusive = true }
}

doCustomNavigationLogic(settings)
} else {
rootNavigator.navigate(Routes.CreateProfile.name) {
popUpTo(Routes.Splash.name) { inclusive = true }
// If firstTimeApp launch, goto Onboarding[clientMode] (androidNode / xClient)
// If not, goto CreateProfile
if (settings.firstLaunch) {
rootNavigator.navigate(Routes.Onboarding.name) {
popUpTo(Routes.Splash.name) { inclusive = true }
}
} else {
rootNavigator.navigate(Routes.CreateProfile.name) {
popUpTo(Routes.Splash.name) { inclusive = true }
}
}
}
}
}
}

/**
* Default implementation in shared is for xClients. Override on node to avoid this.
*/
open fun doCustomNavigationLogic(settings: Settings) {
if (settings.bisqApiUrl.isNotEmpty()) {
// Test if the Bisq remote instance is up and responding
// If yes, goto TabContainer screen.
// If no, goto TrustedNodeSetupScreen
} else {
rootNavigator.navigate(Routes.TrustedNodeSetup.name) {
popUpTo(Routes.Splash.name) { inclusive = true }
}
}
}

private fun cancelJob() {
job?.cancel()
job = null
Expand Down
Loading