From e71b75ffd271b42c2be85c7927dd25598d106d59 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Mon, 18 Nov 2024 00:01:00 +0700
Subject: [PATCH 01/25] Add domain for user profile

---
 bisqapps/androidNode/build.gradle.kts         |   6 +-
 .../user_profile/NodeUserProfileFacade.kt     | 120 ++++++++++++++++++
 .../user_profile/NodeUserProfileModel.kt      |  10 ++
 bisqapps/shared/domain/build.gradle.kts       |  27 ++++
 .../client/service/ApiRequestService.kt       |  39 ++++++
 .../user_profile/ClientUserProfileFacade.kt   |  67 ++++++++++
 .../user_profile/ClientUserProfileModel.kt    |  11 ++
 .../user_profile/CreateUserIdentityRequest.kt |  11 ++
 .../user_profile/UserProfileApiGateway.kt     |  41 ++++++
 .../user_profile/UserProfileResponse.kt       |   6 +
 .../mobile/domain/security/keys/KeyPair.kt    |   9 ++
 .../mobile/domain/security/pow/ProofOfWork.kt |  14 ++
 .../domain/user/identity/PreparedData.kt      |  16 +++
 .../domain/user_profile/UserProfileFacade.kt  |  48 +++++++
 .../domain/user_profile/UserProfileModel.kt   |  32 +++++
 .../bisq/mobile/utils/ByteArrayUtil.kt        |  25 ++++
 16 files changed, 480 insertions(+), 2 deletions(-)
 create mode 100644 bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileFacade.kt
 create mode 100644 bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileModel.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileFacade.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileModel.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/CreateUserIdentityRequest.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileResponse.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/KeyPair.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/pow/ProofOfWork.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/identity/PreparedData.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileFacade.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/utils/ByteArrayUtil.kt

diff --git a/bisqapps/androidNode/build.gradle.kts b/bisqapps/androidNode/build.gradle.kts
index dd13e739..dcf9c7fb 100644
--- a/bisqapps/androidNode/build.gradle.kts
+++ b/bisqapps/androidNode/build.gradle.kts
@@ -1,7 +1,8 @@
+
+import com.google.protobuf.gradle.proto
+import org.apache.tools.ant.taskdefs.condition.Os
 import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
 import org.jetbrains.kotlin.gradle.dsl.JvmTarget
-import org.apache.tools.ant.taskdefs.condition.Os
-import com.google.protobuf.gradle.proto
 
 plugins {
     alias(libs.plugins.kotlinMultiplatform)
@@ -161,6 +162,7 @@ dependencies {
 
     implementation(libs.koin.core)
     implementation(libs.koin.android)
+    implementation(libs.logging.kermit)
 }
 
 // ensure tests run on J17
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileFacade.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileFacade.kt
new file mode 100644
index 00000000..932838c3
--- /dev/null
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileFacade.kt
@@ -0,0 +1,120 @@
+package network.bisq.mobile.android.node.domain.user_profile
+
+import bisq.common.encoding.Hex
+import bisq.security.DigestUtil
+import bisq.security.SecurityService
+import bisq.user.UserService
+import bisq.user.identity.NymIdGenerator
+import bisq.user.identity.UserIdentity
+import co.touchlab.kermit.Logger
+import network.bisq.mobile.domain.user_profile.UserProfileFacade
+import network.bisq.mobile.domain.user_profile.UserProfileModel
+import java.util.Random
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * This is a facade to the Bisq 2 libraries UserIdentityService and UserProfileServices.
+ * It provides the API for the users profile presenter to interact with that domain.
+ * It uses in a in-memory model for the relevant data required for the presenter to reflect the domains state.
+ * Persistence is done inside the Bisq 2 libraries.
+ */
+class NodeUserProfileFacade(
+    override val model: UserProfileModel,
+    val securityService: SecurityService,
+    val userService: UserService
+) :
+    UserProfileFacade {
+
+    companion object {
+        private const val AVATAR_VERSION = 0
+    }
+
+    val log = Logger.withTag("NodeUserProfileFacade")
+
+
+    override fun hasUserProfile(): Boolean {
+        return userService.userIdentityService.userIdentities.isEmpty()
+    }
+
+    override suspend fun generateKeyPair() {
+        model as NodeUserProfileModel
+        val keyPair = securityService.keyBundleService.generateKeyPair()
+        model.keyPair = keyPair
+        val pubKeyHash = DigestUtil.hash(keyPair.public.encoded)
+        model.pubKeyHash = pubKeyHash
+        model.setId(Hex.encode(pubKeyHash))
+        val ts = System.currentTimeMillis()
+        val proofOfWork = userService.userIdentityService.mintNymProofOfWork(pubKeyHash)
+        val powDuration = System.currentTimeMillis() - ts
+        log.i("Proof of work creation completed after $powDuration ms")
+        createSimulatedDelay(powDuration)
+        model.proofOfWork = proofOfWork
+        val powSolution = proofOfWork.solution
+        val nym = NymIdGenerator.generate(pubKeyHash, powSolution)
+        model.setNym(nym)
+
+        // CatHash is in desktop, needs to be reimplemented or the javafx part extracted and refactored into a non javafx lib
+        //  Image image = CatHash.getImage(pubKeyHash,
+        //                                powSolution,
+        //                                CURRENT_AVATARS_VERSION,
+        //                                CreateProfileModel.CAT_HASH_IMAGE_SIZE);
+    }
+
+    override suspend fun createAndPublishNewUserProfile() {
+        model as NodeUserProfileModel
+        model.setIsBusy(true)  // UI should start busy animation based on that property
+        userService.userIdentityService.createAndPublishNewUserProfile(
+            model.nickName.value,
+            model.keyPair,
+            model.pubKeyHash,
+            model.proofOfWork,
+            AVATAR_VERSION,
+            "",
+            ""
+        )
+            .whenComplete { userIdentity: UserIdentity?, throwable: Throwable? ->
+                // UI should stop busy animation and show `next` button
+                model.setIsBusy(false)
+            }
+    }
+
+    override fun findUserProfile(id: String): UserProfileModel? {
+        return getUserProfiles().find { model -> model.id.equals(id) }
+    }
+
+    override fun getUserProfiles(): Sequence<UserProfileModel> {
+        return userService.userIdentityService.userIdentities
+            .asSequence()
+            .map { userIdentity ->
+                val userProfile = userIdentity.userProfile
+                val model = NodeUserProfileModel()
+                model.setNickName(userProfile.nickName)
+                model.setNym(userProfile.nym)
+                model.setId(userProfile.id)
+                model.keyPair = userIdentity.identity.keyBundle.keyPair
+                model.pubKeyHash = userIdentity.userProfile.pubKeyHash
+                model.proofOfWork = userIdentity.userProfile.proofOfWork
+                model
+            }
+    }
+
+    private fun createSimulatedDelay(powDuration: Long) {
+        try {
+            // Proof of work creation for difficulty 65536 takes about 50 ms to 100 ms on a 4 GHz Intel Core i7.
+            // Target duration would be 500-2000 ms, but it is hard to find the right difficulty that works
+            // well also for low-end CPUs. So we take a rather safe lower difficulty value and add here some
+            // delay to not have a too fast flicker-effect in the UI when recreating the nym.
+            // We add a min delay of 200 ms with some randomness to make the usage of the proof of work more
+            // visible.
+            val random: Int = Random().nextInt(100)
+            // Limit to 200-2000 ms
+            Thread.sleep(
+                min(2000.0, max(200.0, (200 + random - powDuration).toDouble()))
+                    .toLong()
+            )
+        } catch (e: InterruptedException) {
+            throw RuntimeException(e)
+        }
+    }
+}
\ No newline at end of file
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileModel.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileModel.kt
new file mode 100644
index 00000000..7b3d05d1
--- /dev/null
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileModel.kt
@@ -0,0 +1,10 @@
+package network.bisq.mobile.android.node.domain.user_profile
+
+import bisq.security.pow.ProofOfWork
+import network.bisq.mobile.domain.user_profile.UserProfileModel
+import java.security.KeyPair
+
+class NodeUserProfileModel : UserProfileModel() {
+    lateinit var keyPair: KeyPair
+    lateinit var proofOfWork: ProofOfWork
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/build.gradle.kts b/bisqapps/shared/domain/build.gradle.kts
index 065b761a..e6c80068 100644
--- a/bisqapps/shared/domain/build.gradle.kts
+++ b/bisqapps/shared/domain/build.gradle.kts
@@ -2,6 +2,7 @@ plugins {
     alias(libs.plugins.kotlinMultiplatform)
     alias(libs.plugins.kotlinCocoapods)
     alias(libs.plugins.androidLibrary)
+    kotlin("plugin.serialization") version "2.0.21"
 }
 
 version = project.findProperty("shared.version") as String
@@ -36,6 +37,32 @@ kotlin {
             implementation(libs.koin.core)
             implementation(libs.kotlinx.coroutines)
             implementation(libs.logging.kermit)
+
+            implementation("io.ktor:ktor-client-core:3.0.1") {
+                exclude(group = "org.slf4j", module = "slf4j-api")
+            }
+            implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3") {
+                exclude(group = "org.slf4j", module = "slf4j-api")
+            }
+            implementation("io.ktor:ktor-client-serialization:3.0.1") {
+                exclude(group = "org.slf4j", module = "slf4j-api")
+            }
+            implementation("io.ktor:ktor-client-json:3.0.1") {
+                exclude(group = "org.slf4j", module = "slf4j-api")
+            }
+            implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.1") {
+                exclude(group = "org.slf4j", module = "slf4j-api")
+            }
+            implementation("io.ktor:ktor-client-cio:3.0.1") {
+                exclude(group = "org.slf4j", module = "slf4j-api")
+            }
+            implementation("io.ktor:ktor-client-content-negotiation:3.0.1") {
+                exclude(group = "org.slf4j", module = "slf4j-api")
+            }
+            implementation("org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin:2.0.21") {
+                exclude(group = "org.slf4j", module = "slf4j-api")
+            }
+            implementation("com.squareup.okio:okio:3.9.1")
         }
         commonTest.dependencies {
             implementation(libs.kotlin.test)
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
new file mode 100644
index 00000000..a644f2ed
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
@@ -0,0 +1,39 @@
+package network.bisq.mobile.client.service
+
+import co.touchlab.kermit.Logger
+import io.ktor.client.HttpClient
+import io.ktor.client.call.body
+import io.ktor.client.engine.cio.CIO
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.client.request.get
+import io.ktor.client.request.post
+import io.ktor.client.request.setBody
+import io.ktor.client.statement.bodyAsText
+import io.ktor.http.contentType
+import io.ktor.serialization.kotlinx.json.json
+import kotlinx.serialization.json.Json
+
+class ApiRequestService(val baseUrl: String) {
+    private val log = Logger.withTag("RequestService")
+
+    suspend fun get(path: String): String {
+        val client = HttpClient(CIO) {
+            install(ContentNegotiation) {
+                json(Json { ignoreUnknownKeys = true })
+            }
+        }
+        return client.get(baseUrl + path).bodyAsText()
+    }
+
+    suspend fun post(path: String, requestBody: Any): String {
+        val client = HttpClient(CIO) {
+            install(ContentNegotiation) {
+                json(Json { ignoreUnknownKeys = true })
+            }
+        }
+        return client.post(baseUrl + path) {
+            contentType(io.ktor.http.ContentType.Application.Json)
+            setBody(requestBody)
+        }.body()
+    }
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileFacade.kt
new file mode 100644
index 00000000..03fecf57
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileFacade.kt
@@ -0,0 +1,67 @@
+package network.bisq.mobile.domain.client.main.user_profile
+
+import co.touchlab.kermit.Logger
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import network.bisq.mobile.client.user_profile.UserProfileResponse
+import network.bisq.mobile.domain.user_profile.UserProfileFacade
+import network.bisq.mobile.domain.user_profile.UserProfileModel
+
+class ClientUserProfileFacade(
+    override val model: UserProfileModel,
+    private val apiGateway: UserProfileApiGateway
+) :
+    UserProfileFacade {
+    private val log = Logger.withTag("IosClientUserProfileController")
+
+    // TODO Dispatchers.IO is not supported on iOS. Either customize or find whats on iOS appropriate.
+    private val coroutineScope = CoroutineScope(Dispatchers.Main)
+
+    override fun hasUserProfile(): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override suspend fun generateKeyPair() {
+        model as ClientUserProfileModel
+        coroutineScope.launch {
+            try {
+                val result = apiGateway.requestPreparedData()
+                model.preparedDataAsJson = result.first
+                val preparedData = result.second
+                model.keyPair = preparedData.keyPair
+                model.proofOfWork = preparedData.proofOfWork
+                model.setNym(preparedData.nym)
+                model.setId(preparedData.id)
+            } catch (e: Exception) {
+                log.e { e.toString() }
+            }
+        }
+    }
+
+    override suspend fun createAndPublishNewUserProfile() {
+        model as ClientUserProfileModel
+        coroutineScope.launch {
+            try {
+                val userProfileResponse: UserProfileResponse =
+                    apiGateway.createAndPublishNewUserProfile(
+                        model.nickName.value,
+                        model.preparedDataAsJson
+                    )
+                require(model.id.value == userProfileResponse.userProfileId)
+                { "userProfileId from model does not match userProfileId from response" }
+            } catch (e: Exception) {
+                log.e { e.toString() }
+            }
+        }
+    }
+
+    override fun getUserProfiles(): Sequence<UserProfileModel> {
+        TODO("Not yet implemented")
+    }
+
+    override fun findUserProfile(id: String): UserProfileModel? {
+        TODO("Not yet implemented")
+    }
+
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileModel.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileModel.kt
new file mode 100644
index 00000000..a06f4822
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileModel.kt
@@ -0,0 +1,11 @@
+package network.bisq.mobile.domain.client.main.user_profile
+
+import network.bisq.mobile.domain.security.keys.KeyPair
+import network.bisq.mobile.domain.security.pow.ProofOfWork
+import network.bisq.mobile.domain.user_profile.UserProfileModel
+
+class ClientUserProfileModel : UserProfileModel() {
+    lateinit var preparedDataAsJson: String
+    lateinit var keyPair: KeyPair
+    lateinit var proofOfWork: ProofOfWork
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/CreateUserIdentityRequest.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/CreateUserIdentityRequest.kt
new file mode 100644
index 00000000..e7a9ecac
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/CreateUserIdentityRequest.kt
@@ -0,0 +1,11 @@
+package network.bisq.mobile.domain.client.main.user_profile
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class CreateUserIdentityRequest(
+    val nickName: String,
+    val terms: String = "",
+    val statement: String = "",
+    val preparedDataJson: String
+)
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
new file mode 100644
index 00000000..ca4b498f
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
@@ -0,0 +1,41 @@
+package network.bisq.mobile.domain.client.main.user_profile
+
+import co.touchlab.kermit.Logger
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.modules.contextual
+import network.bisq.mobile.client.service.ApiRequestService
+import network.bisq.mobile.client.user_profile.UserProfileResponse
+import network.bisq.mobile.domain.user.identity.PreparedData
+import network.bisq.mobile.utils.ByteArrayAsBase64Serializer
+
+class UserProfileApiGateway(
+    private val apiRequestService: ApiRequestService
+) {
+    private val log = Logger.withTag("UserProfileApiGateway")
+
+    suspend fun requestPreparedData(): Pair<String, PreparedData> {
+        val response = apiRequestService.get("user-identity/prepared-data")
+        val json = Json {
+            serializersModule = SerializersModule {
+                contextual(ByteArrayAsBase64Serializer)
+            }
+        }
+        return Pair(response, json.decodeFromString<PreparedData>(response))
+    }
+
+    suspend fun createAndPublishNewUserProfile(
+        nickName: String,
+        preparedDataAsJson: String
+    ): UserProfileResponse {
+        val createUserIdentityRequest = CreateUserIdentityRequest(
+            nickName,
+            "",
+            "",
+            preparedDataAsJson
+        )
+        val response =
+            apiRequestService.post("user-identity/user-identities", createUserIdentityRequest)
+        return Json.decodeFromString(response)
+    }
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileResponse.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileResponse.kt
new file mode 100644
index 00000000..68f07676
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileResponse.kt
@@ -0,0 +1,6 @@
+package network.bisq.mobile.client.user_profile
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class UserProfileResponse(val userProfileId: String)
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/KeyPair.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/KeyPair.kt
new file mode 100644
index 00000000..9dcfada4
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/KeyPair.kt
@@ -0,0 +1,9 @@
+package network.bisq.mobile.domain.security.keys
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class KeyPair(
+    val privateKey: String,
+    val publicKey: String
+)
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/pow/ProofOfWork.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/pow/ProofOfWork.kt
new file mode 100644
index 00000000..5f18dfdf
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/pow/ProofOfWork.kt
@@ -0,0 +1,14 @@
+package network.bisq.mobile.domain.security.pow
+
+import kotlinx.serialization.Contextual
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ProofOfWork(
+    @Contextual val payload: ByteArray,
+    val counter: Long,
+    @Contextual val challenge: ByteArray? = null,
+    val difficulty: Double,
+    @Contextual val solution: ByteArray,
+    val duration: Long
+)
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/identity/PreparedData.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/identity/PreparedData.kt
new file mode 100644
index 00000000..ea87eb41
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/identity/PreparedData.kt
@@ -0,0 +1,16 @@
+package network.bisq.mobile.domain.user.identity
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import network.bisq.mobile.domain.security.keys.KeyPair
+import network.bisq.mobile.domain.security.pow.ProofOfWork
+
+@Serializable
+data class PreparedData(
+    @SerialName("keyPair")
+    val keyPair: KeyPair,
+    val id: String,
+    val nym: String,
+    val proofOfWork: ProofOfWork
+)
+
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileFacade.kt
new file mode 100644
index 00000000..c119d191
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileFacade.kt
@@ -0,0 +1,48 @@
+package network.bisq.mobile.domain.user_profile
+
+interface UserProfileFacade {
+    /**
+     * The model for holding presentation relevant data as well as data needed for creating the user identity.
+     */
+    val model: UserProfileModel
+
+    /**
+     * Returns true if there is a user identity already created.
+     * This should be used to detect a first time user who has no identity created yet and where
+     * we display the create user profile screen.
+     */
+    fun hasUserProfile(): Boolean
+
+    /**
+     * Generates a key pair and derived data as well as proof of work which is used to strengthen
+     * the security of the nym (Bot ID) and the profile image (CatHash) against brute force attacks.
+     * The user can generate unlimited key pairs until they get a nym and profile image they like.
+     * We keep always the last generated one, to be used for creating the user identity, once the user
+     * has provided a nick name and clicked the `create` button.
+     * As the duration for creating the proof of work is very short and most of the time not
+     * noticeable, we add an artificial delay to communicate that there is some proof of work
+     * happening in the background.
+     * The profile ID is the hash of the public key. The Nym is generated based on that hash and
+     * the proof of work solution.
+     * The CatHash image is also created based on that hash and the proof of work solution.
+     */
+    suspend fun generateKeyPair()
+
+    /**
+     * Once the user clicks the `create` button we create a user identity and publish the
+     * user profile to the network.
+     * The user identity contains the key pair and is private data. The UserProfile is public data
+     * and shared with the network.
+     */
+    suspend fun createAndPublishNewUserProfile()
+
+    /**
+     * Find a UserProfileModel based on the give user profile ID
+     */
+    fun findUserProfile(id: String): UserProfileModel?
+
+    /**
+     * Create UserProfileModels from the userIdentities.
+     */
+    fun getUserProfiles(): Sequence<UserProfileModel>
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt
new file mode 100644
index 00000000..dab1d0ff
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt
@@ -0,0 +1,32 @@
+package network.bisq.mobile.domain.user_profile
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+open class UserProfileModel {
+    private val _nickName = MutableStateFlow("")
+    val nickName: StateFlow<String> get() = _nickName
+    fun setNickName(value: String) {
+        _nickName.value = value
+    }
+
+    private val _id = MutableStateFlow("")
+    val id: StateFlow<String> get() = _id
+    fun setId(value: String) {
+        _id.value = value
+    }
+
+    private val _nym = MutableStateFlow("")
+    val nym: StateFlow<String> get() = _nym
+    fun setNym(value: String) {
+        _nym.value = value
+    }
+
+    private val _isBusy = MutableStateFlow(false)
+    val isBusy: StateFlow<Boolean> get() = _isBusy
+    fun setIsBusy(value: Boolean) {
+        _isBusy.value = value
+    }
+
+    lateinit var pubKeyHash: ByteArray
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/utils/ByteArrayUtil.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/utils/ByteArrayUtil.kt
new file mode 100644
index 00000000..19eca39f
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/utils/ByteArrayUtil.kt
@@ -0,0 +1,25 @@
+package network.bisq.mobile.utils
+
+import io.ktor.util.encodeBase64
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import okio.ByteString.Companion.decodeBase64
+
+object ByteArrayAsBase64Serializer : KSerializer<ByteArray> {
+    override val descriptor: SerialDescriptor =
+        PrimitiveSerialDescriptor("ByteArrayAsBase64", PrimitiveKind.STRING)
+
+    override fun serialize(encoder: Encoder, value: ByteArray) {
+        val base64String = value.encodeBase64()
+        encoder.encodeString(base64String)
+    }
+
+    override fun deserialize(decoder: Decoder): ByteArray {
+        val base64String = decoder.decodeString()
+        return base64String.decodeBase64()?.toByteArray()!!
+    }
+}
\ No newline at end of file

From f1af95f94e0ecbca77f071df86957f19acab32d4 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Mon, 18 Nov 2024 16:41:44 +0700
Subject: [PATCH 02/25] Add Supplier to AndroidApplicationService. Remove demo
 code from MainNodePresenter and inti code to AndroidApplicationService. Add
 generateKeyPairInProgress and createAndPublishInProgress properties. Add
 createSimulatedDelay method to ClientUserProfileServiceFacade

Supplier is then used in DI and provides the services from the AndroidApplicationService when used by a lazy getter.
This hack is needed as AndroidApplicationService requires some constructor argumewnts which are not present as application startup but only after the mainActiviy is created.
We startup KOIN in the MainApplication.onCreate method.
We use the MainNodePresenter which gets called its onViewAttached once the mainActiviy is ready and then we can set the applicationService inside the supplier.

I don't like that unsafe lateinit setter strategy and have proposed an alterantive solution in my POC which would work with constructor injection. Maybe there are some hacks in KOIN to get the done in a different manner, but the main problem is that KOIN gets started at the MainApplication.onCreate and not on the MainActivity.onCreate. Only at that moment we have all the basic dependencies from the Android UI framework.
But even if we would move that to the MainActivity.onCreate, we would need to pass the properties to the module in some way.
---
 .../AndroidApplicationService.kt              |  89 +++-
 .../android/node/di/AndroidNodeModule.kt      |  20 +-
 ...ade.kt => NodeUserProfileServiceFacade.kt} |  32 +-
 .../node/presentation/MainNodePresenter.kt    | 424 +-----------------
 bisqapps/shared/domain/build.gradle.kts       |   1 +
 ...e.kt => ClientUserProfileServiceFacade.kt} |  32 +-
 .../domain/user_profile/UserProfileModel.kt   |  14 +-
 ...eFacade.kt => UserProfileServiceFacade.kt} |   2 +-
 8 files changed, 160 insertions(+), 454 deletions(-)
 rename bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/{service => }/AndroidApplicationService.kt (74%)
 rename bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/{NodeUserProfileFacade.kt => NodeUserProfileServiceFacade.kt} (81%)
 rename bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/{ClientUserProfileFacade.kt => ClientUserProfileServiceFacade.kt} (61%)
 rename bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/{UserProfileFacade.kt => UserProfileServiceFacade.kt} (98%)

diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/service/AndroidApplicationService.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt
similarity index 74%
rename from bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/service/AndroidApplicationService.kt
rename to bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt
index ae9e2ca0..8010be8e 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/service/AndroidApplicationService.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt
@@ -14,14 +14,21 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with Bisq. If not, see <http://www.gnu.org/licenses/>.
  */
-package network.bisq.mobile.android.node.service
+package network.bisq.mobile.android.node
 
+import android.content.Context
+import android.os.Process
+import androidx.core.util.Supplier
 import bisq.account.AccountService
 import bisq.application.ApplicationService
 import bisq.application.State
 import bisq.bonded_roles.BondedRolesService
 import bisq.bonded_roles.security_manager.alert.AlertNotificationsService
 import bisq.chat.ChatService
+import bisq.common.facades.FacadeProvider
+import bisq.common.facades.android.AndroidGuavaFacade
+import bisq.common.facades.android.AndroidJdkFacade
+import bisq.common.network.AndroidEmulatorLocalhostFacade
 import bisq.common.observable.Observable
 import bisq.common.util.ExceptionUtil
 import bisq.contract.ContractService
@@ -39,10 +46,14 @@ import bisq.trade.TradeService
 import bisq.user.UserService
 import com.google.common.base.Preconditions
 import lombok.Getter
+import lombok.Setter
 import lombok.extern.slf4j.Slf4j
+import network.bisq.mobile.android.node.service.AndroidMemoryReportService
+import org.bouncycastle.jce.provider.BouncyCastleProvider
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import java.nio.file.Path
+import java.security.Security
 import java.util.Optional
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.TimeUnit
@@ -55,21 +66,73 @@ import java.util.concurrent.TimeUnit
  */
 @Slf4j
 @Getter
-class AndroidApplicationService(androidMemoryService: AndroidMemoryReportService, userDataDir: Path?) :
+class AndroidApplicationService(context: Context, userDataDir: Path?) :
     ApplicationService("android", arrayOf<String>(), userDataDir) {
+
+    @Getter
+    class Supplier {
+        @Setter
+        lateinit var applicationService: AndroidApplicationService
+
+        var securityServiceSupplier: androidx.core.util.Supplier<SecurityService> =
+            Supplier { applicationService.securityService }
+        var networkServiceSupplier: androidx.core.util.Supplier<NetworkService> =
+            Supplier { applicationService.networkService }
+        var identityServiceSupplier: androidx.core.util.Supplier<IdentityService> =
+            Supplier { applicationService.identityService }
+        var bondedRolesServiceSupplier: androidx.core.util.Supplier<BondedRolesService> =
+            Supplier { applicationService.bondedRolesService }
+        var accountServiceSupplier: androidx.core.util.Supplier<AccountService> =
+            Supplier { applicationService.accountService }
+        var offerServiceSupplier: androidx.core.util.Supplier<OfferService> =
+            Supplier { applicationService.offerService }
+        var contractServiceSupplier: androidx.core.util.Supplier<ContractService> =
+            Supplier { applicationService.contractService }
+        var userServiceSupplier: androidx.core.util.Supplier<UserService> =
+            Supplier { applicationService.userService }
+        var chatServiceSupplier: androidx.core.util.Supplier<ChatService> =
+            Supplier { applicationService.chatService }
+        var settingsServiceSupplier: androidx.core.util.Supplier<SettingsService> =
+            Supplier { applicationService.settingsService }
+        var supportServiceSupplier: androidx.core.util.Supplier<SupportService> =
+            Supplier { applicationService.supportService }
+        var systemNotificationServiceSupplier: androidx.core.util.Supplier<SystemNotificationService> =
+            Supplier { applicationService.systemNotificationService }
+        var tradeServiceSupplier: androidx.core.util.Supplier<TradeService> =
+            Supplier { applicationService.tradeService }
+        var alertNotificationsServiceSupplier: androidx.core.util.Supplier<AlertNotificationsService> =
+            Supplier { applicationService.alertNotificationsService }
+        var favouriteMarketsServiceSupplier: androidx.core.util.Supplier<FavouriteMarketsService> =
+            Supplier { applicationService.favouriteMarketsService }
+        var dontShowAgainServiceSupplier: androidx.core.util.Supplier<DontShowAgainService> =
+            Supplier { applicationService.dontShowAgainService }
+    }
     companion object {
         const val STARTUP_TIMEOUT_SEC: Long = 300
         const val SHUTDOWN_TIMEOUT_SEC: Long = 10
-        private val INSTANCE: AndroidApplicationService? = null
         val log: Logger = LoggerFactory.getLogger(ApplicationService::class.java)
     }
 
+    init {
+        FacadeProvider.setLocalhostFacade(AndroidEmulatorLocalhostFacade())
+        FacadeProvider.setJdkFacade(AndroidJdkFacade(Process.myPid()))
+        FacadeProvider.setGuavaFacade(AndroidGuavaFacade())
+
+        // Androids default BC version does not support all algorithms we need, thus we remove
+        // it and add our BC provider
+        Security.removeProvider("BC")
+        Security.addProvider(BouncyCastleProvider())
+    }
+
     val state = Observable(State.INITIALIZE_APP)
     private val shutDownErrorMessage = Observable<String>()
     private val startupErrorMessage = Observable<String>()
 
+    val androidMemoryService = AndroidMemoryReportService(context)
+
     val securityService =
         SecurityService(persistenceService, SecurityService.Config.from(getConfig("security")))
+
     val networkService = NetworkService(
         NetworkServiceConfig.from(
             config.baseDir,
@@ -81,7 +144,7 @@ class AndroidApplicationService(androidMemoryService: AndroidMemoryReportService
         securityService.equihashProofOfWorkService,
         androidMemoryService
     )
-    private val identityService = IdentityService(
+    val identityService = IdentityService(
         persistenceService,
         securityService.keyBundleService,
         networkService
@@ -91,9 +154,9 @@ class AndroidApplicationService(androidMemoryService: AndroidMemoryReportService
         getPersistenceService(),
         networkService
     )
-    private val accountService = AccountService(persistenceService)
-    private val offerService = OfferService(networkService, identityService, persistenceService)
-    private val contractService = ContractService(securityService)
+    val accountService = AccountService(persistenceService)
+    val offerService = OfferService(networkService, identityService, persistenceService)
+    val contractService = ContractService(securityService)
     val userService = UserService(
         persistenceService,
         securityService,
@@ -103,12 +166,12 @@ class AndroidApplicationService(androidMemoryService: AndroidMemoryReportService
     )
     val chatService: ChatService
     val settingsService = SettingsService(persistenceService)
-    private val supportService: SupportService
-    private val systemNotificationService = SystemNotificationService(Optional.empty())
-    private val tradeService: TradeService
-    private val alertNotificationsService: AlertNotificationsService
-    private val favouriteMarketsService: FavouriteMarketsService
-    private val dontShowAgainService: DontShowAgainService
+    val supportService: SupportService
+    val systemNotificationService = SystemNotificationService(Optional.empty())
+    val tradeService: TradeService
+    val alertNotificationsService: AlertNotificationsService
+    val favouriteMarketsService: FavouriteMarketsService
+    val dontShowAgainService: DontShowAgainService
 
 
     init {
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
index ca1d6bb7..324d858b 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
@@ -1,23 +1,29 @@
 package network.bisq.mobile.android.node.di
 
-import bisq.security.SecurityService
-import bisq.user.identity.UserIdentityService
+import network.bisq.mobile.android.node.AndroidApplicationService
 import network.bisq.mobile.android.node.domain.data.repository.NodeGreetingRepository
+import network.bisq.mobile.android.node.domain.user_profile.NodeUserProfileModel
+import network.bisq.mobile.android.node.domain.user_profile.NodeUserProfileServiceFacade
 import network.bisq.mobile.android.node.presentation.MainNodePresenter
-import network.bisq.mobile.android.node.service.AndroidApplicationService
-import network.bisq.mobile.android.node.service.AndroidMemoryReportService
+import network.bisq.mobile.domain.user_profile.UserProfileModel
+import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import network.bisq.mobile.presentation.MainPresenter
 import network.bisq.mobile.presentation.ui.AppPresenter
-import org.koin.android.ext.koin.androidContext
 import org.koin.dsl.bind
 import org.koin.dsl.module
 
 val androidNodeModule = module {
     // this one is for example properties, will be eliminated soon
     single<NodeGreetingRepository> { NodeGreetingRepository() }
-    // this line showcases both, the posibility to change behaviour of the app by changing one definiton
+
+    single { AndroidApplicationService.Supplier() }
+
+    // this line showcases both, the possibility to change behaviour of the app by changing one definition
     // and binding the same obj to 2 different abstractions
-    single<MainPresenter> { MainNodePresenter(get()) } bind AppPresenter::class
+    single<MainPresenter> { MainNodePresenter(get(), get()) } bind AppPresenter::class
+
+    single<UserProfileModel> { NodeUserProfileModel() }
+    single<UserProfileServiceFacade> { NodeUserProfileServiceFacade(get(), get()) }
 
     // Services
 //    TODO might not work because of the jars dependencies, needs more work
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileFacade.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt
similarity index 81%
rename from bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileFacade.kt
rename to bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt
index 932838c3..4a9f3c0d 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileFacade.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt
@@ -7,8 +7,9 @@ import bisq.user.UserService
 import bisq.user.identity.NymIdGenerator
 import bisq.user.identity.UserIdentity
 import co.touchlab.kermit.Logger
-import network.bisq.mobile.domain.user_profile.UserProfileFacade
+import network.bisq.mobile.android.node.AndroidApplicationService
 import network.bisq.mobile.domain.user_profile.UserProfileModel
+import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import java.util.Random
 import kotlin.math.max
 import kotlin.math.min
@@ -19,18 +20,24 @@ import kotlin.math.min
  * It uses in a in-memory model for the relevant data required for the presenter to reflect the domains state.
  * Persistence is done inside the Bisq 2 libraries.
  */
-class NodeUserProfileFacade(
+class NodeUserProfileServiceFacade(
     override val model: UserProfileModel,
-    val securityService: SecurityService,
-    val userService: UserService
+    private val applicationServiceSupplier: AndroidApplicationService.Supplier
 ) :
-    UserProfileFacade {
+    UserProfileServiceFacade {
 
     companion object {
         private const val AVATAR_VERSION = 0
     }
 
-    val log = Logger.withTag("NodeUserProfileFacade")
+    private val log = Logger.withTag("NodeUserProfileFacade")
+
+
+    private val securityService: SecurityService
+        get() = applicationServiceSupplier.securityServiceSupplier.get()
+
+    private val userService: UserService
+        get() = applicationServiceSupplier.userServiceSupplier.get()
 
 
     override fun hasUserProfile(): Boolean {
@@ -39,6 +46,7 @@ class NodeUserProfileFacade(
 
     override suspend fun generateKeyPair() {
         model as NodeUserProfileModel
+        model.setGenerateKeyPairInProgress(true)
         val keyPair = securityService.keyBundleService.generateKeyPair()
         model.keyPair = keyPair
         val pubKeyHash = DigestUtil.hash(keyPair.public.encoded)
@@ -59,11 +67,13 @@ class NodeUserProfileFacade(
         //                                powSolution,
         //                                CURRENT_AVATARS_VERSION,
         //                                CreateProfileModel.CAT_HASH_IMAGE_SIZE);
+
+        model.setGenerateKeyPairInProgress(false)
     }
 
     override suspend fun createAndPublishNewUserProfile() {
         model as NodeUserProfileModel
-        model.setIsBusy(true)  // UI should start busy animation based on that property
+        model.setCreateAndPublishInProgress(true)  // UI should start busy animation based on that property
         userService.userIdentityService.createAndPublishNewUserProfile(
             model.nickName.value,
             model.keyPair,
@@ -75,7 +85,7 @@ class NodeUserProfileFacade(
         )
             .whenComplete { userIdentity: UserIdentity?, throwable: Throwable? ->
                 // UI should stop busy animation and show `next` button
-                model.setIsBusy(false)
+                model.setCreateAndPublishInProgress(false)
             }
     }
 
@@ -102,15 +112,15 @@ class NodeUserProfileFacade(
     private fun createSimulatedDelay(powDuration: Long) {
         try {
             // Proof of work creation for difficulty 65536 takes about 50 ms to 100 ms on a 4 GHz Intel Core i7.
-            // Target duration would be 500-2000 ms, but it is hard to find the right difficulty that works
+            // Target duration would be 200-1000 ms, but it is hard to find the right difficulty that works
             // well also for low-end CPUs. So we take a rather safe lower difficulty value and add here some
             // delay to not have a too fast flicker-effect in the UI when recreating the nym.
             // We add a min delay of 200 ms with some randomness to make the usage of the proof of work more
             // visible.
-            val random: Int = Random().nextInt(100)
+            val random: Int = Random().nextInt(800)
             // Limit to 200-2000 ms
             Thread.sleep(
-                min(2000.0, max(200.0, (200 + random - powDuration).toDouble()))
+                min(1000.0, max(200.0, (200 + random - powDuration).toDouble()))
                     .toLong()
             )
         } catch (e: InterruptedException) {
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt
index 350ad3b2..14a374ed 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt
@@ -1,438 +1,32 @@
 package network.bisq.mobile.android.node.presentation
 
 import android.app.Activity
-import android.os.Build
-import bisq.application.State
-import bisq.bonded_roles.market_price.MarketPrice
-import bisq.chat.ChatChannelDomain
-import bisq.chat.ChatMessageType
-import bisq.chat.common.CommonPublicChatMessage
-import bisq.chat.two_party.TwoPartyPrivateChatChannel
-import bisq.chat.two_party.TwoPartyPrivateChatMessage
-import bisq.common.currency.MarketRepository
-import bisq.common.locale.LanguageRepository
-import bisq.common.network.TransportType
-import bisq.common.observable.Observable
-import bisq.common.observable.Pin
-import bisq.common.observable.collection.CollectionObserver
-import bisq.common.timer.Scheduler
-import bisq.common.util.MathUtils
-import bisq.common.util.StringUtils
-import bisq.network.p2p.node.Node
-import bisq.network.p2p.services.data.BroadcastResult
-import bisq.security.DigestUtil
-import bisq.security.SecurityService
-import bisq.user.identity.NymIdGenerator
-import bisq.user.identity.UserIdentity
-import bisq.user.identity.UserIdentityService
-import bisq.user.profile.UserProfile
-import kotlinx.coroutines.*
-import network.bisq.mobile.android.node.AndroidNodeGreeting
+import network.bisq.mobile.android.node.AndroidApplicationService
 import network.bisq.mobile.android.node.domain.data.repository.NodeGreetingRepository
-import network.bisq.mobile.android.node.domain.model.UserProfileModel
-import network.bisq.mobile.android.node.service.AndroidApplicationService
-import network.bisq.mobile.android.node.service.AndroidMemoryReportService
-import network.bisq.mobile.domain.data.BackgroundDispatcher
 import network.bisq.mobile.domain.data.model.Greeting
 import network.bisq.mobile.domain.data.repository.GreetingRepository
 import network.bisq.mobile.presentation.MainPresenter
-import java.util.*
-import java.util.concurrent.CompletableFuture
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.atomic.AtomicLong
-import kotlin.jvm.optionals.getOrElse
-import kotlin.math.max
-import kotlin.random.Random
 
 @Suppress("UNCHECKED_CAST")
-class MainNodePresenter(greetingRepository: NodeGreetingRepository) :
-    MainPresenter(greetingRepository as GreetingRepository<Greeting>) {
-    companion object {
-        private const val AVATAR_VERSION = 0
-    }
-
-    private val logMessage: Observable<String> = Observable("")
-    val state = Observable(State.INITIALIZE_APP)
-    private val shutDownErrorMessage = Observable<String>()
-    private val startupErrorMessage = Observable<String>()
-
-    private val profileModel = UserProfileModel()
-
-    private val coroutineScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
-    private val loggingScope = CoroutineScope(BackgroundDispatcher)
-
-    private lateinit var applicationService: AndroidApplicationService
-    private lateinit var userIdentityService: UserIdentityService
-    private lateinit var securityService: SecurityService
-
-
-    init {
-        CoroutineScope(BackgroundDispatcher).launch {
-            greetingRepository.create(AndroidNodeGreeting())
-        }
-    }
+class MainNodePresenter(
+    greetingRepository: NodeGreetingRepository,
+    private val supplier: AndroidApplicationService.Supplier
+) : MainPresenter(greetingRepository as GreetingRepository<Greeting>) {
 
     override fun onViewAttached() {
         super.onViewAttached()
-        logMessage.addObserver {
-            println(it)
-        }
 
-        // TODO this should be injected to the presenter
         val context = (view as Activity).applicationContext
         val filesDirsPath = (view as Activity).filesDir.toPath()
-        val androidMemoryService = AndroidMemoryReportService(context)
-        log("Path for files dir $filesDirsPath")
-        applicationService = AndroidApplicationService(androidMemoryService, filesDirsPath)
-        userIdentityService = applicationService.userService.userIdentityService
-        securityService = applicationService.securityService
-
-        launchServices()
+        supplier.applicationService =
+            AndroidApplicationService(context, filesDirsPath)
+        supplier.applicationService.initialize().join()
     }
 
     override fun onDestroying() {
-        applicationService.shutdown()
+        supplier.applicationService.shutdown()
         super.onDestroying()
     }
 
-    private fun log(message: String) {
-        loggingScope.launch {
-            logMessage.set(message)
-        }
-    }
-
-    private fun launchServices() {
-        coroutineScope.launch {
-            observeAppState()
-            applicationService.readAllPersisted().join()
-            applicationService.initialize().join()
-
-            printDefaultKeyId()
-            printLanguageCode()
-
-            if (userIdentityService.userIdentities.isEmpty()) {
-                //createUserIfNoneExist();
-                initializeUserService()
-
-                // mock profile creation and wait until done.
-                createUserProfile("Android user " + Random(4234234).nextInt(100)).join()
-                log("Created profile for user")
-            }
-
-            // User profile data
-            printUserProfiles()
-
-            // network metadata
-            observeNetworkState()
-            observeNumConnections()
-            fetchMarketPrice(500L)
-
-            // trading
-            observePrivateMessages()
-
-            // Less priority for MVP
-            publishRandomChatMessage();
-            observeChatMessages(5)
-            maybeRemoveMyOldChatMessages()
-            sendRandomMessagesEvery(60L * 100L)
-        }
-    }
-
-
-    ///// SETUP
-    private fun printUserProfiles() {
-        applicationService.userService.userIdentityService.userIdentities.stream()
-            .map { obj: UserIdentity -> obj.userProfile }
-            .map { userProfile: UserProfile -> userProfile.userName + " [" + userProfile.nym + "]" }
-            .forEach { userName: String -> log("My profile $userName") }
-    }
-
-    private fun createUserProfile(nickName: String): CompletableFuture<UserIdentity> {
-        // UI can listen to that state change and show busy animation
-        profileModel.isBusy.set(true)
-        return userIdentityService.createAndPublishNewUserProfile(
-            nickName,
-            profileModel.keyPair,
-            profileModel.pubKeyHash,
-            profileModel.proofOfWork,
-            AVATAR_VERSION,
-            profileModel.terms.get(),
-            profileModel.statement.get()
-        )
-            .whenComplete { userIdentity: UserIdentity?, throwable: Throwable? ->
-                // UI can listen to that state change and stop busy animation and show close button
-                profileModel.isBusy.set(false)
-            }
-    }
-
-    private fun initializeUserService() {
-        val userIdentities = userIdentityService.userIdentities
-        if (userIdentities.isEmpty()) {
-            // Generate
-            onGenerateKeyPair()
-        } else {
-            // If we have already a user profile we don't do anything. Leave it to the parent
-            // controller to skip and not even create initialize controller.
-            log("We have already a user profile.")
-        }
-    }
-
-    private fun onGenerateKeyPair() {
-        val keyPair = securityService.keyBundleService.generateKeyPair()
-        profileModel.keyPair = keyPair
-        val pubKeyHash = DigestUtil.hash(keyPair.public.encoded)
-        profileModel.pubKeyHash = pubKeyHash
-        val proofOfWork = userIdentityService.mintNymProofOfWork(pubKeyHash)
-        profileModel.proofOfWork = proofOfWork
-        val powSolution = proofOfWork.solution
-        val nym = NymIdGenerator.generate(pubKeyHash, powSolution)
-        profileModel.nym.set(nym) // nym will be created on demand from pubKeyHash and pow
-        // CatHash is in desktop, needs to be reimplemented or the javafx part extracted and refactored into a non javafx lib
-        //  Image image = CatHash.getImage(pubKeyHash,
-        //                                powSolution,
-        //                                CURRENT_AVATARS_VERSION,
-        //                                CreateProfileModel.CAT_HASH_IMAGE_SIZE);
-    }
-
-    private fun printDefaultKeyId() {
-        log(
-            "Default key ID: ${applicationService.securityService.keyBundleService.defaultKeyId}"
-        )
-    }
-
-    private fun printLanguageCode() {
-        log(
-            "Language: ${LanguageRepository.getDisplayLanguage(applicationService.settingsService.languageCode.get())}"
-        )
-    }
-
-    /////// USE CASES
-
-    private fun observeAppState() {
-        applicationService.state.addObserver { state: State ->
-            log("Application state: ${state.name}")
-        }
-    }
-
-    /**
-     * This is key to bisq androidNode. Trading will happen through private messaging for us.
-     */
-    private fun observePrivateMessages() {
-        val pinByChannelId: MutableMap<String, Pin> = HashMap()
-        applicationService.chatService.twoPartyPrivateChatChannelService.channels
-            .addObserver(object : CollectionObserver<TwoPartyPrivateChatChannel> {
-                override fun add(channel: TwoPartyPrivateChatChannel) {
-                    log("Private channel: ${channel.displayString}")
-                    pinByChannelId.computeIfAbsent(
-                        channel.id
-                    ) { _: String? ->
-                        channel.chatMessages.addObserver(object :
-                            CollectionObserver<TwoPartyPrivateChatMessage> {
-                            override fun add(message: TwoPartyPrivateChatMessage) {
-                                if (message.chatMessageType == null) {
-                                    return
-                                }
-                                var text = ""
-                                @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
-                                text = when (message.chatMessageType) {
-                                    ChatMessageType.TEXT -> {
-                                        message.text
-                                    }
-
-                                    ChatMessageType.LEAVE -> {
-                                        "PEER LEFT " + message.text
-                                        // leave handling not working yet correctly
-                                        /* Scheduler.run(()->applicationService.chatService.getTwoPartyPrivateChatChannelService().leaveChannel(channel))
-                                                        .after(500);*/
-                                    }
-
-                                    ChatMessageType.TAKE_BISQ_EASY_OFFER -> {
-                                        "TAKE_BISQ_EASY_OFFER " + message.text
-                                    }
-
-                                    ChatMessageType.PROTOCOL_LOG_MESSAGE -> {
-                                        "PROTOCOL_LOG_MESSAGE " + message.text
-                                    }
-                                }
-                                val displayString = "[" + channel.displayString + "] " + text
-                                log("Private message $displayString")
-                            }
-
-                            override fun remove(o: Any) {
-                                // We do not support remove of PM
-                            }
-
-                            override fun clear() {
-                            }
-                        })
-                    }
-                }
-
-                override fun remove(o: Any) {
-                    if (o is TwoPartyPrivateChatChannel) {
-                        val id: String = o.id
-                        if (pinByChannelId.containsKey(id)) {
-                            pinByChannelId[id]!!.unbind()
-                            pinByChannelId.remove(id)
-                        }
-                        log("Closed private channel ${o.displayString}")
-                    }
-                }
-
-                override fun clear() {
-                }
-            })
-    }
-
-    private fun observeNetworkState() {
-        Optional.ofNullable(
-            applicationService.networkService.defaultNodeStateByTransportType[TransportType.CLEAR]
-        )
-            .orElseGet { null }
-            .addObserver { state: Node.State -> log("Network state: $state") }
-    }
-
-    private fun observeNumConnections() {
-        val serviceNode =
-            applicationService.networkService.serviceNodesByTransport.findServiceNode(TransportType.CLEAR)
-                .orElseGet { null }
-        val defaultNode = serviceNode?.defaultNode
-        val peerGroupManager = serviceNode?.peerGroupManager?.orElseGet { null }
-        val peerGroupService = peerGroupManager?.peerGroupService
-        val numConnections = AtomicLong()
-        Scheduler.run {
-            val currentNumConnections = peerGroupService?.getAllConnectedPeers(defaultNode)?.count()
-            if (numConnections.get() != currentNumConnections) {
-                if (currentNumConnections != null) {
-                    numConnections.set(currentNumConnections)
-                }
-                log("Number of connections: $currentNumConnections")
-            }
-        }.periodically(100)
-    }
-
-    /**
-     * Fetch the market price, if not avail it will retry every delay ms.
-     * TODO: change to fetch continously updating an observable that can be printed with each change
-     */
-    private fun fetchMarketPrice(retryDelay: Long) {
-        val priceQuote = applicationService.bondedRolesService.marketPriceService
-            .findMarketPrice(MarketRepository.getUSDBitcoinMarket())
-            .map { e: MarketPrice ->
-                MathUtils.roundDouble(e.priceQuote.value / 10000.0, 2).toString() + " BTC/USD"
-            }
-        if (!priceQuote.isPresent) {
-            Scheduler.run { this.fetchMarketPrice(retryDelay) }.after(retryDelay)
-        } else {
-            log("Market price: ${priceQuote.get()}")
-        }
-    }
-
-    private fun sendRandomMessagesEvery(delayMs: Long) {
-        CoroutineScope(BackgroundDispatcher).launch {
-            while (isActive) { // Coroutine will keep running while active
-                publishRandomChatMessage()
-                delay(delayMs) // Delay for 1 minute (60,000 ms)
-            }
-        }
-    }
 
-    private fun publishRandomChatMessage() {
-        val userService = applicationService.userService
-        val userIdentityService = userService.userIdentityService
-        val chatService = applicationService.chatService
-        val chatChannelDomain = ChatChannelDomain.DISCUSSION
-        val discussionChannelService =
-            chatService.commonPublicChatChannelServices[chatChannelDomain]
-        val channel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
-            discussionChannelService!!.channels.stream().findFirst().orElseThrow()
-        } else {
-            discussionChannelService!!.channels.stream().findFirst().getOrElse { null }
-        }
-        val userIdentity = userIdentityService.selectedUserIdentity
-        discussionChannelService.publishChatMessage(
-            "Dev message " + Random(34532454325).nextInt(100),
-            Optional.empty(),
-            channel,
-            userIdentity
-        ).whenComplete { result: BroadcastResult?, _: Throwable? ->
-            log("publishChatMessage result $result")
-        }
-    }
-
-    private fun observeChatMessages(numLastMessages: Int) {
-        val userService = applicationService.userService
-        val chatService = applicationService.chatService
-        val chatChannelDomain = ChatChannelDomain.DISCUSSION
-        val discussionChannelService =
-            chatService.commonPublicChatChannelServices[chatChannelDomain]
-        val channel = discussionChannelService!!.channels.stream().findFirst().orElseGet { null }
-        val toSkip = max(0.0, (channel.chatMessages.size - numLastMessages).toDouble())
-            .toInt()
-        val displayedMessages: MutableList<String> = ArrayList()
-        channel.chatMessages.stream()
-            .sorted(Comparator.comparingLong { obj: CommonPublicChatMessage -> obj.date })
-            .map { message: CommonPublicChatMessage ->
-                displayedMessages.add(message.id)
-                val authorUserProfileId = message.authorUserProfileId
-                val userName = userService.userProfileService.findUserProfile(authorUserProfileId)
-                    .map { obj: UserProfile -> obj.userName }
-                    .orElse("N/A")
-                maybeRemoveMyOldChatMessages()
-                "{" + userName + "} " + message.text
-            }
-            .skip(toSkip.toLong())
-            .forEach { e: String -> log("Chat message $e") }
-
-        channel.chatMessages.addObserver(object : CollectionObserver<CommonPublicChatMessage> {
-            override fun add(message: CommonPublicChatMessage) {
-                if (displayedMessages.contains(message.id)) {
-                    return
-                }
-                displayedMessages.add(message.id)
-                val authorUserProfileId = message.authorUserProfileId
-                val userName = userService.userProfileService.findUserProfile(authorUserProfileId)
-                    .map { obj: UserProfile -> obj.userName }
-                    .orElse("N/A")
-                val text = message.text
-                val displayString = "{$userName} $text"
-                log("Chat message: $displayString")
-                maybeRemoveMyOldChatMessages()
-            }
-
-            override fun remove(o: Any) {
-                if (o is CommonPublicChatMessage) log("Removed chat message: ${o.text}")
-            }
-
-            override fun clear() {
-            }
-        })
-    }
-
-    private fun maybeRemoveMyOldChatMessages() {
-        val userService = applicationService.userService
-        val userIdentityService = userService.userIdentityService
-        val userIdentity = userIdentityService.selectedUserIdentity
-        val chatService = applicationService.chatService
-        val chatChannelDomain = ChatChannelDomain.DISCUSSION
-        val discussionChannelService =
-            chatService.commonPublicChatChannelServices[chatChannelDomain]
-        val channel = discussionChannelService!!.channels.stream().findFirst().orElseGet { null }
-        val myProfileId = userIdentity.userProfile.id
-        log("Number of chat messages: ${channel.chatMessages.size}")
-        val expireDate = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(30)
-        channel.chatMessages.stream()
-            .filter { message: CommonPublicChatMessage -> message.date < expireDate }
-            .filter { message: CommonPublicChatMessage -> myProfileId == message.authorUserProfileId }
-            .forEach { message: CommonPublicChatMessage ->
-                log("Remove my old chat message: ${StringUtils.truncate(message.text, 10)}")
-                discussionChannelService.deleteChatMessage(
-                    message,
-                    userIdentity.networkIdWithKeyPair
-                ).whenComplete { r: BroadcastResult, t: Throwable? ->
-                    log("Remove message result: ${t == null}")
-                    log("Error: $r")
-                }
-            }
-    }
 }
\ No newline at end of file
diff --git a/bisqapps/shared/domain/build.gradle.kts b/bisqapps/shared/domain/build.gradle.kts
index e6c80068..98232555 100644
--- a/bisqapps/shared/domain/build.gradle.kts
+++ b/bisqapps/shared/domain/build.gradle.kts
@@ -63,6 +63,7 @@ kotlin {
                 exclude(group = "org.slf4j", module = "slf4j-api")
             }
             implementation("com.squareup.okio:okio:3.9.1")
+            implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
         }
         commonTest.dependencies {
             implementation(libs.kotlin.test)
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
similarity index 61%
rename from bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileFacade.kt
rename to bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
index 03fecf57..ea14967c 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
@@ -3,16 +3,21 @@ package network.bisq.mobile.domain.client.main.user_profile
 import co.touchlab.kermit.Logger
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
+import kotlinx.datetime.Clock
 import network.bisq.mobile.client.user_profile.UserProfileResponse
-import network.bisq.mobile.domain.user_profile.UserProfileFacade
 import network.bisq.mobile.domain.user_profile.UserProfileModel
+import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.random.Random
 
-class ClientUserProfileFacade(
+class ClientUserProfileServiceFacade(
     override val model: UserProfileModel,
     private val apiGateway: UserProfileApiGateway
 ) :
-    UserProfileFacade {
+    UserProfileServiceFacade {
     private val log = Logger.withTag("IosClientUserProfileController")
 
     // TODO Dispatchers.IO is not supported on iOS. Either customize or find whats on iOS appropriate.
@@ -26,15 +31,22 @@ class ClientUserProfileFacade(
         model as ClientUserProfileModel
         coroutineScope.launch {
             try {
+                model.setGenerateKeyPairInProgress(true)
+                val ts = Clock.System.now().toEpochMilliseconds()
                 val result = apiGateway.requestPreparedData()
                 model.preparedDataAsJson = result.first
                 val preparedData = result.second
+
+                createSimulatedDelay(Clock.System.now().toEpochMilliseconds() - ts)
+
                 model.keyPair = preparedData.keyPair
                 model.proofOfWork = preparedData.proofOfWork
                 model.setNym(preparedData.nym)
                 model.setId(preparedData.id)
             } catch (e: Exception) {
                 log.e { e.toString() }
+            } finally {
+                model.setGenerateKeyPairInProgress(false)
             }
         }
     }
@@ -43,6 +55,7 @@ class ClientUserProfileFacade(
         model as ClientUserProfileModel
         coroutineScope.launch {
             try {
+                model.setCreateAndPublishInProgress(true)
                 val userProfileResponse: UserProfileResponse =
                     apiGateway.createAndPublishNewUserProfile(
                         model.nickName.value,
@@ -52,6 +65,8 @@ class ClientUserProfileFacade(
                 { "userProfileId from model does not match userProfileId from response" }
             } catch (e: Exception) {
                 log.e { e.toString() }
+            } finally {
+                model.setCreateAndPublishInProgress(false)
             }
         }
     }
@@ -64,4 +79,15 @@ class ClientUserProfileFacade(
         TODO("Not yet implemented")
     }
 
+    private suspend fun createSimulatedDelay(requestDuration: Long) {
+        // Proof of work creation for difficulty 65536 takes about 50 ms to 100 ms on a 4 GHz Intel Core i7.
+        // The API request is likely also quite fast
+        // We design a delay of 200 - 1000 ms taking into account a random value and the requestDuration.
+        // The delay should avoid a too fast flicker-effect in the UI when recreating the nym,
+        // and should make the usage of the proof of work more visible.
+        val random: Int = Random.nextInt(800)
+        val delayDuration = min(1000.0, max(200.0, (200 + random - requestDuration).toDouble()))
+            .toLong()
+        delay(delayDuration)
+    }
 }
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt
index dab1d0ff..780b3901 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt
@@ -22,10 +22,16 @@ open class UserProfileModel {
         _nym.value = value
     }
 
-    private val _isBusy = MutableStateFlow(false)
-    val isBusy: StateFlow<Boolean> get() = _isBusy
-    fun setIsBusy(value: Boolean) {
-        _isBusy.value = value
+    private val _generateKeyPairInProgress = MutableStateFlow(false)
+    val generateKeyPairInProgress: StateFlow<Boolean> get() = _generateKeyPairInProgress
+    fun setGenerateKeyPairInProgress(value: Boolean) {
+        _generateKeyPairInProgress.value = value
+    }
+
+    private val _createAndPublishInProgress = MutableStateFlow(false)
+    val createAndPublishInProgress: StateFlow<Boolean> get() = _createAndPublishInProgress
+    fun setCreateAndPublishInProgress(value: Boolean) {
+        _createAndPublishInProgress.value = value
     }
 
     lateinit var pubKeyHash: ByteArray
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileServiceFacade.kt
similarity index 98%
rename from bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileFacade.kt
rename to bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileServiceFacade.kt
index c119d191..36556eed 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileServiceFacade.kt
@@ -1,6 +1,6 @@
 package network.bisq.mobile.domain.user_profile
 
-interface UserProfileFacade {
+interface UserProfileServiceFacade {
     /**
      * The model for holding presentation relevant data as well as data needed for creating the user identity.
      */

From d631060c541ef76c3735fbd69ff8cf7de29cd21c Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Mon, 18 Nov 2024 17:06:27 +0700
Subject: [PATCH 03/25] Rename AndroidModule to AndroidClientModule Add DI
 config to AndroidClientModule Remove ICreateProfilePresenter as there is only
 one instance and not expected a use case for multiple instances. Add
 UserProfileRepository holding model and serviceFacade Adjust Screen and
 Presenter.

Discussion:
To me the usage of the repository does not make much sense here.
The 2 relevant methods do not map into the CRUD pattern which seems to be the design philosophy for repositories.
I would suggest to use it only when teh CRUD pattern and persistence is used and appropriate. And to inject the model and the service facade to the presenter in the other cases.
Otherwise we just create a useless wrapper.
@rodvar: If you would implement the usage here in a different way, can you provide with the given use case an implementation example?
---
 .../bisq/mobile/client/MainApplication.kt     |  4 +--
 .../mobile/client/di/AndroidClientModule.kt   | 19 ++++++++++++
 .../bisq/mobile/client/di/AndroidModule.kt    |  8 -----
 .../domain/data/repository/Repositories.kt    |  9 ++++--
 .../bisq/mobile/domain/di/DomainModule.kt     | 10 ++++--
 .../presentation/di/PresentationModule.kt     | 31 ++++++++++---------
 .../ui/uicases/startup/CreateProfileScreen.kt | 13 +++++---
 7 files changed, 60 insertions(+), 34 deletions(-)
 create mode 100644 bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
 delete mode 100644 bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidModule.kt

diff --git a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainApplication.kt b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainApplication.kt
index 63d95793..1cb7a55e 100644
--- a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainApplication.kt
+++ b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainApplication.kt
@@ -1,7 +1,7 @@
 package network.bisq.mobile.client
 
 import android.app.Application
-import network.bisq.mobile.client.di.androidModule
+import network.bisq.mobile.client.di.androidClientModule
 import network.bisq.mobile.domain.di.domainModule
 import network.bisq.mobile.presentation.di.presentationModule
 import org.koin.android.ext.koin.androidContext
@@ -14,7 +14,7 @@ class MainApplication: Application() {
 
         startKoin {
             androidContext(this@MainApplication)
-            modules(listOf(domainModule, presentationModule, androidModule)) 
+            modules(listOf(domainModule, presentationModule, androidClientModule))
         }
     }
 }
diff --git a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
new file mode 100644
index 00000000..93bdcddb
--- /dev/null
+++ b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
@@ -0,0 +1,19 @@
+package network.bisq.mobile.client.di
+
+import network.bisq.mobile.client.service.ApiRequestService
+import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileModel
+import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileServiceFacade
+import network.bisq.mobile.domain.client.main.user_profile.UserProfileApiGateway
+import network.bisq.mobile.domain.data.repository.UserProfileRepository
+import network.bisq.mobile.domain.user_profile.UserProfileModel
+import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
+import org.koin.dsl.module
+
+val androidClientModule = module {
+    single<UserProfileModel> { ClientUserProfileModel() }
+    single { ApiRequestService("10.0.2.2") }
+    single { UserProfileApiGateway(get()) }
+    single<UserProfileServiceFacade> { ClientUserProfileServiceFacade(get(), get()) }
+    single { UserProfileRepository(get(), get()) }
+
+}
diff --git a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidModule.kt b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidModule.kt
deleted file mode 100644
index 84390ea3..00000000
--- a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidModule.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package network.bisq.mobile.client.di
-
-import network.bisq.mobile.presentation.MainPresenter
-import org.koin.dsl.module
-
-val androidModule = module {
-    // add your own modules
-}
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt
index 0ebf0612..0b09afc4 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt
@@ -1,11 +1,16 @@
 package network.bisq.mobile.domain.data.repository
 
-import network.bisq.mobile.domain.data.model.*
+import network.bisq.mobile.domain.data.model.BisqStats
+import network.bisq.mobile.domain.data.model.BtcPrice
+import network.bisq.mobile.domain.data.model.Greeting
+import network.bisq.mobile.domain.data.model.Settings
+import network.bisq.mobile.domain.user_profile.UserProfileModel
+import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 
 // this way of definingsupports both platforms
 // add your repositories here and then in your DI module call this classes for instanciation
 open class GreetingRepository<T: Greeting>: SingleObjectRepository<T>()
 open class BisqStatsRepository: SingleObjectRepository<BisqStats>()
 open class BtcPriceRepository: SingleObjectRepository<BtcPrice>()
-open class UserProfileRepository: SingleObjectRepository<UserProfile>()
 open class SettingsRepository: SingleObjectRepository<Settings>()
+class UserProfileRepository(val model: UserProfileModel, val service: UserProfileServiceFacade)
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt
index 99374209..767918f4 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt
@@ -1,13 +1,17 @@
 package network.bisq.mobile.domain.di
 
-import network.bisq.mobile.domain.data.model.*
-import network.bisq.mobile.domain.data.repository.*
+import network.bisq.mobile.domain.data.model.Greeting
+import network.bisq.mobile.domain.data.repository.BisqStatsRepository
+import network.bisq.mobile.domain.data.repository.BtcPriceRepository
+import network.bisq.mobile.domain.data.repository.GreetingRepository
+import network.bisq.mobile.domain.data.repository.SettingsRepository
+import network.bisq.mobile.domain.data.repository.UserProfileRepository
 import org.koin.dsl.module
 
 val domainModule = module {
     single<GreetingRepository<Greeting>> { GreetingRepository() }
     single<BisqStatsRepository> { BisqStatsRepository() }
     single<BtcPriceRepository> { BtcPriceRepository() }
-    single<UserProfileRepository> { UserProfileRepository() }
     single<SettingsRepository> { SettingsRepository() }
+    single { UserProfileRepository(get(), get()) }
 }
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt
index 75157adc..dd0d6b06 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt
@@ -1,26 +1,24 @@
 package network.bisq.mobile.presentation.di
 
-import androidx.compose.runtime.mutableStateOf
 import androidx.navigation.NavController
 import androidx.navigation.NavHostController
 import network.bisq.mobile.presentation.MainPresenter
 import network.bisq.mobile.presentation.ui.AppPresenter
-import network.bisq.mobile.presentation.ui.uicases.startup.ICreateProfilePresenter
+import network.bisq.mobile.presentation.ui.uicases.GettingStartedPresenter
+import network.bisq.mobile.presentation.ui.uicases.IGettingStarted
+import network.bisq.mobile.presentation.ui.uicases.startup.CreateProfilePresenter
 import network.bisq.mobile.presentation.ui.uicases.startup.IOnboardingPresenter
 import network.bisq.mobile.presentation.ui.uicases.startup.ISplashPresenter
 import network.bisq.mobile.presentation.ui.uicases.startup.ITrustedNodeSetupPresenter
-import network.bisq.mobile.presentation.ui.uicases.startup.CreateProfilePresenter
 import network.bisq.mobile.presentation.ui.uicases.startup.OnBoardingPresenter
 import network.bisq.mobile.presentation.ui.uicases.startup.SplashPresenter
 import network.bisq.mobile.presentation.ui.uicases.startup.TrustedNodeSetupPresenter
-import network.bisq.mobile.presentation.ui.uicases.GettingStartedPresenter
-import network.bisq.mobile.presentation.ui.uicases.IGettingStarted
 import org.koin.core.qualifier.named
 import org.koin.dsl.bind
 import org.koin.dsl.module
 
 val presentationModule = module {
-    
+
     single(named("RootNavController")) { getKoin().getProperty<NavHostController>("RootNavController") }
     single(named("TabNavController")) { getKoin().getProperty<NavHostController>("TabNavController") }
 
@@ -28,14 +26,19 @@ val presentationModule = module {
 
     // TODO: Since NavController will be required for almost all Presenters for basic navigation
     // Added this as top constructor level param. Is this okay?
-    single {
-        (navController: NavController) -> SplashPresenter(
+    single { (navController: NavController) ->
+        SplashPresenter(
             get(),
             navController = navController
         )
     } bind ISplashPresenter::class
 
-    single { (navController: NavController) -> OnBoardingPresenter(get(), navController) } bind IOnboardingPresenter::class
+    single { (navController: NavController) ->
+        OnBoardingPresenter(
+            get(),
+            navController
+        )
+    } bind IOnboardingPresenter::class
 
     single<GettingStartedPresenter> {
         GettingStartedPresenter(
@@ -45,16 +48,16 @@ val presentationModule = module {
         )
     } bind IGettingStarted::class
 
-    single {
-        (navController: NavController) -> CreateProfilePresenter(
+    single { (navController: NavController) ->
+        CreateProfilePresenter(
             get(),
             navController = navController,
             userProfileRepository = get()
         )
-    } bind ICreateProfilePresenter::class
+    }
 
-    single {
-        (navController: NavController) -> TrustedNodeSetupPresenter(
+    single { (navController: NavController) ->
+        TrustedNodeSetupPresenter(
             get(),
             navController = navController,
             settingsRepository = get()
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt
index 9e3eace4..f7b19e05 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt
@@ -10,6 +10,7 @@ import androidx.compose.runtime.collectAsState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
+import androidx.navigation.NavController
 import androidx.navigation.NavHostController
 import bisqapps.shared.presentation.generated.resources.Res
 import bisqapps.shared.presentation.generated.resources.img_bot_image
@@ -23,8 +24,10 @@ import network.bisq.mobile.presentation.ui.theme.BisqTheme
 import org.jetbrains.compose.resources.ExperimentalResourceApi
 import org.jetbrains.compose.resources.painterResource
 import org.koin.compose.koinInject
-import org.koin.core.parameter.parametersOf
 import org.koin.core.qualifier.named
+import kotlinx.coroutines.flow.StateFlow
+import cafe.adriel.lyricist.LocalStrings
+import org.koin.core.parameter.parametersOf
 
 interface ICreateProfilePresenter: ViewPresenter {
     val profileName: StateFlow<String>
@@ -81,12 +84,12 @@ fun CreateProfileScreen(
         Image(painterResource(Res.drawable.img_bot_image), "Crypto geHostnerated image (PoW)") // TODO: Translation
         Spacer(modifier = Modifier.height(32.dp))
         BisqText.baseRegular(
-            text = presenter.nym.collectAsState().value,
+            text = "Sleepily-Distracted-Zyophyte-257",
             color = BisqTheme.colors.light1,
         )
         Spacer(modifier = Modifier.height(12.dp))
         BisqText.baseRegular(
-            text = presenter.id.collectAsState().value,
+            text = strings.onboarding_createProfile_nym,
             color = BisqTheme.colors.grey2,
         )
         Spacer(modifier = Modifier.height(38.dp))
@@ -94,12 +97,12 @@ fun CreateProfileScreen(
             text = strings.onboarding_createProfile_regenerate,
             backgroundColor = BisqTheme.colors.dark5,
             padding = PaddingValues(horizontal = 64.dp, vertical = 12.dp),
-            onClick = {presenter.onGenerateKeyPair() }
+            onClick = {}
         )
         Spacer(modifier = Modifier.height(40.dp))
         BisqButton(
             strings.buttons_next,
-            onClick = { presenter.onCreateAndPublishNewUserProfile() },
+            onClick = { presenter.navigateToNextScreen() },
             backgroundColor = if (profileName.isEmpty()) BisqTheme.colors.primaryDisabled else BisqTheme.colors.primary
         )
    }

From e7dfb3e83dc8a02f3e331ec39404d0b88965c4fc Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Mon, 18 Nov 2024 17:23:44 +0700
Subject: [PATCH 04/25] Add "android.permission.INTERNET" entry to manifest for
 Android client

---
 bisqapps/androidClient/src/androidMain/AndroidManifest.xml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/bisqapps/androidClient/src/androidMain/AndroidManifest.xml b/bisqapps/androidClient/src/androidMain/AndroidManifest.xml
index 22a5a17b..7696d689 100644
--- a/bisqapps/androidClient/src/androidMain/AndroidManifest.xml
+++ b/bisqapps/androidClient/src/androidMain/AndroidManifest.xml
@@ -1,6 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
 
+    <uses-permission android:name="android.permission.INTERNET" />
+
     <application
         android:name=".MainApplication"
         android:allowBackup="true"

From 26b2588d81049f7a9684a897946f81dd39eca2e0 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Mon, 18 Nov 2024 17:28:17 +0700
Subject: [PATCH 05/25] Fix parameter in ApiRequestService. We pass the host
 instead of baseUrl.

On iOS simulator we need to use localhost, not 10.0.2.2
---
 .../network/bisq/mobile/client/service/ApiRequestService.kt    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
index a644f2ed..d0037a4c 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
@@ -13,8 +13,9 @@ import io.ktor.http.contentType
 import io.ktor.serialization.kotlinx.json.json
 import kotlinx.serialization.json.Json
 
-class ApiRequestService(val baseUrl: String) {
+class ApiRequestService(host: String) {
     private val log = Logger.withTag("RequestService")
+    var baseUrl = "http://$host:8082/api/v1/"
 
     suspend fun get(path: String): String {
         val client = HttpClient(CIO) {

From 9714b3536a1933c6920e18dfd0f2b1c62bc7d4a7 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Mon, 18 Nov 2024 17:38:04 +0700
Subject: [PATCH 06/25] Add IosClientModule Use Dispatchers.Main instead of
 Dispatchers.IO as it seems iOS does not support that.

---
 .../di/DependenciesProviderHelper.kt          |  6 ++++--
 .../mobile/presentation/di/IosClientModule.kt | 19 +++++++++++++++++++
 2 files changed, 23 insertions(+), 2 deletions(-)
 create mode 100644 bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt

diff --git a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/DependenciesProviderHelper.kt b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/DependenciesProviderHelper.kt
index 6aff1a25..529b6d14 100644
--- a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/DependenciesProviderHelper.kt
+++ b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/DependenciesProviderHelper.kt
@@ -1,6 +1,8 @@
 package network.bisq.mobile.presentation.di
 
-import kotlinx.cinterop.*
+import kotlinx.cinterop.BetaInteropApi
+import kotlinx.cinterop.ObjCClass
+import kotlinx.cinterop.getOriginalKotlinClass
 import network.bisq.mobile.domain.di.domainModule
 import org.koin.core.Koin
 import org.koin.core.context.startKoin
@@ -11,7 +13,7 @@ class DependenciesProviderHelper {
 
     fun initKoin() {
         val instance = startKoin {
-            modules(listOf(domainModule, presentationModule))
+            modules(listOf(domainModule, presentationModule, iosClientModule))
         }
 
         koin = instance.koin
diff --git a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
new file mode 100644
index 00000000..4d11bce1
--- /dev/null
+++ b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
@@ -0,0 +1,19 @@
+package network.bisq.mobile.presentation.di
+
+import network.bisq.mobile.client.service.ApiRequestService
+import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileModel
+import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileServiceFacade
+import network.bisq.mobile.domain.client.main.user_profile.UserProfileApiGateway
+import network.bisq.mobile.domain.data.repository.UserProfileRepository
+import network.bisq.mobile.domain.user_profile.UserProfileModel
+import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
+import org.koin.dsl.module
+
+val iosClientModule = module {
+    single<UserProfileModel> { ClientUserProfileModel() }
+    single { ApiRequestService("localhost") }
+    single { UserProfileApiGateway(get()) }
+    single<UserProfileServiceFacade> { ClientUserProfileServiceFacade(get(), get()) }
+    single { UserProfileRepository(get(), get()) }
+
+}

From 901777622bb685d040537b86d9a27c18c6d5d3fe Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Mon, 18 Nov 2024 17:53:21 +0700
Subject: [PATCH 07/25] Add equals and hashCode

---
 .../mobile/domain/security/pow/ProofOfWork.kt | 31 ++++++++++++++++++-
 1 file changed, 30 insertions(+), 1 deletion(-)

diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/pow/ProofOfWork.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/pow/ProofOfWork.kt
index 5f18dfdf..f9b53db0 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/pow/ProofOfWork.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/pow/ProofOfWork.kt
@@ -11,4 +11,33 @@ data class ProofOfWork(
     val difficulty: Double,
     @Contextual val solution: ByteArray,
     val duration: Long
-)
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ProofOfWork
+
+        if (!payload.contentEquals(other.payload)) return false
+        if (counter != other.counter) return false
+        if (challenge != null) {
+            if (other.challenge == null) return false
+            if (!challenge.contentEquals(other.challenge)) return false
+        } else if (other.challenge != null) return false
+        if (difficulty != other.difficulty) return false
+        if (!solution.contentEquals(other.solution)) return false
+        if (duration != other.duration) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = payload.contentHashCode()
+        result = 31 * result + counter.hashCode()
+        result = 31 * result + (challenge?.contentHashCode() ?: 0)
+        result = 31 * result + difficulty.hashCode()
+        result = 31 * result + solution.contentHashCode()
+        result = 31 * result + duration.hashCode()
+        return result
+    }
+}

From c2d76c45f84011679fc3815320e2d104c463dd42 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Mon, 18 Nov 2024 17:55:05 +0700
Subject: [PATCH 08/25] Remove redundant config for UserProfileRepository

---
 .../kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt | 2 --
 .../network/bisq/mobile/presentation/di/IosClientModule.kt      | 2 --
 2 files changed, 4 deletions(-)

diff --git a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
index 93bdcddb..2125ba65 100644
--- a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
+++ b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
@@ -4,7 +4,6 @@ import network.bisq.mobile.client.service.ApiRequestService
 import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileModel
 import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileServiceFacade
 import network.bisq.mobile.domain.client.main.user_profile.UserProfileApiGateway
-import network.bisq.mobile.domain.data.repository.UserProfileRepository
 import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import org.koin.dsl.module
@@ -14,6 +13,5 @@ val androidClientModule = module {
     single { ApiRequestService("10.0.2.2") }
     single { UserProfileApiGateway(get()) }
     single<UserProfileServiceFacade> { ClientUserProfileServiceFacade(get(), get()) }
-    single { UserProfileRepository(get(), get()) }
 
 }
diff --git a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
index 4d11bce1..cfea4e89 100644
--- a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
+++ b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
@@ -4,7 +4,6 @@ import network.bisq.mobile.client.service.ApiRequestService
 import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileModel
 import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileServiceFacade
 import network.bisq.mobile.domain.client.main.user_profile.UserProfileApiGateway
-import network.bisq.mobile.domain.data.repository.UserProfileRepository
 import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import org.koin.dsl.module
@@ -14,6 +13,5 @@ val iosClientModule = module {
     single { ApiRequestService("localhost") }
     single { UserProfileApiGateway(get()) }
     single<UserProfileServiceFacade> { ClientUserProfileServiceFacade(get(), get()) }
-    single { UserProfileRepository(get(), get()) }
 
 }

From 649774bd4198ca11b8465f034b22507bace6c0d3 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Mon, 18 Nov 2024 17:55:22 +0700
Subject: [PATCH 09/25] Remove unused model

---
 .../node/domain/model/UserProfileModel.kt     | 32 -------------------
 1 file changed, 32 deletions(-)
 delete mode 100644 bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/model/UserProfileModel.kt

diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/model/UserProfileModel.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/model/UserProfileModel.kt
deleted file mode 100644
index 60e308ab..00000000
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/model/UserProfileModel.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package network.bisq.mobile.android.node.domain.model
-
-import bisq.common.observable.Observable
-import bisq.security.pow.ProofOfWork
-import lombok.Getter
-import lombok.Setter
-import java.security.KeyPair
-
-/**
- * TODO do we need to make this shared? If so it involves getting rid of the bisq.* deps, lombok and include java.security in shared..
- * Most probably we don't and this can be just part of androidNode (we'll know when the bisq-apis gets defined)
- */
-@Getter
-class UserProfileModel {
-    @Setter
-    var keyPair: KeyPair? = null
-
-    @Setter
-    lateinit var pubKeyHash: ByteArray
-
-    @Setter
-    var proofOfWork: ProofOfWork? = null
-
-    private val userName = Observable<String>()
-    val terms = Observable("")
-    val statement = Observable("")
-    val nym = Observable<String>()
-    private val nickName = Observable<String>()
-    private val profileId = Observable<String>()
-
-    val isBusy = Observable<Boolean>()
-}

From 6bae94a02acf4672f0f5f9ddd4d3aa2e47ed6416 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Mon, 18 Nov 2024 20:26:43 +0700
Subject: [PATCH 10/25] Add pruneAllBackups and readAllPersisted calls in
 AndroidApplicationService

---
 .../mobile/android/node/AndroidApplicationService.kt     | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt
index 8010be8e..0f733020 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt
@@ -107,6 +107,7 @@ class AndroidApplicationService(context: Context, userDataDir: Path?) :
         var dontShowAgainServiceSupplier: androidx.core.util.Supplier<DontShowAgainService> =
             Supplier { applicationService.dontShowAgainService }
     }
+
     companion object {
         const val STARTUP_TIMEOUT_SEC: Long = 300
         const val SHUTDOWN_TIMEOUT_SEC: Long = 10
@@ -215,6 +216,14 @@ class AndroidApplicationService(context: Context, userDataDir: Path?) :
     }
 
     override fun initialize(): CompletableFuture<Boolean> {
+        var ts = System.currentTimeMillis()
+        pruneAllBackups().join()
+        log.info("pruneAllBackups took {} ms", System.currentTimeMillis() - ts)
+
+        ts = System.currentTimeMillis()
+        readAllPersisted().join()
+        log.info("readAllPersisted took {} ms", System.currentTimeMillis() - ts)
+
         return securityService.initialize()
             .thenCompose<Boolean> { result: Boolean? ->
                 setState(State.INITIALIZE_NETWORK)

From a0b2affcb9acf12406040e2e8411b9cd2ef54d89 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Mon, 18 Nov 2024 23:52:28 +0700
Subject: [PATCH 11/25] Add applySelectedUserProfile, getSelectedUserProfile
 and getUserIdentityIds methods.

In the CreateProfilePresenter at onLaunched there are commented out some different dev test modes;
- Use hasUserProfile to check if create user profile screen should be shown
- onGenerateKeyPair if no profile is present
- applySelectedUserProfile if one is present and we apply the data to the screen. This is only for dev testing. We have to display the selected user profile in the app in a TBD way.

Tested with all 3 apps and working.
Requires latest Bisq 2 main version and a running rest api.
---
 bisqapps/androidClient/build.gradle.kts       |  5 ++
 bisqapps/androidNode/build.gradle.kts         |  4 ++
 .../NodeUserProfileServiceFacade.kt           | 53 +++++++++++++------
 .../client/service/ApiRequestService.kt       |  2 +-
 .../ClientUserProfileServiceFacade.kt         | 42 +++++++++++----
 .../user_profile/UserProfileApiGateway.kt     | 16 ++++++
 .../mobile/domain/common/network/Address.kt   |  9 ++++
 .../domain/common/network/TransportType.kt    | 10 ++++
 .../mobile/domain/data/model/UserProfile.kt   | 13 -----
 .../domain/network/identity/NetworkId.kt      | 12 +++++
 .../mobile/domain/security/keys/PubKey.kt     |  9 ++++
 .../mobile/domain/user/profile/UserProfile.kt | 21 ++++++++
 .../domain/user_profile/UserProfileModel.kt   |  9 ++++
 .../user_profile/UserProfileServiceFacade.kt  | 10 ++--
 .../ui/uicases/startup/SplashPresenter.kt     |  4 +-
 15 files changed, 171 insertions(+), 48 deletions(-)
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/Address.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/TransportType.kt
 delete mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/UserProfile.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/network/identity/NetworkId.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/PubKey.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/profile/UserProfile.kt

diff --git a/bisqapps/androidClient/build.gradle.kts b/bisqapps/androidClient/build.gradle.kts
index c2cf66cb..42e7a1ff 100644
--- a/bisqapps/androidClient/build.gradle.kts
+++ b/bisqapps/androidClient/build.gradle.kts
@@ -63,6 +63,9 @@ android {
     packaging {
         resources {
             excludes += "/META-INF/{AL2.0,LGPL2.1}"
+            excludes.add("META-INF/LICENSE.md")
+            excludes.add("META-INF/NOTICE.md")
+            excludes.add("META-INF/NOTICE.markdown")
         }
     }
     buildTypes {
@@ -82,6 +85,8 @@ android {
 dependencies {
     implementation(project(":shared:presentation"))
     implementation(project(":shared:domain"))
+    // FIXME hack to avoid the issue that org.slf4j is not found as we exclude it in shared
+    implementation("io.ktor:ktor-client-cio:3.0.1")
     debugImplementation(compose.uiTooling)
 }
 
diff --git a/bisqapps/androidNode/build.gradle.kts b/bisqapps/androidNode/build.gradle.kts
index dcf9c7fb..a8e84bfb 100644
--- a/bisqapps/androidNode/build.gradle.kts
+++ b/bisqapps/androidNode/build.gradle.kts
@@ -82,6 +82,10 @@ android {
             // Exclude the conflicting META-INF files
             excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF")
             excludes.add("META-INF/DEPENDENCIES")
+            excludes.add("META-INF/LICENSE.md")
+            excludes.add("META-INF/NOTICE.md")
+            excludes.add("META-INF/INDEX.LIST")
+            excludes.add("META-INF/NOTICE.markdown")
             pickFirsts.add("**/protobuf/**/*.class")
         }
     }
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt
index 4a9f3c0d..ebb666ad 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt
@@ -6,6 +6,7 @@ import bisq.security.SecurityService
 import bisq.user.UserService
 import bisq.user.identity.NymIdGenerator
 import bisq.user.identity.UserIdentity
+import bisq.user.profile.UserProfile
 import co.touchlab.kermit.Logger
 import network.bisq.mobile.android.node.AndroidApplicationService
 import network.bisq.mobile.domain.user_profile.UserProfileModel
@@ -40,8 +41,8 @@ class NodeUserProfileServiceFacade(
         get() = applicationServiceSupplier.userServiceSupplier.get()
 
 
-    override fun hasUserProfile(): Boolean {
-        return userService.userIdentityService.userIdentities.isEmpty()
+    override suspend fun hasUserProfile(): Boolean {
+        return userService.userIdentityService.userIdentities.isNotEmpty()
     }
 
     override suspend fun generateKeyPair() {
@@ -89,24 +90,42 @@ class NodeUserProfileServiceFacade(
             }
     }
 
-    override fun findUserProfile(id: String): UserProfileModel? {
-        return getUserProfiles().find { model -> model.id.equals(id) }
+    override suspend fun getUserIdentityIds(): List<String> {
+        return userService.userIdentityService.userIdentities
+            .map { userIdentity -> userIdentity.id }
+    }
+
+    override suspend fun applySelectedUserProfile() {
+        val userProfile = getSelectedUserProfile()
+        if (userProfile != null) {
+            model.setNickName(userProfile.nickName)
+            model.setNym(userProfile.nym)
+            model.setId(userProfile.id)
+        }
+    }
+
+    private fun getSelectedUserProfile(): UserProfile? {
+        val userIdentity = userService.userIdentityService.selectedUserIdentity ?: return null
+        return userIdentity.userProfile
+
     }
 
-    override fun getUserProfiles(): Sequence<UserProfileModel> {
+    private fun findUserProfile(id: String): UserProfileModel? {
         return userService.userIdentityService.userIdentities
-            .asSequence()
-            .map { userIdentity ->
-                val userProfile = userIdentity.userProfile
-                val model = NodeUserProfileModel()
-                model.setNickName(userProfile.nickName)
-                model.setNym(userProfile.nym)
-                model.setId(userProfile.id)
-                model.keyPair = userIdentity.identity.keyBundle.keyPair
-                model.pubKeyHash = userIdentity.userProfile.pubKeyHash
-                model.proofOfWork = userIdentity.userProfile.proofOfWork
-                model
-            }
+            .map { userIdentity -> getNodeUserProfileModel(userIdentity) }
+            .find { model -> model.id.equals(id) }
+    }
+
+    private fun getNodeUserProfileModel(userIdentity: UserIdentity): UserProfileModel {
+        val userProfile = userIdentity.userProfile
+        val model = NodeUserProfileModel()
+        model.setNickName(userProfile.nickName)
+        model.setNym(userProfile.nym)
+        model.setId(userProfile.id)
+        model.keyPair = userIdentity.identity.keyBundle.keyPair
+        model.pubKeyHash = userIdentity.userProfile.pubKeyHash
+        model.proofOfWork = userIdentity.userProfile.proofOfWork
+        return model
     }
 
     private fun createSimulatedDelay(powDuration: Long) {
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
index d0037a4c..1fb482fc 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
@@ -15,7 +15,7 @@ import kotlinx.serialization.json.Json
 
 class ApiRequestService(host: String) {
     private val log = Logger.withTag("RequestService")
-    var baseUrl = "http://$host:8082/api/v1/"
+    private var baseUrl = "http://$host:8082/api/v1/"
 
     suspend fun get(path: String): String {
         val client = HttpClient(CIO) {
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
index ea14967c..035e22f4 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
@@ -7,6 +7,7 @@ import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.datetime.Clock
 import network.bisq.mobile.client.user_profile.UserProfileResponse
+import network.bisq.mobile.domain.user.profile.UserProfile
 import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import kotlin.math.max
@@ -23,8 +24,8 @@ class ClientUserProfileServiceFacade(
     // TODO Dispatchers.IO is not supported on iOS. Either customize or find whats on iOS appropriate.
     private val coroutineScope = CoroutineScope(Dispatchers.Main)
 
-    override fun hasUserProfile(): Boolean {
-        TODO("Not yet implemented")
+    override suspend fun hasUserProfile(): Boolean {
+        return getUserIdentityIds().isNotEmpty()
     }
 
     override suspend fun generateKeyPair() {
@@ -33,9 +34,9 @@ class ClientUserProfileServiceFacade(
             try {
                 model.setGenerateKeyPairInProgress(true)
                 val ts = Clock.System.now().toEpochMilliseconds()
-                val result = apiGateway.requestPreparedData()
-                model.preparedDataAsJson = result.first
-                val preparedData = result.second
+                val response = apiGateway.requestPreparedData()
+                model.preparedDataAsJson = response.first
+                val preparedData = response.second
 
                 createSimulatedDelay(Clock.System.now().toEpochMilliseconds() - ts)
 
@@ -56,12 +57,12 @@ class ClientUserProfileServiceFacade(
         coroutineScope.launch {
             try {
                 model.setCreateAndPublishInProgress(true)
-                val userProfileResponse: UserProfileResponse =
+                val response: UserProfileResponse =
                     apiGateway.createAndPublishNewUserProfile(
                         model.nickName.value,
                         model.preparedDataAsJson
                     )
-                require(model.id.value == userProfileResponse.userProfileId)
+                require(model.id.value == response.userProfileId)
                 { "userProfileId from model does not match userProfileId from response" }
             } catch (e: Exception) {
                 log.e { e.toString() }
@@ -71,11 +72,32 @@ class ClientUserProfileServiceFacade(
         }
     }
 
-    override fun getUserProfiles(): Sequence<UserProfileModel> {
-        TODO("Not yet implemented")
+
+    override suspend fun getUserIdentityIds(): List<String> {
+        return try {
+            apiGateway.getUserIdentityIds()
+        } catch (e: Exception) {
+            log.e { e.toString() }
+            emptyList()
+        }
+    }
+
+    override suspend fun applySelectedUserProfile() {
+        try {
+            val userProfile = getSelectedUserProfile()
+            model.setNickName(userProfile.nickName)
+            model.setNym(userProfile.nym)
+            model.setId(userProfile.id)
+        } catch (e: Exception) {
+            log.e { e.toString() }
+        }
+    }
+
+    private suspend fun getSelectedUserProfile(): UserProfile {
+        return apiGateway.getSelectedUserProfile()
     }
 
-    override fun findUserProfile(id: String): UserProfileModel? {
+    private suspend fun findUserProfile(id: String): UserProfileModel? {
         TODO("Not yet implemented")
     }
 
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
index ca4b498f..1261abf4 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
@@ -7,6 +7,7 @@ import kotlinx.serialization.modules.contextual
 import network.bisq.mobile.client.service.ApiRequestService
 import network.bisq.mobile.client.user_profile.UserProfileResponse
 import network.bisq.mobile.domain.user.identity.PreparedData
+import network.bisq.mobile.domain.user.profile.UserProfile
 import network.bisq.mobile.utils.ByteArrayAsBase64Serializer
 
 class UserProfileApiGateway(
@@ -38,4 +39,19 @@ class UserProfileApiGateway(
             apiRequestService.post("user-identity/user-identities", createUserIdentityRequest)
         return Json.decodeFromString(response)
     }
+
+    suspend fun getUserIdentityIds(): List<String> {
+        val response = apiRequestService.get("user-identity/ids")
+        return Json.decodeFromString(response)
+    }
+
+    suspend fun getSelectedUserProfile(): UserProfile {
+        val response = apiRequestService.get("user-identity/selected/user-profile")
+        val json = Json {
+            serializersModule = SerializersModule {
+                contextual(ByteArrayAsBase64Serializer)
+            }
+        }
+        return  json.decodeFromString<UserProfile>(response)
+    }
 }
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/Address.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/Address.kt
new file mode 100644
index 00000000..8dbaba52
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/Address.kt
@@ -0,0 +1,9 @@
+package network.bisq.mobile.domain.common.network
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Address(
+    val host: String,
+    val port: Int,
+)
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/TransportType.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/TransportType.kt
new file mode 100644
index 00000000..a15d5f09
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/TransportType.kt
@@ -0,0 +1,10 @@
+package network.bisq.mobile.domain.common.network
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+enum class TransportType {
+    TOR,
+    I2P,
+    CLEAR;
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/UserProfile.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/UserProfile.kt
deleted file mode 100644
index b945806d..00000000
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/UserProfile.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package network.bisq.mobile.domain.data.model
-
-class UserProfile: BaseModel() {
-    var name = ""
-}
-
-interface UserProfileFactory {
-    fun createUserProfile(): UserProfile
-}
-
-class DefaultUserProfileFactory : UserProfileFactory {
-    override fun createUserProfile() = UserProfile()
-}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/network/identity/NetworkId.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/network/identity/NetworkId.kt
new file mode 100644
index 00000000..a40272af
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/network/identity/NetworkId.kt
@@ -0,0 +1,12 @@
+package network.bisq.mobile.domain.network.identity
+
+import kotlinx.serialization.Serializable
+import network.bisq.mobile.domain.common.network.Address
+import network.bisq.mobile.domain.common.network.TransportType
+import network.bisq.mobile.domain.security.keys.PubKey
+
+@Serializable
+data class NetworkId(
+    val addressByTransportTypeMap: Map<TransportType, Address>,
+    val pubKey: PubKey,
+)
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/PubKey.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/PubKey.kt
new file mode 100644
index 00000000..43ee198f
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/PubKey.kt
@@ -0,0 +1,9 @@
+package network.bisq.mobile.domain.security.keys
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PubKey(
+    val publicKey: String,
+    val keyId: String
+)
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/profile/UserProfile.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/profile/UserProfile.kt
new file mode 100644
index 00000000..67322f47
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/profile/UserProfile.kt
@@ -0,0 +1,21 @@
+package network.bisq.mobile.domain.user.profile
+
+import kotlinx.serialization.Serializable
+import network.bisq.mobile.domain.network.identity.NetworkId
+import network.bisq.mobile.domain.security.pow.ProofOfWork
+
+@Serializable
+data class UserProfile(
+    val nickName: String,
+    val proofOfWork: ProofOfWork,
+    val networkId: NetworkId,
+    val terms: String,
+    val statement: String,
+    val avatarVersion: Int,
+    val applicationVersion: String,
+    val id: String,
+    val nym: String,
+    val userName: String,
+    val pubKeyHash: String,
+    val publishDate: Long
+)
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt
index 780b3901..ea824bbe 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt
@@ -35,4 +35,13 @@ open class UserProfileModel {
     }
 
     lateinit var pubKeyHash: ByteArray
+
+    override fun toString(): String {
+        return "UserProfileModel(_nickName=${_nickName.value}, " +
+                "_id=${_id.value}, " +
+                "_nym=${_nym.value}, " +
+                "_generateKeyPairInProgress=${_generateKeyPairInProgress.value}, " +
+                "_createAndPublishInProgress=${_createAndPublishInProgress.value}, " +
+                "pubKeyHash=${pubKeyHash.contentToString()})"
+    }
 }
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileServiceFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileServiceFacade.kt
index 36556eed..35b2232e 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileServiceFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileServiceFacade.kt
@@ -11,7 +11,7 @@ interface UserProfileServiceFacade {
      * This should be used to detect a first time user who has no identity created yet and where
      * we display the create user profile screen.
      */
-    fun hasUserProfile(): Boolean
+    suspend fun hasUserProfile(): Boolean
 
     /**
      * Generates a key pair and derived data as well as proof of work which is used to strengthen
@@ -37,12 +37,12 @@ interface UserProfileServiceFacade {
     suspend fun createAndPublishNewUserProfile()
 
     /**
-     * Find a UserProfileModel based on the give user profile ID
+     * Create UserProfileModels from the userIdentities.
      */
-    fun findUserProfile(id: String): UserProfileModel?
+    suspend fun getUserIdentityIds(): List<String>
 
     /**
-     * Create UserProfileModels from the userIdentities.
+     * Applies the selected user identity to the user profile model
      */
-    fun getUserProfiles(): Sequence<UserProfileModel>
+    suspend fun applySelectedUserProfile()
 }
\ No newline at end of file
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt
index ee32925a..64bd7464 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt
@@ -3,11 +3,11 @@ package network.bisq.mobile.presentation.ui.uicases.startup
 import androidx.navigation.NavController
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import network.bisq.mobile.presentation.BasePresenter
-import network.bisq.mobile.presentation.ui.navigation.Routes
-import kotlinx.coroutines.delay
 import network.bisq.mobile.presentation.MainPresenter
+import network.bisq.mobile.presentation.ui.navigation.Routes
 
 open class SplashPresenter(
     mainPresenter: MainPresenter,

From da4d48b50375c6d2e900ae7a6bd7000239687e6d Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Tue, 19 Nov 2024 10:36:21 +0700
Subject: [PATCH 12/25] Move gradle dependencies to toml file

---
 bisqapps/androidClient/build.gradle.kts |  2 +-
 bisqapps/gradle/libs.versions.toml      | 15 +++++++++++
 bisqapps/shared/domain/build.gradle.kts | 36 +++++++++----------------
 3 files changed, 28 insertions(+), 25 deletions(-)

diff --git a/bisqapps/androidClient/build.gradle.kts b/bisqapps/androidClient/build.gradle.kts
index 42e7a1ff..f5701dc8 100644
--- a/bisqapps/androidClient/build.gradle.kts
+++ b/bisqapps/androidClient/build.gradle.kts
@@ -86,7 +86,7 @@ dependencies {
     implementation(project(":shared:presentation"))
     implementation(project(":shared:domain"))
     // FIXME hack to avoid the issue that org.slf4j is not found as we exclude it in shared
-    implementation("io.ktor:ktor-client-cio:3.0.1")
+    implementation(libs.ktor.client.cio)
     debugImplementation(compose.uiTooling)
 }
 
diff --git a/bisqapps/gradle/libs.versions.toml b/bisqapps/gradle/libs.versions.toml
index 868b6e77..0533898c 100644
--- a/bisqapps/gradle/libs.versions.toml
+++ b/bisqapps/gradle/libs.versions.toml
@@ -19,6 +19,9 @@ androidx-multidex = "2.0.1"
 bisq-core = "2.1.2"
 compose-plugin = "1.7.0"
 junit = "4.13.2"
+kotlinxDatetime = "0.4.0"
+kotlinxSerializationCore = "1.7.3"
+ktorClientCio = "3.0.1"
 mockio = "1.12.0"
 kotlin = "2.0.20"
 kotlinTestJunit = "2.0.20"
@@ -26,6 +29,8 @@ kotlinx = "1.9.0"
 kermit = "2.0.4"
 buildconfig = "5.5.0"
 navigationCompose = "2.7.0-alpha07"
+okio = "3.9.1"
+orgJetbrainsKotlinPluginSerializationGradlePlugin = "2.0.21"
 protobuf = "0.9.4"
 protob = "4.28.2"
 ksp = "2.0.20-1.0.25"
@@ -62,6 +67,14 @@ typesafe-config-lib = { strictly = '1.4.3' }
 kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
 kotlin-test-junit-v180 = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlinTestJunit" }
 kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" }
+kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
+kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" }
+ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktorClientCio" }
+ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktorClientCio" }
+ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClientCio" }
+ktor-client-json = { module = "io.ktor:ktor-client-json", version.ref = "ktorClientCio" }
+ktor-client-serialization = { module = "io.ktor:ktor-client-serialization", version.ref = "ktorClientCio" }
+ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktorClientCio" }
 mock-io = { module = "io.mockk:mockk", version.ref = "mockio" }
 junit = { group = "junit", name = "junit", version.ref = "junit" }
 androidx-multidex = { group = "androidx.multidex", name = "multidex", version.ref = "androidx-multidex" }
@@ -84,6 +97,8 @@ logging-kermit = { group = "co.touchlab", name = "kermit", version.ref = "kermit
 
 lombok = { module = 'org.projectlombok:lombok', version.ref = 'lombok-lib' }
 
+okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
+jetbrains-serialization-gradle-plugin = { module = "org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin", version.ref = "orgJetbrainsKotlinPluginSerializationGradlePlugin" }
 typesafe-config = { module = 'com.typesafe:config', version.ref = 'typesafe-config-lib' }
 
 bouncycastle = { module = 'org.bouncycastle:bcprov-jdk18on', version.ref = 'bouncycastle-lib' }
diff --git a/bisqapps/shared/domain/build.gradle.kts b/bisqapps/shared/domain/build.gradle.kts
index 98232555..6d944366 100644
--- a/bisqapps/shared/domain/build.gradle.kts
+++ b/bisqapps/shared/domain/build.gradle.kts
@@ -37,33 +37,21 @@ kotlin {
             implementation(libs.koin.core)
             implementation(libs.kotlinx.coroutines)
             implementation(libs.logging.kermit)
+            implementation(libs.okio)
+            implementation(libs.kotlinx.datetime)
 
-            implementation("io.ktor:ktor-client-core:3.0.1") {
-                exclude(group = "org.slf4j", module = "slf4j-api")
-            }
-            implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3") {
-                exclude(group = "org.slf4j", module = "slf4j-api")
-            }
-            implementation("io.ktor:ktor-client-serialization:3.0.1") {
-                exclude(group = "org.slf4j", module = "slf4j-api")
-            }
-            implementation("io.ktor:ktor-client-json:3.0.1") {
-                exclude(group = "org.slf4j", module = "slf4j-api")
-            }
-            implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.1") {
-                exclude(group = "org.slf4j", module = "slf4j-api")
-            }
-            implementation("io.ktor:ktor-client-cio:3.0.1") {
-                exclude(group = "org.slf4j", module = "slf4j-api")
-            }
-            implementation("io.ktor:ktor-client-content-negotiation:3.0.1") {
-                exclude(group = "org.slf4j", module = "slf4j-api")
-            }
-            implementation("org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin:2.0.21") {
+            implementation(libs.ktor.client.core)
+            implementation(libs.kotlinx.serialization.core)
+            implementation(libs.ktor.client.serialization)
+            implementation(libs.ktor.client.json)
+            implementation(libs.ktor.serialization.kotlinx.json)
+            implementation(libs.ktor.client.cio)
+            implementation(libs.ktor.client.content.negotiation)
+            implementation(libs.jetbrains.serialization.gradle.plugin)
+
+            configurations.all {
                 exclude(group = "org.slf4j", module = "slf4j-api")
             }
-            implementation("com.squareup.okio:okio:3.9.1")
-            implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
         }
         commonTest.dependencies {
             implementation(libs.kotlin.test)

From 6a509443e9b98f6b4f1e3833042282b1969fc4dd Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Tue, 19 Nov 2024 10:49:01 +0700
Subject: [PATCH 13/25] Fix merge issue

---
 .../ui/uicases/startup/CreateProfileScreen.kt | 26 +++++++++----------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt
index f7b19e05..3557aae4 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt
@@ -1,16 +1,17 @@
 package network.bisq.mobile.presentation.ui.uicases.startup
 
 import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.*
-import network.bisq.mobile.presentation.ui.components.atoms.BisqText
-import network.bisq.mobile.presentation.ui.components.layout.BisqScrollLayout
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
-import androidx.navigation.NavController
 import androidx.navigation.NavHostController
 import bisqapps.shared.presentation.generated.resources.Res
 import bisqapps.shared.presentation.generated.resources.img_bot_image
@@ -19,15 +20,14 @@ import kotlinx.coroutines.flow.StateFlow
 import network.bisq.mobile.components.MaterialTextField
 import network.bisq.mobile.presentation.ViewPresenter
 import network.bisq.mobile.presentation.ui.components.atoms.BisqButton
+import network.bisq.mobile.presentation.ui.components.atoms.BisqText
 import network.bisq.mobile.presentation.ui.components.atoms.icons.BisqLogo
+import network.bisq.mobile.presentation.ui.components.layout.BisqScrollLayout
 import network.bisq.mobile.presentation.ui.theme.BisqTheme
-import org.jetbrains.compose.resources.ExperimentalResourceApi
 import org.jetbrains.compose.resources.painterResource
 import org.koin.compose.koinInject
-import org.koin.core.qualifier.named
-import kotlinx.coroutines.flow.StateFlow
-import cafe.adriel.lyricist.LocalStrings
 import org.koin.core.parameter.parametersOf
+import org.koin.core.qualifier.named
 
 interface ICreateProfilePresenter: ViewPresenter {
     val profileName: StateFlow<String>
@@ -84,12 +84,12 @@ fun CreateProfileScreen(
         Image(painterResource(Res.drawable.img_bot_image), "Crypto geHostnerated image (PoW)") // TODO: Translation
         Spacer(modifier = Modifier.height(32.dp))
         BisqText.baseRegular(
-            text = "Sleepily-Distracted-Zyophyte-257",
+            text = presenter.nym.collectAsState().value,
             color = BisqTheme.colors.light1,
         )
         Spacer(modifier = Modifier.height(12.dp))
         BisqText.baseRegular(
-            text = strings.onboarding_createProfile_nym,
+            text = presenter.id.collectAsState().value,
             color = BisqTheme.colors.grey2,
         )
         Spacer(modifier = Modifier.height(38.dp))
@@ -97,13 +97,13 @@ fun CreateProfileScreen(
             text = strings.onboarding_createProfile_regenerate,
             backgroundColor = BisqTheme.colors.dark5,
             padding = PaddingValues(horizontal = 64.dp, vertical = 12.dp),
-            onClick = {}
+            onClick = {presenter.onGenerateKeyPair() }
         )
         Spacer(modifier = Modifier.height(40.dp))
         BisqButton(
             strings.buttons_next,
-            onClick = { presenter.navigateToNextScreen() },
+            onClick = { presenter.onCreateAndPublishNewUserProfile() },
             backgroundColor = if (profileName.isEmpty()) BisqTheme.colors.primaryDisabled else BisqTheme.colors.primary
         )
-   }
+    }
 }
\ No newline at end of file

From 9b5b041351347d75738843d94efba63c8c02e827 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Tue, 19 Nov 2024 11:07:41 +0700
Subject: [PATCH 14/25] Fix merge issue Go to next screen on
 onCreateAndPublishNewUserProfile completed.

---
 .../uicases/startup/CreateProfilePresenter.kt | 97 +++++++++----------
 .../ui/uicases/startup/CreateProfileScreen.kt | 23 +----
 2 files changed, 53 insertions(+), 67 deletions(-)

diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt
index 8487d023..b1560f9e 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt
@@ -1,84 +1,83 @@
 package network.bisq.mobile.presentation.ui.uicases.startup
 
 import androidx.navigation.NavController
+import co.touchlab.kermit.Logger
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.IO
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.flow.collectLatest
 import network.bisq.mobile.domain.data.BackgroundDispatcher
 import network.bisq.mobile.domain.data.repository.UserProfileRepository
-import network.bisq.mobile.domain.data.model.UserProfile
 import network.bisq.mobile.presentation.BasePresenter
-import network.bisq.mobile.presentation.ui.navigation.Routes
 import network.bisq.mobile.presentation.MainPresenter
+import network.bisq.mobile.presentation.ui.navigation.Routes
 
 open class CreateProfilePresenter(
     mainPresenter: MainPresenter,
     private val navController: NavController,
     private val userProfileRepository: UserProfileRepository
-) : BasePresenter(mainPresenter), ICreateProfilePresenter {
+) : BasePresenter(mainPresenter) {
 
-    private val _profileName = MutableStateFlow("")
-    override val profileName: StateFlow<String> = _profileName
-    override val nym: StateFlow<String> = MutableStateFlow("")
-    override val id: StateFlow<String> = MutableStateFlow("")
+    private val log = Logger.withTag("CreateProfilePresenter")
+    private val userProfileModel = userProfileRepository.model
 
-    // TODO: Not working
-    init {
-        CoroutineScope(BackgroundDispatcher).launch {
-            userProfileRepository.data.collectLatest { userProfile ->
-                userProfile?.let {
-                    _profileName.value = it.name
-                }
-            }
-        }
-    }
+    val nickName: StateFlow<String> = userProfileModel.nickName
+    val nym: StateFlow<String> = userProfileModel.nym
+    val id: StateFlow<String> = userProfileModel.id
 
-    override fun onProfileNameChanged(newName: String) {
-        _profileName.value = newName
-    }
 
-    override fun navigateToNextScreen() {
-        if (_profileName.value.isNotEmpty()) {
-            saveUserProfile()
-            navController.navigate(Routes.TrustedNodeSetup.name) {
-                popUpTo(Routes.CreateProfile.name) { inclusive = true }
-            }
-        }
-    }
-
-    override fun onGenerateKeyPair() {
+    override fun onViewAttached() {
+        onGenerateKeyPair()
 
+        // Currently we just always show the create profile page.
+        // We need to make the UI behaving to the intended use case.
+        // 1. After loading screen -> check if there is an existing user profile by
+        // calling `userProfileRepository.service.hasUserProfile()`
+        // 1a. If there is an existing user profile, do not show create user profile screen,
+        // but show user profile is some not yet defined way (right top corner in Desktop shows user profile).
+        // `userProfileRepository.service.applySelectedUserProfile()` fills the user profile data to
+        // userProfileRepository.model to be used in the UI.
+        // 1b. If there is no existing user profile, show create profile screen and call
+        // `onGenerateKeyPair()` when view is ready.
     }
 
-    override fun onCreateAndPublishNewUserProfile() {
-        this.navigateToNextScreen()
+    fun onNickNameChanged(value: String) {
+        userProfileRepository.model.setNickName(value)
     }
 
-    fun saveUserProfile() {
+    fun onGenerateKeyPair() {
+        // takes 200 -1000 ms
+        // todo start busy animation in UI
         CoroutineScope(BackgroundDispatcher).launch {
-            val updatedProfile = UserProfile().apply {
-                name = _profileName.value
+            userProfileRepository.model.generateKeyPairInProgress.collect { inProgress ->
+                if (!inProgress) {
+                    // todo stop busy animation in UI
+                }
             }
-            userProfileRepository.update(updatedProfile)
         }
-    }
 
-    /*
-    fun onNicknameChanged(newNickname: String) {
-        _nickname.value = newNickname
+        CoroutineScope(BackgroundDispatcher).launch {
+            userProfileRepository.service.generateKeyPair()
+        }
     }
 
-    fun navigateToNextScreen() {
-        if (_nickname.value.isNotEmpty()) {
-            navController.navigate(Routes.TrustedNodeSetup.name) {
-                popUpTo(Routes.CreateProfile.name) { inclusive = true }
+    fun onCreateAndPublishNewUserProfile() {
+        // todo start busy animation in UI
+        // We cannot use BackgroundDispatcher here as we get error:
+        // `Method setCurrentState must be called on the main thread`
+        CoroutineScope(Dispatchers.Main).launch {
+            userProfileRepository.model.createAndPublishInProgress.collect { inProgress ->
+                if (!inProgress) {
+                    // todo stop busy animation in UI
+                    navController.navigate(Routes.TrustedNodeSetup.name) {
+                        popUpTo(Routes.CreateProfile.name) { inclusive = true }
+                    }
+                }
             }
         }
-    }
-    */
 
+        CoroutineScope(BackgroundDispatcher).launch {
+            userProfileRepository.service.createAndPublishNewUserProfile()
+        }
+    }
 }
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt
index 3557aae4..15d7c168 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt
@@ -16,9 +16,7 @@ import androidx.navigation.NavHostController
 import bisqapps.shared.presentation.generated.resources.Res
 import bisqapps.shared.presentation.generated.resources.img_bot_image
 import cafe.adriel.lyricist.LocalStrings
-import kotlinx.coroutines.flow.StateFlow
 import network.bisq.mobile.components.MaterialTextField
-import network.bisq.mobile.presentation.ViewPresenter
 import network.bisq.mobile.presentation.ui.components.atoms.BisqButton
 import network.bisq.mobile.presentation.ui.components.atoms.BisqText
 import network.bisq.mobile.presentation.ui.components.atoms.icons.BisqLogo
@@ -29,26 +27,15 @@ import org.koin.compose.koinInject
 import org.koin.core.parameter.parametersOf
 import org.koin.core.qualifier.named
 
-interface ICreateProfilePresenter: ViewPresenter {
-    val profileName: StateFlow<String>
-    val nym: StateFlow<String>
-    val id: StateFlow<String>
-
-    fun onProfileNameChanged(newName: String)
-    fun navigateToNextScreen()
-    fun onGenerateKeyPair()
-    fun onCreateAndPublishNewUserProfile()
-}
-
 
 @Composable
 fun CreateProfileScreen(
 ) {
     val strings = LocalStrings.current
     val navController: NavHostController = koinInject(named("RootNavController"))
-    val presenter: ICreateProfilePresenter = koinInject { parametersOf(navController) }
+    val presenter: CreateProfilePresenter = koinInject { parametersOf(navController) }
 
-    val profileName = presenter.profileName.collectAsState().value
+    val profileName = presenter.nickName.collectAsState().value
 
     LaunchedEffect(Unit) {
         presenter.onViewAttached()
@@ -76,12 +63,12 @@ fun CreateProfileScreen(
                 color = BisqTheme.colors.light2,
             )
             MaterialTextField(
-                text = presenter.profileName.collectAsState().value,
+                text = presenter.nickName.collectAsState().value,
                 placeholder = strings.onboarding_createProfile_nickName_prompt,
-                onValueChanged = { presenter.onProfileNameChanged(it) })
+                onValueChanged = { presenter.onNickNameChanged(it) })
         }
         Spacer(modifier = Modifier.height(36.dp))
-        Image(painterResource(Res.drawable.img_bot_image), "Crypto geHostnerated image (PoW)") // TODO: Translation
+        Image(painterResource(Res.drawable.img_bot_image), "User profile icon generated from the hash of the public key") // TODO: Translation
         Spacer(modifier = Modifier.height(32.dp))
         BisqText.baseRegular(
             text = presenter.nym.collectAsState().value,

From 0b0175dc4700b8ae5540b23c278095d2bcc4806d Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Tue, 19 Nov 2024 11:22:50 +0700
Subject: [PATCH 15/25] Move bisq2 compatible models to client.replicated_model

---
 .../replicated_model}/common/network/Address.kt      |  2 +-
 .../common/network/TransportType.kt                  |  2 +-
 .../replicated_model/network/identity/NetworkId.kt   | 12 ++++++++++++
 .../replicated_model}/security/keys/KeyPair.kt       |  2 +-
 .../replicated_model}/security/keys/PubKey.kt        |  2 +-
 .../replicated_model}/security/pow/ProofOfWork.kt    |  2 +-
 .../replicated_model}/user/identity/PreparedData.kt  |  6 +++---
 .../replicated_model}/user/profile/UserProfile.kt    |  6 +++---
 .../client/user_profile/ClientUserProfileModel.kt    |  4 ++--
 .../user_profile/ClientUserProfileServiceFacade.kt   |  2 +-
 .../client/user_profile/UserProfileApiGateway.kt     |  4 ++--
 .../bisq/mobile/domain/network/identity/NetworkId.kt | 12 ------------
 12 files changed, 28 insertions(+), 28 deletions(-)
 rename bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/{domain => client/replicated_model}/common/network/Address.kt (64%)
 rename bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/{domain => client/replicated_model}/common/network/TransportType.kt (63%)
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/network/identity/NetworkId.kt
 rename bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/{domain => client/replicated_model}/security/keys/KeyPair.kt (66%)
 rename bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/{domain => client/replicated_model}/security/keys/PubKey.kt (65%)
 rename bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/{domain => client/replicated_model}/security/pow/ProofOfWork.kt (95%)
 rename bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/{domain => client/replicated_model}/user/identity/PreparedData.kt (54%)
 rename bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/{domain => client/replicated_model}/user/profile/UserProfile.kt (65%)
 delete mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/network/identity/NetworkId.kt

diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/Address.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/common/network/Address.kt
similarity index 64%
rename from bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/Address.kt
rename to bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/common/network/Address.kt
index 8dbaba52..d6a48891 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/Address.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/common/network/Address.kt
@@ -1,4 +1,4 @@
-package network.bisq.mobile.domain.common.network
+package network.bisq.mobile.client.replicated_model.common.network
 
 import kotlinx.serialization.Serializable
 
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/TransportType.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/common/network/TransportType.kt
similarity index 63%
rename from bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/TransportType.kt
rename to bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/common/network/TransportType.kt
index a15d5f09..ef3eab73 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/common/network/TransportType.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/common/network/TransportType.kt
@@ -1,4 +1,4 @@
-package network.bisq.mobile.domain.common.network
+package network.bisq.mobile.client.replicated_model.common.network
 
 import kotlinx.serialization.Serializable
 
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/network/identity/NetworkId.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/network/identity/NetworkId.kt
new file mode 100644
index 00000000..5c8b2a69
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/network/identity/NetworkId.kt
@@ -0,0 +1,12 @@
+package network.bisq.mobile.client.replicated_model.network.identity
+
+import kotlinx.serialization.Serializable
+import network.bisq.mobile.client.replicated_model.common.network.Address
+import network.bisq.mobile.client.replicated_model.common.network.TransportType
+import network.bisq.mobile.client.replicated_model.security.keys.PubKey
+
+@Serializable
+data class NetworkId(
+    val addressByTransportTypeMap: Map<TransportType, Address>,
+    val pubKey: PubKey,
+)
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/KeyPair.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/security/keys/KeyPair.kt
similarity index 66%
rename from bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/KeyPair.kt
rename to bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/security/keys/KeyPair.kt
index 9dcfada4..071c2529 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/KeyPair.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/security/keys/KeyPair.kt
@@ -1,4 +1,4 @@
-package network.bisq.mobile.domain.security.keys
+package network.bisq.mobile.client.replicated_model.security.keys
 
 import kotlinx.serialization.Serializable
 
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/PubKey.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/security/keys/PubKey.kt
similarity index 65%
rename from bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/PubKey.kt
rename to bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/security/keys/PubKey.kt
index 43ee198f..d0c9ef7f 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/keys/PubKey.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/security/keys/PubKey.kt
@@ -1,4 +1,4 @@
-package network.bisq.mobile.domain.security.keys
+package network.bisq.mobile.client.replicated_model.security.keys
 
 import kotlinx.serialization.Serializable
 
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/pow/ProofOfWork.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/security/pow/ProofOfWork.kt
similarity index 95%
rename from bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/pow/ProofOfWork.kt
rename to bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/security/pow/ProofOfWork.kt
index f9b53db0..abee0d76 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/security/pow/ProofOfWork.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/security/pow/ProofOfWork.kt
@@ -1,4 +1,4 @@
-package network.bisq.mobile.domain.security.pow
+package network.bisq.mobile.client.replicated_model.security.pow
 
 import kotlinx.serialization.Contextual
 import kotlinx.serialization.Serializable
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/identity/PreparedData.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/user/identity/PreparedData.kt
similarity index 54%
rename from bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/identity/PreparedData.kt
rename to bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/user/identity/PreparedData.kt
index ea87eb41..4f280ef0 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/identity/PreparedData.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/user/identity/PreparedData.kt
@@ -1,9 +1,9 @@
-package network.bisq.mobile.domain.user.identity
+package network.bisq.mobile.client.replicated_model.user.identity
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
-import network.bisq.mobile.domain.security.keys.KeyPair
-import network.bisq.mobile.domain.security.pow.ProofOfWork
+import network.bisq.mobile.client.replicated_model.security.keys.KeyPair
+import network.bisq.mobile.client.replicated_model.security.pow.ProofOfWork
 
 @Serializable
 data class PreparedData(
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/profile/UserProfile.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/user/profile/UserProfile.kt
similarity index 65%
rename from bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/profile/UserProfile.kt
rename to bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/user/profile/UserProfile.kt
index 67322f47..f2eea427 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user/profile/UserProfile.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/user/profile/UserProfile.kt
@@ -1,8 +1,8 @@
-package network.bisq.mobile.domain.user.profile
+package network.bisq.mobile.client.replicated_model.user.profile
 
 import kotlinx.serialization.Serializable
-import network.bisq.mobile.domain.network.identity.NetworkId
-import network.bisq.mobile.domain.security.pow.ProofOfWork
+import network.bisq.mobile.client.replicated_model.network.identity.NetworkId
+import network.bisq.mobile.client.replicated_model.security.pow.ProofOfWork
 
 @Serializable
 data class UserProfile(
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileModel.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileModel.kt
index a06f4822..ad102f41 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileModel.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileModel.kt
@@ -1,7 +1,7 @@
 package network.bisq.mobile.domain.client.main.user_profile
 
-import network.bisq.mobile.domain.security.keys.KeyPair
-import network.bisq.mobile.domain.security.pow.ProofOfWork
+import network.bisq.mobile.client.replicated_model.security.keys.KeyPair
+import network.bisq.mobile.client.replicated_model.security.pow.ProofOfWork
 import network.bisq.mobile.domain.user_profile.UserProfileModel
 
 class ClientUserProfileModel : UserProfileModel() {
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
index 035e22f4..1df76de4 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
@@ -7,7 +7,7 @@ import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.datetime.Clock
 import network.bisq.mobile.client.user_profile.UserProfileResponse
-import network.bisq.mobile.domain.user.profile.UserProfile
+import network.bisq.mobile.client.replicated_model.user.profile.UserProfile
 import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import kotlin.math.max
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
index 1261abf4..4e533e11 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
@@ -6,8 +6,8 @@ import kotlinx.serialization.modules.SerializersModule
 import kotlinx.serialization.modules.contextual
 import network.bisq.mobile.client.service.ApiRequestService
 import network.bisq.mobile.client.user_profile.UserProfileResponse
-import network.bisq.mobile.domain.user.identity.PreparedData
-import network.bisq.mobile.domain.user.profile.UserProfile
+import network.bisq.mobile.client.replicated_model.user.identity.PreparedData
+import network.bisq.mobile.client.replicated_model.user.profile.UserProfile
 import network.bisq.mobile.utils.ByteArrayAsBase64Serializer
 
 class UserProfileApiGateway(
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/network/identity/NetworkId.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/network/identity/NetworkId.kt
deleted file mode 100644
index a40272af..00000000
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/network/identity/NetworkId.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package network.bisq.mobile.domain.network.identity
-
-import kotlinx.serialization.Serializable
-import network.bisq.mobile.domain.common.network.Address
-import network.bisq.mobile.domain.common.network.TransportType
-import network.bisq.mobile.domain.security.keys.PubKey
-
-@Serializable
-data class NetworkId(
-    val addressByTransportTypeMap: Map<TransportType, Address>,
-    val pubKey: PubKey,
-)

From a8c1ef4f09d2b95524a4513fc0c8a3a9180569b9 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Tue, 19 Nov 2024 12:09:07 +0700
Subject: [PATCH 16/25] Add clientModule, use DI for HttpClient

---
 .../bisq/mobile/client/MainApplication.kt     |  3 ++-
 .../mobile/client/di/AndroidClientModule.kt   |  2 +-
 .../bisq/mobile/client/di/ClientModule.kt     | 19 ++++++++++++++++++
 .../mobile/client/replicated_model/package.kt | 12 +++++++++++
 .../client/service/ApiRequestService.kt       | 20 +++----------------
 .../user_profile/UserProfileApiGateway.kt     |  4 ++--
 .../di/DependenciesProviderHelper.kt          |  3 ++-
 .../mobile/presentation/di/IosClientModule.kt |  2 +-
 8 files changed, 42 insertions(+), 23 deletions(-)
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/di/ClientModule.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/package.kt

diff --git a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainApplication.kt b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainApplication.kt
index 1cb7a55e..6e3599a2 100644
--- a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainApplication.kt
+++ b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainApplication.kt
@@ -2,6 +2,7 @@ package network.bisq.mobile.client
 
 import android.app.Application
 import network.bisq.mobile.client.di.androidClientModule
+import network.bisq.mobile.client.di.clientModule
 import network.bisq.mobile.domain.di.domainModule
 import network.bisq.mobile.presentation.di.presentationModule
 import org.koin.android.ext.koin.androidContext
@@ -14,7 +15,7 @@ class MainApplication: Application() {
 
         startKoin {
             androidContext(this@MainApplication)
-            modules(listOf(domainModule, presentationModule, androidClientModule))
+            modules(listOf(domainModule, presentationModule, clientModule, androidClientModule))
         }
     }
 }
diff --git a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
index 2125ba65..5ac35361 100644
--- a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
+++ b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
@@ -10,7 +10,7 @@ import org.koin.dsl.module
 
 val androidClientModule = module {
     single<UserProfileModel> { ClientUserProfileModel() }
-    single { ApiRequestService("10.0.2.2") }
+    single { ApiRequestService(get(), "10.0.2.2") }
     single { UserProfileApiGateway(get()) }
     single<UserProfileServiceFacade> { ClientUserProfileServiceFacade(get(), get()) }
 
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/di/ClientModule.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/di/ClientModule.kt
new file mode 100644
index 00000000..50643c4d
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/di/ClientModule.kt
@@ -0,0 +1,19 @@
+package network.bisq.mobile.client.di
+
+import io.ktor.client.HttpClient
+import io.ktor.client.engine.cio.CIO
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.serialization.kotlinx.json.json
+import kotlinx.serialization.json.Json
+import org.koin.dsl.module
+
+
+val clientModule = module {
+    single {
+        HttpClient(CIO) {
+            install(ContentNegotiation) {
+                json(Json { ignoreUnknownKeys = true })
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/package.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/package.kt
new file mode 100644
index 00000000..59ef6ec6
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/replicated_model/package.kt
@@ -0,0 +1,12 @@
+/**
+ * The model graph in that package reflect exactly the Bisq 2 model graph and should be used only
+ * for such models.
+ * They come with some adjustments to make it KMP compatible and JSON serializable.
+ * They contain only fields which are not annotated with @JsonIgnore in Bisq 2.
+ *
+ * We do only implement those models which are required in the supported use cases.
+ * In future we might move it to an independent library project to make them re-usable for other
+ * Kotlin based projects using the Bisq 2 REST API.
+ */
+package network.bisq.mobile.client.replicated_model
+
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
index 1fb482fc..8179ae0d 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
@@ -3,36 +3,22 @@ package network.bisq.mobile.client.service
 import co.touchlab.kermit.Logger
 import io.ktor.client.HttpClient
 import io.ktor.client.call.body
-import io.ktor.client.engine.cio.CIO
-import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
 import io.ktor.client.request.get
 import io.ktor.client.request.post
 import io.ktor.client.request.setBody
 import io.ktor.client.statement.bodyAsText
 import io.ktor.http.contentType
-import io.ktor.serialization.kotlinx.json.json
-import kotlinx.serialization.json.Json
 
-class ApiRequestService(host: String) {
+class ApiRequestService(private val httpClient: HttpClient, host: String) {
     private val log = Logger.withTag("RequestService")
     private var baseUrl = "http://$host:8082/api/v1/"
 
     suspend fun get(path: String): String {
-        val client = HttpClient(CIO) {
-            install(ContentNegotiation) {
-                json(Json { ignoreUnknownKeys = true })
-            }
-        }
-        return client.get(baseUrl + path).bodyAsText()
+        return httpClient.get(baseUrl + path).bodyAsText()
     }
 
     suspend fun post(path: String, requestBody: Any): String {
-        val client = HttpClient(CIO) {
-            install(ContentNegotiation) {
-                json(Json { ignoreUnknownKeys = true })
-            }
-        }
-        return client.post(baseUrl + path) {
+        return httpClient.post(baseUrl + path) {
             contentType(io.ktor.http.ContentType.Application.Json)
             setBody(requestBody)
         }.body()
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
index 4e533e11..2ac655e9 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
@@ -4,10 +4,10 @@ import co.touchlab.kermit.Logger
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.modules.SerializersModule
 import kotlinx.serialization.modules.contextual
-import network.bisq.mobile.client.service.ApiRequestService
-import network.bisq.mobile.client.user_profile.UserProfileResponse
 import network.bisq.mobile.client.replicated_model.user.identity.PreparedData
 import network.bisq.mobile.client.replicated_model.user.profile.UserProfile
+import network.bisq.mobile.client.service.ApiRequestService
+import network.bisq.mobile.client.user_profile.UserProfileResponse
 import network.bisq.mobile.utils.ByteArrayAsBase64Serializer
 
 class UserProfileApiGateway(
diff --git a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/DependenciesProviderHelper.kt b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/DependenciesProviderHelper.kt
index 529b6d14..0524df94 100644
--- a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/DependenciesProviderHelper.kt
+++ b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/DependenciesProviderHelper.kt
@@ -3,6 +3,7 @@ package network.bisq.mobile.presentation.di
 import kotlinx.cinterop.BetaInteropApi
 import kotlinx.cinterop.ObjCClass
 import kotlinx.cinterop.getOriginalKotlinClass
+import network.bisq.mobile.client.di.clientModule
 import network.bisq.mobile.domain.di.domainModule
 import org.koin.core.Koin
 import org.koin.core.context.startKoin
@@ -13,7 +14,7 @@ class DependenciesProviderHelper {
 
     fun initKoin() {
         val instance = startKoin {
-            modules(listOf(domainModule, presentationModule, iosClientModule))
+            modules(listOf(domainModule, presentationModule, clientModule, iosClientModule))
         }
 
         koin = instance.koin
diff --git a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
index cfea4e89..198dd826 100644
--- a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
+++ b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
@@ -10,7 +10,7 @@ import org.koin.dsl.module
 
 val iosClientModule = module {
     single<UserProfileModel> { ClientUserProfileModel() }
-    single { ApiRequestService("localhost") }
+    single { ApiRequestService(get(), "localhost") }
     single { UserProfileApiGateway(get()) }
     single<UserProfileServiceFacade> { ClientUserProfileServiceFacade(get(), get()) }
 

From ad7ca976781350dc056de7fe36201645e36802be Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Tue, 19 Nov 2024 12:39:13 +0700
Subject: [PATCH 17/25] Use class name for Logger.withTag Remove unneeded
 coroutineScope.launch blocks

---
 .../NodeUserProfileServiceFacade.kt           |  2 +-
 .../client/service/ApiRequestService.kt       |  2 +-
 .../ClientUserProfileServiceFacade.kt         | 69 ++++++++-----------
 .../user_profile/UserProfileApiGateway.kt     |  2 +-
 .../bisq/mobile/presentation/MainPresenter.kt |  2 +-
 .../uicases/startup/CreateProfilePresenter.kt |  2 +-
 6 files changed, 35 insertions(+), 44 deletions(-)

diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt
index ebb666ad..4d43a771 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt
@@ -31,7 +31,7 @@ class NodeUserProfileServiceFacade(
         private const val AVATAR_VERSION = 0
     }
 
-    private val log = Logger.withTag("NodeUserProfileFacade")
+    private val log = Logger.withTag(this::class.simpleName ?: "NodeUserProfileServiceFacade")
 
 
     private val securityService: SecurityService
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
index 8179ae0d..7340ee63 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
@@ -10,7 +10,7 @@ import io.ktor.client.statement.bodyAsText
 import io.ktor.http.contentType
 
 class ApiRequestService(private val httpClient: HttpClient, host: String) {
-    private val log = Logger.withTag("RequestService")
+    private val log = Logger.withTag(this::class.simpleName ?: "ApiRequestService")
     private var baseUrl = "http://$host:8082/api/v1/"
 
     suspend fun get(path: String): String {
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
index 1df76de4..79de6313 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
@@ -1,13 +1,10 @@
 package network.bisq.mobile.domain.client.main.user_profile
 
 import co.touchlab.kermit.Logger
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
 import kotlinx.datetime.Clock
-import network.bisq.mobile.client.user_profile.UserProfileResponse
 import network.bisq.mobile.client.replicated_model.user.profile.UserProfile
+import network.bisq.mobile.client.user_profile.UserProfileResponse
 import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import kotlin.math.max
@@ -19,10 +16,8 @@ class ClientUserProfileServiceFacade(
     private val apiGateway: UserProfileApiGateway
 ) :
     UserProfileServiceFacade {
-    private val log = Logger.withTag("IosClientUserProfileController")
+    private val log = Logger.withTag(this::class.simpleName ?: "UserProfileServiceFacade")
 
-    // TODO Dispatchers.IO is not supported on iOS. Either customize or find whats on iOS appropriate.
-    private val coroutineScope = CoroutineScope(Dispatchers.Main)
 
     override suspend fun hasUserProfile(): Boolean {
         return getUserIdentityIds().isNotEmpty()
@@ -30,45 +25,41 @@ class ClientUserProfileServiceFacade(
 
     override suspend fun generateKeyPair() {
         model as ClientUserProfileModel
-        coroutineScope.launch {
-            try {
-                model.setGenerateKeyPairInProgress(true)
-                val ts = Clock.System.now().toEpochMilliseconds()
-                val response = apiGateway.requestPreparedData()
-                model.preparedDataAsJson = response.first
-                val preparedData = response.second
+        try {
+            model.setGenerateKeyPairInProgress(true)
+            val ts = Clock.System.now().toEpochMilliseconds()
+            val response = apiGateway.requestPreparedData()
+            model.preparedDataAsJson = response.first
+            val preparedData = response.second
 
-                createSimulatedDelay(Clock.System.now().toEpochMilliseconds() - ts)
+            createSimulatedDelay(Clock.System.now().toEpochMilliseconds() - ts)
 
-                model.keyPair = preparedData.keyPair
-                model.proofOfWork = preparedData.proofOfWork
-                model.setNym(preparedData.nym)
-                model.setId(preparedData.id)
-            } catch (e: Exception) {
-                log.e { e.toString() }
-            } finally {
-                model.setGenerateKeyPairInProgress(false)
-            }
+            model.keyPair = preparedData.keyPair
+            model.proofOfWork = preparedData.proofOfWork
+            model.setNym(preparedData.nym)
+            model.setId(preparedData.id)
+        } catch (e: Exception) {
+            log.e { e.toString() }
+        } finally {
+            model.setGenerateKeyPairInProgress(false)
         }
     }
 
     override suspend fun createAndPublishNewUserProfile() {
         model as ClientUserProfileModel
-        coroutineScope.launch {
-            try {
-                model.setCreateAndPublishInProgress(true)
-                val response: UserProfileResponse =
-                    apiGateway.createAndPublishNewUserProfile(
-                        model.nickName.value,
-                        model.preparedDataAsJson
-                    )
-                require(model.id.value == response.userProfileId)
-                { "userProfileId from model does not match userProfileId from response" }
-            } catch (e: Exception) {
-                log.e { e.toString() }
-            } finally {
-                model.setCreateAndPublishInProgress(false)
-            }
+        try {
+            model.setCreateAndPublishInProgress(true)
+            val response: UserProfileResponse =
+                apiGateway.createAndPublishNewUserProfile(
+                    model.nickName.value,
+                    model.preparedDataAsJson
+                )
+            require(model.id.value == response.userProfileId)
+            { "userProfileId from model does not match userProfileId from response" }
+        } catch (e: Exception) {
+            log.e { e.toString() }
+        } finally {
+            model.setCreateAndPublishInProgress(false)
         }
     }
 
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
index 2ac655e9..bae1ebfa 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
@@ -13,7 +13,7 @@ import network.bisq.mobile.utils.ByteArrayAsBase64Serializer
 class UserProfileApiGateway(
     private val apiRequestService: ApiRequestService
 ) {
-    private val log = Logger.withTag("UserProfileApiGateway")
+    private val log = Logger.withTag(this::class.simpleName ?: "UserProfileApiGateway")
 
     suspend fun requestPreparedData(): Pair<String, PreparedData> {
         val response = apiRequestService.get("user-identity/prepared-data")
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt
index 0577a9af..37592c88 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt
@@ -17,7 +17,7 @@ import network.bisq.mobile.presentation.ui.AppPresenter
  * Main Presenter as an example of implementation for now.
  */
 open class MainPresenter(private val greetingRepository: GreetingRepository<Greeting>) : BasePresenter(null), AppPresenter {
-    private val log = Logger.withTag("MainPresenter")
+    private val log = Logger.withTag(this::class.simpleName ?: "MainPresenter")
     // Observable state
     private val _isContentVisible = MutableStateFlow(false)
     override val isContentVisible: StateFlow<Boolean> = _isContentVisible
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt
index b1560f9e..2a950f51 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt
@@ -18,7 +18,7 @@ open class CreateProfilePresenter(
     private val userProfileRepository: UserProfileRepository
 ) : BasePresenter(mainPresenter) {
 
-    private val log = Logger.withTag("CreateProfilePresenter")
+    private val log = Logger.withTag(this::class.simpleName ?: "CreateProfilePresenter")
     private val userProfileModel = userProfileRepository.model
 
     val nickName: StateFlow<String> = userProfileModel.nickName

From 26367457f02720beea50dc09fc64495763b58eb6 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Tue, 19 Nov 2024 16:33:04 +0700
Subject: [PATCH 18/25] Add ignoreUnknownKeys to json deserializer Make Json 
 configs fields

---
 .../user_profile/UserProfileApiGateway.kt     | 27 +++++++++----------
 1 file changed, 13 insertions(+), 14 deletions(-)

diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
index bae1ebfa..eb6e13e3 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
@@ -15,14 +15,17 @@ class UserProfileApiGateway(
 ) {
     private val log = Logger.withTag(this::class.simpleName ?: "UserProfileApiGateway")
 
+    private val json = Json { ignoreUnknownKeys = true }
+    private val jsonWithByteArraySerializer = Json {
+        serializersModule = SerializersModule {
+            contextual(ByteArrayAsBase64Serializer)
+        }
+        ignoreUnknownKeys = true
+    }
+
     suspend fun requestPreparedData(): Pair<String, PreparedData> {
         val response = apiRequestService.get("user-identity/prepared-data")
-        val json = Json {
-            serializersModule = SerializersModule {
-                contextual(ByteArrayAsBase64Serializer)
-            }
-        }
-        return Pair(response, json.decodeFromString<PreparedData>(response))
+        return Pair(response, jsonWithByteArraySerializer.decodeFromString<PreparedData>(response))
     }
 
     suspend fun createAndPublishNewUserProfile(
@@ -37,21 +40,17 @@ class UserProfileApiGateway(
         )
         val response =
             apiRequestService.post("user-identity/user-identities", createUserIdentityRequest)
-        return Json.decodeFromString(response)
+        return json.decodeFromString(response)
     }
 
     suspend fun getUserIdentityIds(): List<String> {
         val response = apiRequestService.get("user-identity/ids")
-        return Json.decodeFromString(response)
+        return json.decodeFromString(response)
     }
 
+
     suspend fun getSelectedUserProfile(): UserProfile {
         val response = apiRequestService.get("user-identity/selected/user-profile")
-        val json = Json {
-            serializersModule = SerializersModule {
-                contextual(ByteArrayAsBase64Serializer)
-            }
-        }
-        return  json.decodeFromString<UserProfile>(response)
+        return jsonWithByteArraySerializer.decodeFromString<UserProfile>(response)
     }
 }
\ No newline at end of file

From 57aaade9ed01a591108164013cda8a48be520e11 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Wed, 20 Nov 2024 10:28:53 +0700
Subject: [PATCH 19/25] Add serviceFacade and model for bootstrap

---
 .../mobile/client/di/AndroidClientModule.kt   |  7 +++
 .../android/node/di/AndroidNodeModule.kt      |  9 ++++
 .../NodeApplicationBootstrapFacade.kt         | 49 +++++++++++++++++++
 .../NodeApplicationBootstrapModel.kt          |  7 +++
 .../ClientApplicationBootstrapFacade.kt       | 36 ++++++++++++++
 .../ClientApplicationBootstrapModel.kt        |  7 +++
 .../bootstrap/ApplicationBootstrapFacade.kt   |  5 ++
 .../bootstrap/ApplicationBootstrapModel.kt    | 18 +++++++
 8 files changed, 138 insertions(+)
 create mode 100644 bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt
 create mode 100644 bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapModel.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapFacade.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapModel.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapFacade.kt
 create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapModel.kt

diff --git a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
index 5ac35361..52c4122e 100644
--- a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
+++ b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
@@ -1,14 +1,21 @@
 package network.bisq.mobile.client.di
 
+import network.bisq.mobile.android.node.main.bootstrap.ClientApplicationBootstrapFacade
+import network.bisq.mobile.android.node.main.bootstrap.ClientApplicationBootstrapModel
 import network.bisq.mobile.client.service.ApiRequestService
 import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileModel
 import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileServiceFacade
 import network.bisq.mobile.domain.client.main.user_profile.UserProfileApiGateway
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
 import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import org.koin.dsl.module
 
 val androidClientModule = module {
+    single<ApplicationBootstrapModel> { ClientApplicationBootstrapModel() }
+    single<ApplicationBootstrapFacade> { ClientApplicationBootstrapFacade(get()) }
+
     single<UserProfileModel> { ClientUserProfileModel() }
     single { ApiRequestService(get(), "10.0.2.2") }
     single { UserProfileApiGateway(get()) }
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
index 324d858b..e63bb8f8 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
@@ -4,7 +4,11 @@ import network.bisq.mobile.android.node.AndroidApplicationService
 import network.bisq.mobile.android.node.domain.data.repository.NodeGreetingRepository
 import network.bisq.mobile.android.node.domain.user_profile.NodeUserProfileModel
 import network.bisq.mobile.android.node.domain.user_profile.NodeUserProfileServiceFacade
+import network.bisq.mobile.android.node.main.bootstrap.NodeApplicationBootstrapFacade
+import network.bisq.mobile.android.node.main.bootstrap.NodeApplicationBootstrapModel
 import network.bisq.mobile.android.node.presentation.MainNodePresenter
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
 import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import network.bisq.mobile.presentation.MainPresenter
@@ -22,9 +26,14 @@ val androidNodeModule = module {
     // and binding the same obj to 2 different abstractions
     single<MainPresenter> { MainNodePresenter(get(), get()) } bind AppPresenter::class
 
+    single<ApplicationBootstrapModel> { NodeApplicationBootstrapModel() }
+    single<ApplicationBootstrapFacade> { NodeApplicationBootstrapFacade(get(), get()) }
+
     single<UserProfileModel> { NodeUserProfileModel() }
     single<UserProfileServiceFacade> { NodeUserProfileServiceFacade(get(), get()) }
 
+
+
     // Services
 //    TODO might not work because of the jars dependencies, needs more work
 //    single <AndroidMemoryReportService> {
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt
new file mode 100644
index 00000000..0126d914
--- /dev/null
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt
@@ -0,0 +1,49 @@
+package network.bisq.mobile.android.node.main.bootstrap
+
+import bisq.application.State
+import network.bisq.mobile.android.node.AndroidApplicationService
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
+
+class NodeApplicationBootstrapFacade(
+    override val model: ApplicationBootstrapModel,
+    applicationService: AndroidApplicationService
+) :
+    ApplicationBootstrapFacade {
+
+    init {
+        val model = model as ClientApplicationBootstrapModel
+        applicationService.state.addObserver { state: State ->
+            when (state) {
+                State.INITIALIZE_APP -> {
+                    model.setState("Starting Bisq")
+                    model.setProgress(0f)
+                }
+
+                State.INITIALIZE_NETWORK -> {
+                    model.setState("Initialize P2P network")
+                    model.setProgress(0.5f)
+                }
+
+                // not used
+                State.INITIALIZE_WALLET -> {
+                }
+
+                State.INITIALIZE_SERVICES -> {
+                    model.setState("Initialize services")
+                    model.setProgress(0.75f)
+                }
+
+                State.APP_INITIALIZED -> {
+                    model.setState("Bisq started")
+                    model.setProgress(1f)
+                }
+
+                State.FAILED -> {
+                    model.setState("Startup failed")
+                    model.setProgress(0f)
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapModel.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapModel.kt
new file mode 100644
index 00000000..c3472d66
--- /dev/null
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapModel.kt
@@ -0,0 +1,7 @@
+package network.bisq.mobile.android.node.main.bootstrap
+
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
+
+// In case we do not get any specific data added we can use ApplicationBootstrapModel instead
+class NodeApplicationBootstrapModel : ApplicationBootstrapModel() {
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapFacade.kt
new file mode 100644
index 00000000..805fa725
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapFacade.kt
@@ -0,0 +1,36 @@
+package network.bisq.mobile.android.node.main.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.main.bootstrap.ApplicationBootstrapModel
+
+class ClientApplicationBootstrapFacade(
+    override val model: ApplicationBootstrapModel
+) :
+    ApplicationBootstrapFacade {
+    private val coroutineScope =  CoroutineScope(BackgroundDispatcher)
+
+    init {
+        val model = model as ClientApplicationBootstrapModel
+        model.setState( "Dummy state 1")
+        model.setProgress(0f)
+
+        // just dummy loading simulation, might be that there is no loading delay at the end...
+        coroutineScope.launch {
+            delay(500L)
+            model.setState( "Dummy state 2")
+            model.setProgress(0.25f)
+
+            delay(500L)
+            model.setState( "Dummy state 3")
+            model.setProgress(0.5f)
+
+            delay(500L)
+            model.setState( "Dummy state 4")
+            model.setProgress(1f)
+        }
+    }
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapModel.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapModel.kt
new file mode 100644
index 00000000..48f7e283
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapModel.kt
@@ -0,0 +1,7 @@
+package network.bisq.mobile.android.node.main.bootstrap
+
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
+
+// In case we do not get any specific data added we can use ApplicationBootstrapModel instead
+class ClientApplicationBootstrapModel : ApplicationBootstrapModel() {
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapFacade.kt
new file mode 100644
index 00000000..284b9fe1
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapFacade.kt
@@ -0,0 +1,5 @@
+package network.bisq.mobile.domain.data.repository.main.bootstrap
+
+interface ApplicationBootstrapFacade {
+    val model: ApplicationBootstrapModel
+}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapModel.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapModel.kt
new file mode 100644
index 00000000..a45d7db4
--- /dev/null
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapModel.kt
@@ -0,0 +1,18 @@
+package network.bisq.mobile.domain.data.repository.main.bootstrap
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+open class ApplicationBootstrapModel {
+    private val _state = MutableStateFlow("")
+    val state: StateFlow<String> = _state
+    fun setState(value: String) {
+        _state.value = value
+    }
+
+    private val _progress = MutableStateFlow(0f)
+    val progress: StateFlow<Float> = _progress
+    fun setProgress(value: Float) {
+        _progress.value = value
+    }
+}
\ No newline at end of file

From ff9b3bf8c270dc047ea4813ca04f2709d7f0254d Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Wed, 20 Nov 2024 12:09:01 +0700
Subject: [PATCH 20/25] Refactor: Rename MainNodePresenter to NodeMainPresenter

---
 .../network/bisq/mobile/android/node/di/AndroidNodeModule.kt  | 4 ++--
 .../{MainNodePresenter.kt => NodeMainPresenter.kt}            | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)
 rename bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/{MainNodePresenter.kt => NodeMainPresenter.kt} (97%)

diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
index e63bb8f8..0cdd5d0c 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
@@ -6,7 +6,7 @@ import network.bisq.mobile.android.node.domain.user_profile.NodeUserProfileModel
 import network.bisq.mobile.android.node.domain.user_profile.NodeUserProfileServiceFacade
 import network.bisq.mobile.android.node.main.bootstrap.NodeApplicationBootstrapFacade
 import network.bisq.mobile.android.node.main.bootstrap.NodeApplicationBootstrapModel
-import network.bisq.mobile.android.node.presentation.MainNodePresenter
+import network.bisq.mobile.android.node.presentation.NodeMainPresenter
 import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
 import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
 import network.bisq.mobile.domain.user_profile.UserProfileModel
@@ -24,7 +24,7 @@ val androidNodeModule = module {
 
     // this line showcases both, the possibility to change behaviour of the app by changing one definition
     // and binding the same obj to 2 different abstractions
-    single<MainPresenter> { MainNodePresenter(get(), get()) } bind AppPresenter::class
+    single<MainPresenter> { NodeMainPresenter(get(), get()) } bind AppPresenter::class
 
     single<ApplicationBootstrapModel> { NodeApplicationBootstrapModel() }
     single<ApplicationBootstrapFacade> { NodeApplicationBootstrapFacade(get(), get()) }
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt
similarity index 97%
rename from bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt
rename to bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt
index 14a374ed..7347961d 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt
@@ -8,7 +8,7 @@ import network.bisq.mobile.domain.data.repository.GreetingRepository
 import network.bisq.mobile.presentation.MainPresenter
 
 @Suppress("UNCHECKED_CAST")
-class MainNodePresenter(
+class NodeMainPresenter(
     greetingRepository: NodeGreetingRepository,
     private val supplier: AndroidApplicationService.Supplier
 ) : MainPresenter(greetingRepository as GreetingRepository<Greeting>) {

From eea998727cf10fe84290ea53fc9c91cd6256867e Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Wed, 20 Nov 2024 12:20:39 +0700
Subject: [PATCH 21/25] Add  serviceFacade and model for application bootstrap.

This only provides the domain part. It is not yet connected to the UI as its still unclear to me what patter to use for that.

When using the Client version one need to instantiate th ClientApplicationBootstrapFacade and call initialize(). Could be done in the presenter...
---
 .../bisq/mobile/client/MainActivity.kt        |  1 +
 .../android/node/AndroidApplicationService.kt |  5 +++--
 .../android/node/di/AndroidNodeModule.kt      |  8 ++++----
 .../NodeApplicationBootstrapFacade.kt         |  7 +++----
 .../node/presentation/NodeMainPresenter.kt    | 20 +++++++++++++------
 .../ClientApplicationBootstrapFacade.kt       |  2 +-
 .../bootstrap/ApplicationBootstrapFacade.kt   |  1 +
 .../mobile/presentation/di/IosClientModule.kt |  7 +++++++
 8 files changed, 34 insertions(+), 17 deletions(-)

diff --git a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainActivity.kt b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainActivity.kt
index 11bc18b7..0eb830e4 100644
--- a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainActivity.kt
+++ b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainActivity.kt
@@ -12,6 +12,7 @@ import org.koin.android.ext.android.inject
 
 class MainActivity : ComponentActivity() {
     private val presenter: MainPresenter by inject()
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         presenter.attachView(this)
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt
index 0f733020..17ef4998 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt
@@ -73,7 +73,8 @@ class AndroidApplicationService(context: Context, userDataDir: Path?) :
     class Supplier {
         @Setter
         lateinit var applicationService: AndroidApplicationService
-
+        var stateSupplier: androidx.core.util.Supplier<Observable<State>> =
+            Supplier { applicationService.state }
         var securityServiceSupplier: androidx.core.util.Supplier<SecurityService> =
             Supplier { applicationService.securityService }
         var networkServiceSupplier: androidx.core.util.Supplier<NetworkService> =
@@ -225,7 +226,7 @@ class AndroidApplicationService(context: Context, userDataDir: Path?) :
         log.info("readAllPersisted took {} ms", System.currentTimeMillis() - ts)
 
         return securityService.initialize()
-            .thenCompose<Boolean> { result: Boolean? ->
+            .thenCompose { result: Boolean? ->
                 setState(State.INITIALIZE_NETWORK)
                 networkService.initialize()
             }
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
index 0cdd5d0c..9ec96ebc 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
@@ -22,10 +22,6 @@ val androidNodeModule = module {
 
     single { AndroidApplicationService.Supplier() }
 
-    // this line showcases both, the possibility to change behaviour of the app by changing one definition
-    // and binding the same obj to 2 different abstractions
-    single<MainPresenter> { NodeMainPresenter(get(), get()) } bind AppPresenter::class
-
     single<ApplicationBootstrapModel> { NodeApplicationBootstrapModel() }
     single<ApplicationBootstrapFacade> { NodeApplicationBootstrapFacade(get(), get()) }
 
@@ -33,6 +29,10 @@ val androidNodeModule = module {
     single<UserProfileServiceFacade> { NodeUserProfileServiceFacade(get(), get()) }
 
 
+    // this line showcases both, the possibility to change behaviour of the app by changing one definition
+    // and binding the same obj to 2 different abstractions
+    single<MainPresenter> { NodeMainPresenter(get(), get(), get()) } bind AppPresenter::class
+
 
     // Services
 //    TODO might not work because of the jars dependencies, needs more work
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt
index 0126d914..7404a625 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt
@@ -7,13 +7,12 @@ import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBoot
 
 class NodeApplicationBootstrapFacade(
     override val model: ApplicationBootstrapModel,
-    applicationService: AndroidApplicationService
+    private val applicationServiceSupplier: AndroidApplicationService.Supplier
 ) :
     ApplicationBootstrapFacade {
 
-    init {
-        val model = model as ClientApplicationBootstrapModel
-        applicationService.state.addObserver { state: State ->
+    override fun initialize() {
+        applicationServiceSupplier.stateSupplier.get().addObserver { state: State ->
             when (state) {
                 State.INITIALIZE_APP -> {
                     model.setState("Starting Bisq")
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt
index 7347961d..c331f8bc 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt
@@ -5,22 +5,30 @@ import network.bisq.mobile.android.node.AndroidApplicationService
 import network.bisq.mobile.android.node.domain.data.repository.NodeGreetingRepository
 import network.bisq.mobile.domain.data.model.Greeting
 import network.bisq.mobile.domain.data.repository.GreetingRepository
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
 import network.bisq.mobile.presentation.MainPresenter
 
 @Suppress("UNCHECKED_CAST")
 class NodeMainPresenter(
     greetingRepository: NodeGreetingRepository,
-    private val supplier: AndroidApplicationService.Supplier
+    private val supplier: AndroidApplicationService.Supplier,
+    private val applicationBootstrapFacade: ApplicationBootstrapFacade
 ) : MainPresenter(greetingRepository as GreetingRepository<Greeting>) {
 
+    // FIXME onViewAttached is called twice
+    var applicationServiceInited = false
     override fun onViewAttached() {
         super.onViewAttached()
 
-        val context = (view as Activity).applicationContext
-        val filesDirsPath = (view as Activity).filesDir.toPath()
-        supplier.applicationService =
-            AndroidApplicationService(context, filesDirsPath)
-        supplier.applicationService.initialize().join()
+        if (!applicationServiceInited) {
+            applicationServiceInited = true
+            val context = (view as Activity).applicationContext
+            val filesDirsPath = (view as Activity).filesDir.toPath()
+            supplier.applicationService =
+                AndroidApplicationService(context, filesDirsPath)
+            applicationBootstrapFacade.initialize()
+            supplier.applicationService.initialize()
+        }
     }
 
     override fun onDestroying() {
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapFacade.kt
index 805fa725..b757fcb8 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapFacade.kt
@@ -13,7 +13,7 @@ class ClientApplicationBootstrapFacade(
     ApplicationBootstrapFacade {
     private val coroutineScope =  CoroutineScope(BackgroundDispatcher)
 
-    init {
+    override fun initialize() {
         val model = model as ClientApplicationBootstrapModel
         model.setState( "Dummy state 1")
         model.setProgress(0f)
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapFacade.kt
index 284b9fe1..a84a71af 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapFacade.kt
@@ -1,5 +1,6 @@
 package network.bisq.mobile.domain.data.repository.main.bootstrap
 
 interface ApplicationBootstrapFacade {
+    fun initialize()
     val model: ApplicationBootstrapModel
 }
\ No newline at end of file
diff --git a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
index 198dd826..fddfac0d 100644
--- a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
+++ b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
@@ -1,14 +1,21 @@
 package network.bisq.mobile.presentation.di
 
+import network.bisq.mobile.android.node.main.bootstrap.ClientApplicationBootstrapFacade
+import network.bisq.mobile.android.node.main.bootstrap.ClientApplicationBootstrapModel
 import network.bisq.mobile.client.service.ApiRequestService
 import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileModel
 import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileServiceFacade
 import network.bisq.mobile.domain.client.main.user_profile.UserProfileApiGateway
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
 import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import org.koin.dsl.module
 
 val iosClientModule = module {
+    single<ApplicationBootstrapModel> { ClientApplicationBootstrapModel() }
+    single<ApplicationBootstrapFacade> { ClientApplicationBootstrapFacade(get()) }
+
     single<UserProfileModel> { ClientUserProfileModel() }
     single { ApiRequestService(get(), "localhost") }
     single { UserProfileApiGateway(get()) }

From 4787d890744da300e2fe267799e90d36dc56dca5 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Wed, 20 Nov 2024 20:35:19 +0700
Subject: [PATCH 22/25] Remove domain models and use return values instead

---
 .../mobile/client/di/AndroidClientModule.kt   | 16 ++--
 .../AndroidClientMainPresenter.kt             | 22 +++++
 .../android/node/di/AndroidNodeModule.kt      | 10 +-
 .../NodeApplicationBootstrapFacade.kt         | 28 +++---
 .../NodeApplicationBootstrapModel.kt          |  7 --
 .../user_profile/NodeUserProfileModel.kt      | 10 --
 .../NodeUserProfileServiceFacade.kt           | 92 ++++++-------------
 .../node/presentation/NodeMainPresenter.kt    |  2 -
 .../ClientApplicationBootstrapFacade.kt       | 26 +++---
 .../ClientApplicationBootstrapModel.kt        |  7 --
 .../user_profile/ClientUserProfileModel.kt    | 11 ---
 .../ClientUserProfileServiceFacade.kt         | 59 ++++--------
 .../bootstrap/ApplicationBootstrapFacade.kt   | 20 +++-
 .../bootstrap/ApplicationBootstrapModel.kt    | 18 ----
 .../domain/data/repository/Repositories.kt    |  3 -
 .../bisq/mobile/domain/di/DomainModule.kt     |  2 -
 .../domain/user_profile/UserProfileModel.kt   | 47 ----------
 .../user_profile/UserProfileServiceFacade.kt  | 11 +--
 .../presentation/di/PresentationModule.kt     |  8 +-
 .../uicases/startup/CreateProfilePresenter.kt | 77 ++++++++++------
 .../ui/uicases/startup/CreateProfileScreen.kt |  5 +-
 .../ui/uicases/startup/SplashPresenter.kt     | 42 +++++----
 .../ui/uicases/startup/SplashScreen.kt        | 31 ++-----
 .../presentation/IosClientMainPresenter.kt    | 21 +++++
 .../mobile/presentation/di/IosClientModule.kt | 15 ++-
 25 files changed, 238 insertions(+), 352 deletions(-)
 create mode 100644 bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/presentation/AndroidClientMainPresenter.kt
 delete mode 100644 bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapModel.kt
 delete mode 100644 bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileModel.kt
 delete mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapModel.kt
 delete mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileModel.kt
 delete mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapModel.kt
 delete mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt
 create mode 100644 bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/IosClientMainPresenter.kt

diff --git a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
index 52c4122e..0cdb64b2 100644
--- a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
+++ b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/di/AndroidClientModule.kt
@@ -1,24 +1,22 @@
 package network.bisq.mobile.client.di
 
 import network.bisq.mobile.android.node.main.bootstrap.ClientApplicationBootstrapFacade
-import network.bisq.mobile.android.node.main.bootstrap.ClientApplicationBootstrapModel
+import network.bisq.mobile.client.presentation.AndroidClientMainPresenter
 import network.bisq.mobile.client.service.ApiRequestService
-import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileModel
 import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileServiceFacade
 import network.bisq.mobile.domain.client.main.user_profile.UserProfileApiGateway
 import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
-import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
-import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
+import network.bisq.mobile.presentation.MainPresenter
+import network.bisq.mobile.presentation.ui.AppPresenter
+import org.koin.dsl.bind
 import org.koin.dsl.module
 
 val androidClientModule = module {
-    single<ApplicationBootstrapModel> { ClientApplicationBootstrapModel() }
-    single<ApplicationBootstrapFacade> { ClientApplicationBootstrapFacade(get()) }
+    single<ApplicationBootstrapFacade> { ClientApplicationBootstrapFacade() }
 
-    single<UserProfileModel> { ClientUserProfileModel() }
     single { ApiRequestService(get(), "10.0.2.2") }
     single { UserProfileApiGateway(get()) }
-    single<UserProfileServiceFacade> { ClientUserProfileServiceFacade(get(), get()) }
-
+    single<UserProfileServiceFacade> { ClientUserProfileServiceFacade(get()) }
+    single<MainPresenter> { AndroidClientMainPresenter(get()) } bind AppPresenter::class
 }
diff --git a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/presentation/AndroidClientMainPresenter.kt b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/presentation/AndroidClientMainPresenter.kt
new file mode 100644
index 00000000..8080b6ab
--- /dev/null
+++ b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/presentation/AndroidClientMainPresenter.kt
@@ -0,0 +1,22 @@
+package network.bisq.mobile.client.presentation
+
+import network.bisq.mobile.domain.data.repository.GreetingRepository
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
+import network.bisq.mobile.presentation.MainPresenter
+
+@Suppress("UNCHECKED_CAST")
+class AndroidClientMainPresenter(
+    private val applicationBootstrapFacade: ApplicationBootstrapFacade
+) : MainPresenter(GreetingRepository()) {
+
+    // FIXME onViewAttached is called twice
+    var applicationServiceInited = false
+    override fun onViewAttached() {
+        super.onViewAttached()
+
+        if (!applicationServiceInited) {
+            applicationServiceInited = true
+            applicationBootstrapFacade.initialize()
+        }
+    }
+}
\ No newline at end of file
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
index 9ec96ebc..b67cea71 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
@@ -2,14 +2,10 @@ package network.bisq.mobile.android.node.di
 
 import network.bisq.mobile.android.node.AndroidApplicationService
 import network.bisq.mobile.android.node.domain.data.repository.NodeGreetingRepository
-import network.bisq.mobile.android.node.domain.user_profile.NodeUserProfileModel
 import network.bisq.mobile.android.node.domain.user_profile.NodeUserProfileServiceFacade
 import network.bisq.mobile.android.node.main.bootstrap.NodeApplicationBootstrapFacade
-import network.bisq.mobile.android.node.main.bootstrap.NodeApplicationBootstrapModel
 import network.bisq.mobile.android.node.presentation.NodeMainPresenter
 import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
-import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
-import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import network.bisq.mobile.presentation.MainPresenter
 import network.bisq.mobile.presentation.ui.AppPresenter
@@ -22,11 +18,9 @@ val androidNodeModule = module {
 
     single { AndroidApplicationService.Supplier() }
 
-    single<ApplicationBootstrapModel> { NodeApplicationBootstrapModel() }
-    single<ApplicationBootstrapFacade> { NodeApplicationBootstrapFacade(get(), get()) }
+    single<ApplicationBootstrapFacade> { NodeApplicationBootstrapFacade(get()) }
 
-    single<UserProfileModel> { NodeUserProfileModel() }
-    single<UserProfileServiceFacade> { NodeUserProfileServiceFacade(get(), get()) }
+    single<UserProfileServiceFacade> { NodeUserProfileServiceFacade(get()) }
 
 
     // this line showcases both, the possibility to change behaviour of the app by changing one definition
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt
index 7404a625..bc12d3d4 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt
@@ -3,25 +3,23 @@ package network.bisq.mobile.android.node.main.bootstrap
 import bisq.application.State
 import network.bisq.mobile.android.node.AndroidApplicationService
 import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
-import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
 
 class NodeApplicationBootstrapFacade(
-    override val model: ApplicationBootstrapModel,
-    private val applicationServiceSupplier: AndroidApplicationService.Supplier
+    private val supplier: AndroidApplicationService.Supplier
 ) :
-    ApplicationBootstrapFacade {
+    ApplicationBootstrapFacade() {
 
     override fun initialize() {
-        applicationServiceSupplier.stateSupplier.get().addObserver { state: State ->
+        supplier.stateSupplier.get().addObserver { state: State ->
             when (state) {
                 State.INITIALIZE_APP -> {
-                    model.setState("Starting Bisq")
-                    model.setProgress(0f)
+                   setState("Starting Bisq")
+                   setProgress(0f)
                 }
 
                 State.INITIALIZE_NETWORK -> {
-                    model.setState("Initialize P2P network")
-                    model.setProgress(0.5f)
+                   setState("Initialize P2P network")
+                   setProgress(0.5f)
                 }
 
                 // not used
@@ -29,18 +27,18 @@ class NodeApplicationBootstrapFacade(
                 }
 
                 State.INITIALIZE_SERVICES -> {
-                    model.setState("Initialize services")
-                    model.setProgress(0.75f)
+                   setState("Initialize services")
+                   setProgress(0.75f)
                 }
 
                 State.APP_INITIALIZED -> {
-                    model.setState("Bisq started")
-                    model.setProgress(1f)
+                   setState("Bisq started")
+                   setProgress(1f)
                 }
 
                 State.FAILED -> {
-                    model.setState("Startup failed")
-                    model.setProgress(0f)
+                   setState("Startup failed")
+                   setProgress(0f)
                 }
             }
         }
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapModel.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapModel.kt
deleted file mode 100644
index c3472d66..00000000
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapModel.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package network.bisq.mobile.android.node.main.bootstrap
-
-import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
-
-// In case we do not get any specific data added we can use ApplicationBootstrapModel instead
-class NodeApplicationBootstrapModel : ApplicationBootstrapModel() {
-}
\ No newline at end of file
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileModel.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileModel.kt
deleted file mode 100644
index 7b3d05d1..00000000
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileModel.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package network.bisq.mobile.android.node.domain.user_profile
-
-import bisq.security.pow.ProofOfWork
-import network.bisq.mobile.domain.user_profile.UserProfileModel
-import java.security.KeyPair
-
-class NodeUserProfileModel : UserProfileModel() {
-    lateinit var keyPair: KeyPair
-    lateinit var proofOfWork: ProofOfWork
-}
\ No newline at end of file
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt
index 4d43a771..5dbd41d8 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/user_profile/NodeUserProfileServiceFacade.kt
@@ -3,14 +3,14 @@ package network.bisq.mobile.android.node.domain.user_profile
 import bisq.common.encoding.Hex
 import bisq.security.DigestUtil
 import bisq.security.SecurityService
+import bisq.security.pow.ProofOfWork
 import bisq.user.UserService
 import bisq.user.identity.NymIdGenerator
-import bisq.user.identity.UserIdentity
 import bisq.user.profile.UserProfile
 import co.touchlab.kermit.Logger
 import network.bisq.mobile.android.node.AndroidApplicationService
-import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
+import java.security.KeyPair
 import java.util.Random
 import kotlin.math.max
 import kotlin.math.min
@@ -21,10 +21,7 @@ import kotlin.math.min
  * It uses in a in-memory model for the relevant data required for the presenter to reflect the domains state.
  * Persistence is done inside the Bisq 2 libraries.
  */
-class NodeUserProfileServiceFacade(
-    override val model: UserProfileModel,
-    private val applicationServiceSupplier: AndroidApplicationService.Supplier
-) :
+class NodeUserProfileServiceFacade(private val supplier: AndroidApplicationService.Supplier) :
     UserProfileServiceFacade {
 
     companion object {
@@ -33,35 +30,33 @@ class NodeUserProfileServiceFacade(
 
     private val log = Logger.withTag(this::class.simpleName ?: "NodeUserProfileServiceFacade")
 
+    private var pubKeyHash: ByteArray? = null
+    private var keyPair: KeyPair? = null
+    private var proofOfWork: ProofOfWork? = null
 
     private val securityService: SecurityService
-        get() = applicationServiceSupplier.securityServiceSupplier.get()
+        get() = supplier.securityServiceSupplier.get()
 
     private val userService: UserService
-        get() = applicationServiceSupplier.userServiceSupplier.get()
+        get() = supplier.userServiceSupplier.get()
 
 
     override suspend fun hasUserProfile(): Boolean {
         return userService.userIdentityService.userIdentities.isNotEmpty()
     }
 
-    override suspend fun generateKeyPair() {
-        model as NodeUserProfileModel
-        model.setGenerateKeyPairInProgress(true)
-        val keyPair = securityService.keyBundleService.generateKeyPair()
-        model.keyPair = keyPair
-        val pubKeyHash = DigestUtil.hash(keyPair.public.encoded)
-        model.pubKeyHash = pubKeyHash
-        model.setId(Hex.encode(pubKeyHash))
+    override suspend fun generateKeyPair(result: (String, String) -> Unit) {
+        keyPair = securityService.keyBundleService.generateKeyPair()
+        pubKeyHash = DigestUtil.hash(keyPair!!.public.encoded)
+
         val ts = System.currentTimeMillis()
-        val proofOfWork = userService.userIdentityService.mintNymProofOfWork(pubKeyHash)
+        proofOfWork = userService.userIdentityService.mintNymProofOfWork(pubKeyHash)
         val powDuration = System.currentTimeMillis() - ts
         log.i("Proof of work creation completed after $powDuration ms")
         createSimulatedDelay(powDuration)
-        model.proofOfWork = proofOfWork
-        val powSolution = proofOfWork.solution
-        val nym = NymIdGenerator.generate(pubKeyHash, powSolution)
-        model.setNym(nym)
+
+        val id = Hex.encode(pubKeyHash)
+        val nym = NymIdGenerator.generate(pubKeyHash, proofOfWork!!.solution)
 
         // CatHash is in desktop, needs to be reimplemented or the javafx part extracted and refactored into a non javafx lib
         //  Image image = CatHash.getImage(pubKeyHash,
@@ -69,63 +64,36 @@ class NodeUserProfileServiceFacade(
         //                                CURRENT_AVATARS_VERSION,
         //                                CreateProfileModel.CAT_HASH_IMAGE_SIZE);
 
-        model.setGenerateKeyPairInProgress(false)
+        result(id!!, nym!!)
     }
 
-    override suspend fun createAndPublishNewUserProfile() {
-        model as NodeUserProfileModel
-        model.setCreateAndPublishInProgress(true)  // UI should start busy animation based on that property
+    override suspend fun createAndPublishNewUserProfile(nickName: String) {
         userService.userIdentityService.createAndPublishNewUserProfile(
-            model.nickName.value,
-            model.keyPair,
-            model.pubKeyHash,
-            model.proofOfWork,
+            nickName,
+            keyPair,
+            pubKeyHash,
+            proofOfWork,
             AVATAR_VERSION,
             "",
             ""
         )
-            .whenComplete { userIdentity: UserIdentity?, throwable: Throwable? ->
-                // UI should stop busy animation and show `next` button
-                model.setCreateAndPublishInProgress(false)
-            }
+
+        pubKeyHash = null
+        keyPair = null
+        proofOfWork = null
     }
 
     override suspend fun getUserIdentityIds(): List<String> {
-        return userService.userIdentityService.userIdentities
-            .map { userIdentity -> userIdentity.id }
+        return userService.userIdentityService.userIdentities.map { userIdentity -> userIdentity.id }
     }
 
-    override suspend fun applySelectedUserProfile() {
+    override suspend fun applySelectedUserProfile(result: (String?, String?, String?) -> Unit) {
         val userProfile = getSelectedUserProfile()
-        if (userProfile != null) {
-            model.setNickName(userProfile.nickName)
-            model.setNym(userProfile.nym)
-            model.setId(userProfile.id)
-        }
+        result(userProfile?.nickName, userProfile?.nym, userProfile?.id)
     }
 
     private fun getSelectedUserProfile(): UserProfile? {
-        val userIdentity = userService.userIdentityService.selectedUserIdentity ?: return null
-        return userIdentity.userProfile
-
-    }
-
-    private fun findUserProfile(id: String): UserProfileModel? {
-        return userService.userIdentityService.userIdentities
-            .map { userIdentity -> getNodeUserProfileModel(userIdentity) }
-            .find { model -> model.id.equals(id) }
-    }
-
-    private fun getNodeUserProfileModel(userIdentity: UserIdentity): UserProfileModel {
-        val userProfile = userIdentity.userProfile
-        val model = NodeUserProfileModel()
-        model.setNickName(userProfile.nickName)
-        model.setNym(userProfile.nym)
-        model.setId(userProfile.id)
-        model.keyPair = userIdentity.identity.keyBundle.keyPair
-        model.pubKeyHash = userIdentity.userProfile.pubKeyHash
-        model.proofOfWork = userIdentity.userProfile.proofOfWork
-        return model
+        return userService.userIdentityService.selectedUserIdentity?.userProfile
     }
 
     private fun createSimulatedDelay(powDuration: Long) {
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt
index c331f8bc..091cde51 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt
@@ -35,6 +35,4 @@ class NodeMainPresenter(
         supplier.applicationService.shutdown()
         super.onDestroying()
     }
-
-
 }
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapFacade.kt
index b757fcb8..76ef94ac 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapFacade.kt
@@ -5,32 +5,28 @@ 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.main.bootstrap.ApplicationBootstrapModel
 
-class ClientApplicationBootstrapFacade(
-    override val model: ApplicationBootstrapModel
-) :
-    ApplicationBootstrapFacade {
-    private val coroutineScope =  CoroutineScope(BackgroundDispatcher)
+class ClientApplicationBootstrapFacade() :
+    ApplicationBootstrapFacade() {
+    private val coroutineScope = CoroutineScope(BackgroundDispatcher)
 
     override fun initialize() {
-        val model = model as ClientApplicationBootstrapModel
-        model.setState( "Dummy state 1")
-        model.setProgress(0f)
+        setState("Dummy state 1")
+        setProgress(0f)
 
         // just dummy loading simulation, might be that there is no loading delay at the end...
         coroutineScope.launch {
             delay(500L)
-            model.setState( "Dummy state 2")
-            model.setProgress(0.25f)
+            setState("Dummy state 2")
+            setProgress(0.25f)
 
             delay(500L)
-            model.setState( "Dummy state 3")
-            model.setProgress(0.5f)
+            setState("Dummy state 3")
+            setProgress(0.5f)
 
             delay(500L)
-            model.setState( "Dummy state 4")
-            model.setProgress(1f)
+            setState("Dummy state 4")
+            setProgress(1f)
         }
     }
 }
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapModel.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapModel.kt
deleted file mode 100644
index 48f7e283..00000000
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/bootstrap/ClientApplicationBootstrapModel.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package network.bisq.mobile.android.node.main.bootstrap
-
-import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
-
-// In case we do not get any specific data added we can use ApplicationBootstrapModel instead
-class ClientApplicationBootstrapModel : ApplicationBootstrapModel() {
-}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileModel.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileModel.kt
deleted file mode 100644
index ad102f41..00000000
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileModel.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package network.bisq.mobile.domain.client.main.user_profile
-
-import network.bisq.mobile.client.replicated_model.security.keys.KeyPair
-import network.bisq.mobile.client.replicated_model.security.pow.ProofOfWork
-import network.bisq.mobile.domain.user_profile.UserProfileModel
-
-class ClientUserProfileModel : UserProfileModel() {
-    lateinit var preparedDataAsJson: String
-    lateinit var keyPair: KeyPair
-    lateinit var proofOfWork: ProofOfWork
-}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
index 79de6313..0f405153 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
@@ -5,65 +5,52 @@ import kotlinx.coroutines.delay
 import kotlinx.datetime.Clock
 import network.bisq.mobile.client.replicated_model.user.profile.UserProfile
 import network.bisq.mobile.client.user_profile.UserProfileResponse
-import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import kotlin.math.max
 import kotlin.math.min
 import kotlin.random.Random
 
-class ClientUserProfileServiceFacade(
-    override val model: UserProfileModel,
-    private val apiGateway: UserProfileApiGateway
-) :
+class ClientUserProfileServiceFacade(private val apiGateway: UserProfileApiGateway) :
     UserProfileServiceFacade {
     private val log = Logger.withTag(this::class.simpleName ?: "UserProfileServiceFacade")
 
+    private var preparedDataAsJson: String? = null
 
     override suspend fun hasUserProfile(): Boolean {
         return getUserIdentityIds().isNotEmpty()
     }
 
-    override suspend fun generateKeyPair() {
-        model as ClientUserProfileModel
+    override suspend fun generateKeyPair(result: (String, String) -> Unit) {
         try {
-            model.setGenerateKeyPairInProgress(true)
             val ts = Clock.System.now().toEpochMilliseconds()
             val response = apiGateway.requestPreparedData()
-            model.preparedDataAsJson = response.first
+            preparedDataAsJson = response.first
             val preparedData = response.second
 
             createSimulatedDelay(Clock.System.now().toEpochMilliseconds() - ts)
 
-            model.keyPair = preparedData.keyPair
-            model.proofOfWork = preparedData.proofOfWork
-            model.setNym(preparedData.nym)
-            model.setId(preparedData.id)
+            result(preparedData.id, preparedData.nym)
         } catch (e: Exception) {
             log.e { e.toString() }
-        } finally {
-            model.setGenerateKeyPairInProgress(false)
         }
     }
 
-    override suspend fun createAndPublishNewUserProfile() {
-        model as ClientUserProfileModel
-        try {
-            model.setCreateAndPublishInProgress(true)
-            val response: UserProfileResponse =
-                apiGateway.createAndPublishNewUserProfile(
-                    model.nickName.value,
-                    model.preparedDataAsJson
-                )
-            require(model.id.value == response.userProfileId)
-            { "userProfileId from model does not match userProfileId from response" }
-        } catch (e: Exception) {
-            log.e { e.toString() }
-        } finally {
-            model.setCreateAndPublishInProgress(false)
+    override suspend fun createAndPublishNewUserProfile(nickName: String) {
+        preparedDataAsJson?.let { preparedDataAsJson ->
+            try {
+                val response: UserProfileResponse =
+                    apiGateway.createAndPublishNewUserProfile(
+                        nickName,
+                        preparedDataAsJson
+                    )
+                this.preparedDataAsJson = null
+                log.i { "Call to createAndPublishNewUserProfile successful. userProfileId = $response.userProfileId" }
+            } catch (e: Exception) {
+                log.e { e.toString() }
+            }
         }
     }
 
-
     override suspend fun getUserIdentityIds(): List<String> {
         return try {
             apiGateway.getUserIdentityIds()
@@ -73,12 +60,10 @@ class ClientUserProfileServiceFacade(
         }
     }
 
-    override suspend fun applySelectedUserProfile() {
+    override suspend fun applySelectedUserProfile(result: (String?, String?, String?) -> Unit) {
         try {
             val userProfile = getSelectedUserProfile()
-            model.setNickName(userProfile.nickName)
-            model.setNym(userProfile.nym)
-            model.setId(userProfile.id)
+            result(userProfile.nickName, userProfile.nym, userProfile.id)
         } catch (e: Exception) {
             log.e { e.toString() }
         }
@@ -88,10 +73,6 @@ class ClientUserProfileServiceFacade(
         return apiGateway.getSelectedUserProfile()
     }
 
-    private suspend fun findUserProfile(id: String): UserProfileModel? {
-        TODO("Not yet implemented")
-    }
-
     private suspend fun createSimulatedDelay(requestDuration: Long) {
         // Proof of work creation for difficulty 65536 takes about 50 ms to 100 ms on a 4 GHz Intel Core i7.
         // The API request is likely also quite fast
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapFacade.kt
index a84a71af..8eb2fff3 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapFacade.kt
@@ -1,6 +1,20 @@
 package network.bisq.mobile.domain.data.repository.main.bootstrap
 
-interface ApplicationBootstrapFacade {
-    fun initialize()
-    val model: ApplicationBootstrapModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+abstract class ApplicationBootstrapFacade {
+    abstract fun initialize()
+
+    private val _state = MutableStateFlow("")
+    val state: StateFlow<String> = _state
+    fun setState(value: String) {
+        _state.value = value
+    }
+
+    private val _progress = MutableStateFlow(0f)
+    val progress: StateFlow<Float> = _progress
+    fun setProgress(value: Float) {
+        _progress.value = value
+    }
 }
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapModel.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapModel.kt
deleted file mode 100644
index a45d7db4..00000000
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/bootstrap/ApplicationBootstrapModel.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package network.bisq.mobile.domain.data.repository.main.bootstrap
-
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-
-open class ApplicationBootstrapModel {
-    private val _state = MutableStateFlow("")
-    val state: StateFlow<String> = _state
-    fun setState(value: String) {
-        _state.value = value
-    }
-
-    private val _progress = MutableStateFlow(0f)
-    val progress: StateFlow<Float> = _progress
-    fun setProgress(value: Float) {
-        _progress.value = value
-    }
-}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt
index 0b09afc4..44b0504b 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt
@@ -4,8 +4,6 @@ import network.bisq.mobile.domain.data.model.BisqStats
 import network.bisq.mobile.domain.data.model.BtcPrice
 import network.bisq.mobile.domain.data.model.Greeting
 import network.bisq.mobile.domain.data.model.Settings
-import network.bisq.mobile.domain.user_profile.UserProfileModel
-import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 
 // this way of definingsupports both platforms
 // add your repositories here and then in your DI module call this classes for instanciation
@@ -13,4 +11,3 @@ open class GreetingRepository<T: Greeting>: SingleObjectRepository<T>()
 open class BisqStatsRepository: SingleObjectRepository<BisqStats>()
 open class BtcPriceRepository: SingleObjectRepository<BtcPrice>()
 open class SettingsRepository: SingleObjectRepository<Settings>()
-class UserProfileRepository(val model: UserProfileModel, val service: UserProfileServiceFacade)
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt
index 767918f4..042a5836 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt
@@ -5,7 +5,6 @@ import network.bisq.mobile.domain.data.repository.BisqStatsRepository
 import network.bisq.mobile.domain.data.repository.BtcPriceRepository
 import network.bisq.mobile.domain.data.repository.GreetingRepository
 import network.bisq.mobile.domain.data.repository.SettingsRepository
-import network.bisq.mobile.domain.data.repository.UserProfileRepository
 import org.koin.dsl.module
 
 val domainModule = module {
@@ -13,5 +12,4 @@ val domainModule = module {
     single<BisqStatsRepository> { BisqStatsRepository() }
     single<BtcPriceRepository> { BtcPriceRepository() }
     single<SettingsRepository> { SettingsRepository() }
-    single { UserProfileRepository(get(), get()) }
 }
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt
deleted file mode 100644
index ea824bbe..00000000
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileModel.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package network.bisq.mobile.domain.user_profile
-
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-
-open class UserProfileModel {
-    private val _nickName = MutableStateFlow("")
-    val nickName: StateFlow<String> get() = _nickName
-    fun setNickName(value: String) {
-        _nickName.value = value
-    }
-
-    private val _id = MutableStateFlow("")
-    val id: StateFlow<String> get() = _id
-    fun setId(value: String) {
-        _id.value = value
-    }
-
-    private val _nym = MutableStateFlow("")
-    val nym: StateFlow<String> get() = _nym
-    fun setNym(value: String) {
-        _nym.value = value
-    }
-
-    private val _generateKeyPairInProgress = MutableStateFlow(false)
-    val generateKeyPairInProgress: StateFlow<Boolean> get() = _generateKeyPairInProgress
-    fun setGenerateKeyPairInProgress(value: Boolean) {
-        _generateKeyPairInProgress.value = value
-    }
-
-    private val _createAndPublishInProgress = MutableStateFlow(false)
-    val createAndPublishInProgress: StateFlow<Boolean> get() = _createAndPublishInProgress
-    fun setCreateAndPublishInProgress(value: Boolean) {
-        _createAndPublishInProgress.value = value
-    }
-
-    lateinit var pubKeyHash: ByteArray
-
-    override fun toString(): String {
-        return "UserProfileModel(_nickName=${_nickName.value}, " +
-                "_id=${_id.value}, " +
-                "_nym=${_nym.value}, " +
-                "_generateKeyPairInProgress=${_generateKeyPairInProgress.value}, " +
-                "_createAndPublishInProgress=${_createAndPublishInProgress.value}, " +
-                "pubKeyHash=${pubKeyHash.contentToString()})"
-    }
-}
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileServiceFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileServiceFacade.kt
index 35b2232e..60011040 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileServiceFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/user_profile/UserProfileServiceFacade.kt
@@ -1,11 +1,6 @@
 package network.bisq.mobile.domain.user_profile
 
 interface UserProfileServiceFacade {
-    /**
-     * The model for holding presentation relevant data as well as data needed for creating the user identity.
-     */
-    val model: UserProfileModel
-
     /**
      * Returns true if there is a user identity already created.
      * This should be used to detect a first time user who has no identity created yet and where
@@ -26,7 +21,7 @@ interface UserProfileServiceFacade {
      * the proof of work solution.
      * The CatHash image is also created based on that hash and the proof of work solution.
      */
-    suspend fun generateKeyPair()
+    suspend fun generateKeyPair(result: (String, String) -> Unit)
 
     /**
      * Once the user clicks the `create` button we create a user identity and publish the
@@ -34,7 +29,7 @@ interface UserProfileServiceFacade {
      * The user identity contains the key pair and is private data. The UserProfile is public data
      * and shared with the network.
      */
-    suspend fun createAndPublishNewUserProfile()
+    suspend fun createAndPublishNewUserProfile(nickName: String)
 
     /**
      * Create UserProfileModels from the userIdentities.
@@ -44,5 +39,5 @@ interface UserProfileServiceFacade {
     /**
      * Applies the selected user identity to the user profile model
      */
-    suspend fun applySelectedUserProfile()
+    suspend fun applySelectedUserProfile(result: (String?, String?, String?) -> Unit)
 }
\ No newline at end of file
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt
index dd0d6b06..57d1db7a 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt
@@ -8,7 +8,6 @@ import network.bisq.mobile.presentation.ui.uicases.GettingStartedPresenter
 import network.bisq.mobile.presentation.ui.uicases.IGettingStarted
 import network.bisq.mobile.presentation.ui.uicases.startup.CreateProfilePresenter
 import network.bisq.mobile.presentation.ui.uicases.startup.IOnboardingPresenter
-import network.bisq.mobile.presentation.ui.uicases.startup.ISplashPresenter
 import network.bisq.mobile.presentation.ui.uicases.startup.ITrustedNodeSetupPresenter
 import network.bisq.mobile.presentation.ui.uicases.startup.OnBoardingPresenter
 import network.bisq.mobile.presentation.ui.uicases.startup.SplashPresenter
@@ -29,9 +28,10 @@ val presentationModule = module {
     single { (navController: NavController) ->
         SplashPresenter(
             get(),
-            navController = navController
+            navController = navController,
+            get()
         )
-    } bind ISplashPresenter::class
+    }
 
     single { (navController: NavController) ->
         OnBoardingPresenter(
@@ -52,7 +52,7 @@ val presentationModule = module {
         CreateProfilePresenter(
             get(),
             navController = navController,
-            userProfileRepository = get()
+            get()
         )
     }
 
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt
index 2a950f51..42294034 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt
@@ -4,10 +4,11 @@ import androidx.navigation.NavController
 import co.touchlab.kermit.Logger
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 import network.bisq.mobile.domain.data.BackgroundDispatcher
-import network.bisq.mobile.domain.data.repository.UserProfileRepository
+import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import network.bisq.mobile.presentation.BasePresenter
 import network.bisq.mobile.presentation.MainPresenter
 import network.bisq.mobile.presentation.ui.navigation.Routes
@@ -15,16 +16,40 @@ import network.bisq.mobile.presentation.ui.navigation.Routes
 open class CreateProfilePresenter(
     mainPresenter: MainPresenter,
     private val navController: NavController,
-    private val userProfileRepository: UserProfileRepository
+    private val userProfileService: UserProfileServiceFacade
 ) : BasePresenter(mainPresenter) {
 
     private val log = Logger.withTag(this::class.simpleName ?: "CreateProfilePresenter")
-    private val userProfileModel = userProfileRepository.model
 
-    val nickName: StateFlow<String> = userProfileModel.nickName
-    val nym: StateFlow<String> = userProfileModel.nym
-    val id: StateFlow<String> = userProfileModel.id
+    private val _id = MutableStateFlow("")
+    val id: StateFlow<String> get() = _id
+    private fun setId(value: String) {
+        _id.value = value
+    }
+
+    private val _nym = MutableStateFlow("")
+    val nym: StateFlow<String> get() = _nym
+    private fun setNym(value: String) {
+        _nym.value = value
+    }
+
+    private val _nickName = MutableStateFlow("")
+    val nickName: StateFlow<String> get() = _nickName
+    fun setNickname(value: String) {
+        _nickName.value = value
+    }
 
+    private val _generateKeyPairInProgress = MutableStateFlow(false)
+    val generateKeyPairInProgress: StateFlow<Boolean> get() = _generateKeyPairInProgress
+    private fun setGenerateKeyPairInProgress(value: Boolean) {
+        _generateKeyPairInProgress.value = value
+    }
+
+    private val _createAndPublishInProgress = MutableStateFlow(false)
+    val createAndPublishInProgress: StateFlow<Boolean> get() = _createAndPublishInProgress
+    private fun setCreateAndPublishInProgress(value: Boolean) {
+        _createAndPublishInProgress.value = value
+    }
 
     override fun onViewAttached() {
         onGenerateKeyPair()
@@ -41,33 +66,31 @@ open class CreateProfilePresenter(
         // `onGenerateKeyPair()` when view is ready.
     }
 
-    fun onNickNameChanged(value: String) {
-        userProfileRepository.model.setNickName(value)
-    }
-
     fun onGenerateKeyPair() {
         // takes 200 -1000 ms
-        // todo start busy animation in UI
         CoroutineScope(BackgroundDispatcher).launch {
-            userProfileRepository.model.generateKeyPairInProgress.collect { inProgress ->
-                if (!inProgress) {
-                    // todo stop busy animation in UI
-                }
+            setGenerateKeyPairInProgress(true)
+            log.i { "Show busy animation for generateKeyPair" }
+            userProfileService.generateKeyPair { id, nym ->
+                setId(id)
+                setNym(nym)
+                //todo show new profile image
             }
-        }
-
-        CoroutineScope(BackgroundDispatcher).launch {
-            userProfileRepository.service.generateKeyPair()
+            setGenerateKeyPairInProgress(false)
+            log.i { "Hide busy animation for generateKeyPair" }
         }
     }
 
     fun onCreateAndPublishNewUserProfile() {
-        // todo start busy animation in UI
-        // We cannot use BackgroundDispatcher here as we get error:
-        // `Method setCurrentState must be called on the main thread`
-        CoroutineScope(Dispatchers.Main).launch {
-            userProfileRepository.model.createAndPublishInProgress.collect { inProgress ->
-                if (!inProgress) {
+        if (nickName.value.isNotEmpty()) {
+            CoroutineScope(BackgroundDispatcher).launch {
+                setCreateAndPublishInProgress(true)
+                log.i { "Show busy animation for createAndPublishInProgress" }
+                userProfileService.createAndPublishNewUserProfile(nickName.value)
+                log.i { "Hide busy animation for createAndPublishInProgress" }
+                setCreateAndPublishInProgress(false)
+
+                CoroutineScope(Dispatchers.Main).launch {
                     // todo stop busy animation in UI
                     navController.navigate(Routes.TrustedNodeSetup.name) {
                         popUpTo(Routes.CreateProfile.name) { inclusive = true }
@@ -75,9 +98,5 @@ open class CreateProfilePresenter(
                 }
             }
         }
-
-        CoroutineScope(BackgroundDispatcher).launch {
-            userProfileRepository.service.createAndPublishNewUserProfile()
-        }
     }
 }
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt
index 15d7c168..58ab8e83 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt
@@ -35,7 +35,6 @@ fun CreateProfileScreen(
     val navController: NavHostController = koinInject(named("RootNavController"))
     val presenter: CreateProfilePresenter = koinInject { parametersOf(navController) }
 
-    val profileName = presenter.nickName.collectAsState().value
 
     LaunchedEffect(Unit) {
         presenter.onViewAttached()
@@ -65,7 +64,7 @@ fun CreateProfileScreen(
             MaterialTextField(
                 text = presenter.nickName.collectAsState().value,
                 placeholder = strings.onboarding_createProfile_nickName_prompt,
-                onValueChanged = { presenter.onNickNameChanged(it) })
+                onValueChanged = { presenter.setNickname(it) })
         }
         Spacer(modifier = Modifier.height(36.dp))
         Image(painterResource(Res.drawable.img_bot_image), "User profile icon generated from the hash of the public key") // TODO: Translation
@@ -90,7 +89,7 @@ fun CreateProfileScreen(
         BisqButton(
             strings.buttons_next,
             onClick = { presenter.onCreateAndPublishNewUserProfile() },
-            backgroundColor = if (profileName.isEmpty()) BisqTheme.colors.primaryDisabled else BisqTheme.colors.primary
+            backgroundColor = if (presenter.nickName.value.isEmpty()) BisqTheme.colors.primaryDisabled else BisqTheme.colors.primary
         )
     }
 }
\ No newline at end of file
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt
index 64bd7464..99d55652 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt
@@ -3,36 +3,30 @@ package network.bisq.mobile.presentation.ui.uicases.startup
 import androidx.navigation.NavController
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
 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 navController: NavController
-) : BasePresenter(mainPresenter), ISplashPresenter {
+    private val navController: NavController,
+    applicationBootstrapFacade: ApplicationBootstrapFacade
+) : BasePresenter(mainPresenter) {
     private val coroutineScope = CoroutineScope(Dispatchers.Main)
 
-    override fun startLoading(onProgressUpdate: (Float) -> Unit) {
+    val state: StateFlow<String> = applicationBootstrapFacade.state
+    val progress: StateFlow<Float> = applicationBootstrapFacade.progress
+
+    override fun onViewAttached() {
         coroutineScope.launch {
-            initializeNetwork{ progress ->
-                onProgressUpdate(progress)
+            progress.collect { value ->
+                when {
+                    value == 1.0f -> navigateToNextScreen()
+                }
             }
-            navigateToNextScreen()
-        }
-    }
-
-    // TODO refactor into a service once networking is done
-    protected open suspend fun initializeNetwork(updateProgress: (Float) -> Unit) {
-
-        //1. Initialize Tor here
-        //2. Connect to peers (for androidNode), to Bisq instance (for xClients)
-        //3. Do any other app initialization
-        for (i in 1..100) {
-            updateProgress(i.toFloat() / 100)
-            delay(25)
         }
     }
 
@@ -40,7 +34,15 @@ open class SplashPresenter(
         // TODO: Conditional nav
         // If firstTimeApp launch, goto Onboarding[clientMode] (androidNode / xClient)
         // If not, goto TabContainerScreen
-        navController.navigate(Routes.Onboarding.name) {
+        /*  navController.navigate(Routes.Onboarding.name) {
+              popUpTo(Routes.Splash.name) { inclusive = true }
+          }*/
+
+        //TODO
+        /* navController.navigate(Routes.TabContainer.name) {
+             popUpTo(Routes.TrustedNodeSetup.name) { inclusive = true }
+         }*/
+        navController.navigate(Routes.CreateProfile.name) {
             popUpTo(Routes.Splash.name) { inclusive = true }
         }
     }
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashScreen.kt
index 33e74445..3d2e31af 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashScreen.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashScreen.kt
@@ -1,63 +1,50 @@
 package network.bisq.mobile.presentation.ui.uicases.startup
 
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.collectAsState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.text.style.TextAlign
-import androidx.navigation.NavController
 import androidx.navigation.NavHostController
-
 import cafe.adriel.lyricist.LocalStrings
-import network.bisq.mobile.presentation.ViewPresenter
 import network.bisq.mobile.presentation.ui.components.atoms.BisqProgressBar
-import network.bisq.mobile.presentation.ui.components.atoms.icons.BisqLogo
 import network.bisq.mobile.presentation.ui.components.atoms.BisqText
+import network.bisq.mobile.presentation.ui.components.atoms.icons.BisqLogo
 import network.bisq.mobile.presentation.ui.components.layout.BisqStaticLayout
-import network.bisq.mobile.presentation.ui.theme.*
+import network.bisq.mobile.presentation.ui.theme.BisqTheme
 import org.koin.compose.koinInject
 import org.koin.core.parameter.parametersOf
 import org.koin.core.qualifier.named
 
-interface ISplashPresenter: ViewPresenter {
-    fun startLoading(onProgressUpdate: (Float) -> Unit)
-}
 
 @Composable
 fun SplashScreen(
 ) {
     val strings = LocalStrings.current
     val navController: NavHostController = koinInject(named("RootNavController"))
-    val presenter: ISplashPresenter = koinInject { parametersOf(navController) }
-    
-    var currentProgress by remember { mutableFloatStateOf(0f) }
+    val presenter: SplashPresenter = koinInject { parametersOf(navController) }
 
     LaunchedEffect(Unit) {
         presenter.onViewAttached()
-        presenter.startLoading { progress ->
-            currentProgress = progress
-        }
     }
 
     BisqStaticLayout {
         BisqLogo()
 
         Column {
-            BisqProgressBar(progress = currentProgress)
+            BisqProgressBar(presenter.progress.collectAsState().value)
 
             // TODO: Get this from presenter
             val networkType = strings.splash_bootstrapState_network_TOR
 
             BisqText.baseRegular(
-                text = strings.splash_bootstrapState_BOOTSTRAP_TO_NETWORK(networkType),
+                text = presenter.state.collectAsState().value,
                 color = BisqTheme.colors.secondaryHover,
                 textAlign = TextAlign.Center,
                 modifier = Modifier.fillMaxWidth(),
-                )
+            )
         }
     }
 }
diff --git a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/IosClientMainPresenter.kt b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/IosClientMainPresenter.kt
new file mode 100644
index 00000000..cc67d416
--- /dev/null
+++ b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/IosClientMainPresenter.kt
@@ -0,0 +1,21 @@
+package network.bisq.mobile.presentation
+
+import network.bisq.mobile.domain.data.repository.GreetingRepository
+import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
+
+@Suppress("UNCHECKED_CAST")
+class IosClientMainPresenter(
+    private val applicationBootstrapFacade: ApplicationBootstrapFacade
+) : MainPresenter(GreetingRepository()) {
+
+    // FIXME onViewAttached is called twice
+    var applicationServiceInited = false
+    override fun onViewAttached() {
+        super.onViewAttached()
+
+        if (!applicationServiceInited) {
+            applicationServiceInited = true
+            applicationBootstrapFacade.initialize()
+        }
+    }
+}
\ No newline at end of file
diff --git a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
index fddfac0d..6b14f6d4 100644
--- a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
+++ b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/IosClientModule.kt
@@ -1,24 +1,23 @@
 package network.bisq.mobile.presentation.di
 
 import network.bisq.mobile.android.node.main.bootstrap.ClientApplicationBootstrapFacade
-import network.bisq.mobile.android.node.main.bootstrap.ClientApplicationBootstrapModel
 import network.bisq.mobile.client.service.ApiRequestService
-import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileModel
 import network.bisq.mobile.domain.client.main.user_profile.ClientUserProfileServiceFacade
 import network.bisq.mobile.domain.client.main.user_profile.UserProfileApiGateway
 import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
-import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapModel
-import network.bisq.mobile.domain.user_profile.UserProfileModel
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
+import network.bisq.mobile.presentation.IosClientMainPresenter
+import network.bisq.mobile.presentation.MainPresenter
+import network.bisq.mobile.presentation.ui.AppPresenter
+import org.koin.dsl.bind
 import org.koin.dsl.module
 
 val iosClientModule = module {
-    single<ApplicationBootstrapModel> { ClientApplicationBootstrapModel() }
-    single<ApplicationBootstrapFacade> { ClientApplicationBootstrapFacade(get()) }
+    single<ApplicationBootstrapFacade> { ClientApplicationBootstrapFacade() }
 
-    single<UserProfileModel> { ClientUserProfileModel() }
     single { ApiRequestService(get(), "localhost") }
     single { UserProfileApiGateway(get()) }
-    single<UserProfileServiceFacade> { ClientUserProfileServiceFacade(get(), get()) }
+    single<UserProfileServiceFacade> { ClientUserProfileServiceFacade(get()) }
 
+    single<MainPresenter> { IosClientMainPresenter(get()) } bind AppPresenter::class
 }

From 5d7ba216fbfa5beab241b8e5a5a700421d4d9c71 Mon Sep 17 00:00:00 2001
From: Rodrigo Varela <rodvar@gmail.com>
Date: Thu, 21 Nov 2024 08:53:55 +1100
Subject: [PATCH 23/25] Nits contribution

 - fix double call on view attached for the special case of the
   MainPresenter
 - remove double static configuration of bisq core jars (left in
   MainApplication override only)
 - provide single instance of android memory report service and adjust
   usages
 - cleanup/comment dead code
 - cleanup unused imports
---
 .../AndroidClientMainPresenter.kt             |  2 --
 .../android/node/AndroidApplicationService.kt | 25 ++-------------
 .../android/node/di/AndroidNodeModule.kt      | 31 +++++--------------
 .../NodeApplicationBootstrapFacade.kt         |  2 +-
 .../node/presentation/NodeMainPresenter.kt    |  5 +--
 .../bisq/mobile/presentation/MainPresenter.kt |  2 --
 .../bisq/mobile/presentation/ui/App.kt        |  8 ++---
 .../presentation/IosClientMainPresenter.kt    |  2 --
 8 files changed, 18 insertions(+), 59 deletions(-)

diff --git a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/presentation/AndroidClientMainPresenter.kt b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/presentation/AndroidClientMainPresenter.kt
index 8080b6ab..a002bfc0 100644
--- a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/presentation/AndroidClientMainPresenter.kt
+++ b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/presentation/AndroidClientMainPresenter.kt
@@ -8,8 +8,6 @@ import network.bisq.mobile.presentation.MainPresenter
 class AndroidClientMainPresenter(
     private val applicationBootstrapFacade: ApplicationBootstrapFacade
 ) : MainPresenter(GreetingRepository()) {
-
-    // FIXME onViewAttached is called twice
     var applicationServiceInited = false
     override fun onViewAttached() {
         super.onViewAttached()
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt
index 17ef4998..dc454292 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/AndroidApplicationService.kt
@@ -16,8 +16,6 @@
  */
 package network.bisq.mobile.android.node
 
-import android.content.Context
-import android.os.Process
 import androidx.core.util.Supplier
 import bisq.account.AccountService
 import bisq.application.ApplicationService
@@ -25,10 +23,6 @@ import bisq.application.State
 import bisq.bonded_roles.BondedRolesService
 import bisq.bonded_roles.security_manager.alert.AlertNotificationsService
 import bisq.chat.ChatService
-import bisq.common.facades.FacadeProvider
-import bisq.common.facades.android.AndroidGuavaFacade
-import bisq.common.facades.android.AndroidJdkFacade
-import bisq.common.network.AndroidEmulatorLocalhostFacade
 import bisq.common.observable.Observable
 import bisq.common.util.ExceptionUtil
 import bisq.contract.ContractService
@@ -49,11 +43,9 @@ import lombok.Getter
 import lombok.Setter
 import lombok.extern.slf4j.Slf4j
 import network.bisq.mobile.android.node.service.AndroidMemoryReportService
-import org.bouncycastle.jce.provider.BouncyCastleProvider
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import java.nio.file.Path
-import java.security.Security
 import java.util.Optional
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.TimeUnit
@@ -66,7 +58,7 @@ import java.util.concurrent.TimeUnit
  */
 @Slf4j
 @Getter
-class AndroidApplicationService(context: Context, userDataDir: Path?) :
+class AndroidApplicationService(androidMemoryReportService: AndroidMemoryReportService, userDataDir: Path?) :
     ApplicationService("android", arrayOf<String>(), userDataDir) {
 
     @Getter
@@ -115,23 +107,10 @@ class AndroidApplicationService(context: Context, userDataDir: Path?) :
         val log: Logger = LoggerFactory.getLogger(ApplicationService::class.java)
     }
 
-    init {
-        FacadeProvider.setLocalhostFacade(AndroidEmulatorLocalhostFacade())
-        FacadeProvider.setJdkFacade(AndroidJdkFacade(Process.myPid()))
-        FacadeProvider.setGuavaFacade(AndroidGuavaFacade())
-
-        // Androids default BC version does not support all algorithms we need, thus we remove
-        // it and add our BC provider
-        Security.removeProvider("BC")
-        Security.addProvider(BouncyCastleProvider())
-    }
-
     val state = Observable(State.INITIALIZE_APP)
     private val shutDownErrorMessage = Observable<String>()
     private val startupErrorMessage = Observable<String>()
 
-    val androidMemoryService = AndroidMemoryReportService(context)
-
     val securityService =
         SecurityService(persistenceService, SecurityService.Config.from(getConfig("security")))
 
@@ -144,7 +123,7 @@ class AndroidApplicationService(context: Context, userDataDir: Path?) :
         securityService.keyBundleService,
         securityService.hashCashProofOfWorkService,
         securityService.equihashProofOfWorkService,
-        androidMemoryService
+        androidMemoryReportService
     )
     val identityService = IdentityService(
         persistenceService,
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
index b67cea71..41000405 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt
@@ -1,14 +1,16 @@
 package network.bisq.mobile.android.node.di
 
 import network.bisq.mobile.android.node.AndroidApplicationService
+import network.bisq.mobile.android.node.domain.bootstrap.NodeApplicationBootstrapFacade
 import network.bisq.mobile.android.node.domain.data.repository.NodeGreetingRepository
 import network.bisq.mobile.android.node.domain.user_profile.NodeUserProfileServiceFacade
-import network.bisq.mobile.android.node.main.bootstrap.NodeApplicationBootstrapFacade
 import network.bisq.mobile.android.node.presentation.NodeMainPresenter
+import network.bisq.mobile.android.node.service.AndroidMemoryReportService
 import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
 import network.bisq.mobile.presentation.MainPresenter
 import network.bisq.mobile.presentation.ui.AppPresenter
+import org.koin.android.ext.koin.androidContext
 import org.koin.dsl.bind
 import org.koin.dsl.module
 
@@ -16,6 +18,10 @@ val androidNodeModule = module {
     // this one is for example properties, will be eliminated soon
     single<NodeGreetingRepository> { NodeGreetingRepository() }
 
+    single<AndroidMemoryReportService> {
+        AndroidMemoryReportService(androidContext())
+    }
+
     single { AndroidApplicationService.Supplier() }
 
     single<ApplicationBootstrapFacade> { NodeApplicationBootstrapFacade(get()) }
@@ -25,26 +31,5 @@ val androidNodeModule = module {
 
     // this line showcases both, the possibility to change behaviour of the app by changing one definition
     // and binding the same obj to 2 different abstractions
-    single<MainPresenter> { NodeMainPresenter(get(), get(), get()) } bind AppPresenter::class
-
-
-    // Services
-//    TODO might not work because of the jars dependencies, needs more work
-//    single <AndroidMemoryReportService> {
-//        val context = androidContext()
-//        AndroidMemoryReportService(context)
-//    }
-//    single <AndroidApplicationService> {
-//        val filesDirsPath = androidContext().filesDir.toPath()
-//        val androidMemoryService: AndroidMemoryReportService = get()
-//        AndroidApplicationService(androidMemoryService, filesDirsPath)
-//    }
-//    single <UserIdentityService> {
-//        val applicationService: AndroidApplicationService = get()
-//        applicationService.userService.userIdentityService
-//    }
-//    single <SecurityService> {
-//        val applicationService: AndroidApplicationService = get()
-//        applicationService.securityService
-//    }
+    single<MainPresenter> { NodeMainPresenter(get(), get(), get(), get()) } bind AppPresenter::class
 }
\ No newline at end of file
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt
index bc12d3d4..62af1fc0 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/bootstrap/NodeApplicationBootstrapFacade.kt
@@ -1,4 +1,4 @@
-package network.bisq.mobile.android.node.main.bootstrap
+package network.bisq.mobile.android.node.domain.bootstrap
 
 import bisq.application.State
 import network.bisq.mobile.android.node.AndroidApplicationService
diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt
index 091cde51..900d700c 100644
--- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt
+++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeMainPresenter.kt
@@ -3,6 +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.domain.data.repository.NodeGreetingRepository
+import network.bisq.mobile.android.node.service.AndroidMemoryReportService
 import network.bisq.mobile.domain.data.model.Greeting
 import network.bisq.mobile.domain.data.repository.GreetingRepository
 import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade
@@ -12,10 +13,10 @@ import network.bisq.mobile.presentation.MainPresenter
 class NodeMainPresenter(
     greetingRepository: NodeGreetingRepository,
     private val supplier: AndroidApplicationService.Supplier,
+    private val androidMemoryReportService: AndroidMemoryReportService,
     private val applicationBootstrapFacade: ApplicationBootstrapFacade
 ) : MainPresenter(greetingRepository as GreetingRepository<Greeting>) {
 
-    // FIXME onViewAttached is called twice
     var applicationServiceInited = false
     override fun onViewAttached() {
         super.onViewAttached()
@@ -25,7 +26,7 @@ class NodeMainPresenter(
             val context = (view as Activity).applicationContext
             val filesDirsPath = (view as Activity).filesDir.toPath()
             supplier.applicationService =
-                AndroidApplicationService(context, filesDirsPath)
+                AndroidApplicationService(androidMemoryReportService, filesDirsPath)
             applicationBootstrapFacade.initialize()
             supplier.applicationService.initialize()
         }
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt
index 37592c88..175bbf77 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt
@@ -2,8 +2,6 @@ package network.bisq.mobile.presentation
 
 import co.touchlab.kermit.Logger
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.IO
 import kotlinx.coroutines.flow.*
 import kotlinx.coroutines.launch
 import network.bisq.mobile.android.node.BuildNodeConfig
diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt
index a40d48b8..da5f9b37 100644
--- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt
+++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt
@@ -1,8 +1,6 @@
 package network.bisq.mobile.presentation.ui
 
-import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.runtime.*
-import androidx.navigation.NavController
 import androidx.navigation.compose.rememberNavController
 import cafe.adriel.lyricist.ProvideStrings
 import cafe.adriel.lyricist.rememberStrings
@@ -38,11 +36,13 @@ fun App() {
 
     var isNavControllerSet by remember { mutableStateOf(false) }
 
-    val presenter: AppPresenter = koinInject()
+//    Looks like the main view composable is not needing the presenter at all - uncomment this if this changes
+//    val presenter: AppPresenter = koinInject()
 
 
     LaunchedEffect(rootNavController) {
-        presenter.onViewAttached()
+//        For the main presenter use case we leave this for the moment the activity/viewcontroller respectively gets attached
+//        presenter.onViewAttached()
         getKoin().setProperty("RootNavController", rootNavController)
         getKoin().setProperty("TabNavController", tabNavController)
         isNavControllerSet = true
diff --git a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/IosClientMainPresenter.kt b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/IosClientMainPresenter.kt
index cc67d416..8d9c4b59 100644
--- a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/IosClientMainPresenter.kt
+++ b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/IosClientMainPresenter.kt
@@ -7,8 +7,6 @@ import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBoot
 class IosClientMainPresenter(
     private val applicationBootstrapFacade: ApplicationBootstrapFacade
 ) : MainPresenter(GreetingRepository()) {
-
-    // FIXME onViewAttached is called twice
     var applicationServiceInited = false
     override fun onViewAttached() {
         super.onViewAttached()

From b468e6b59a099c40feeafdd0c1005c73ed13de83 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Thu, 21 Nov 2024 12:15:37 +0700
Subject: [PATCH 24/25] Apply code review suggestions

---
 .../bisq/mobile/client/di/ClientModule.kt     | 10 +++++-
 .../client/service/ApiRequestService.kt       | 14 ++++----
 .../ClientUserProfileServiceFacade.kt         | 18 +++++-----
 .../user_profile/CreateUserIdentityRequest.kt |  3 +-
 .../user_profile/UserProfileApiGateway.kt     | 34 +++++--------------
 5 files changed, 35 insertions(+), 44 deletions(-)

diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/di/ClientModule.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/di/ClientModule.kt
index 50643c4d..fe568b05 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/di/ClientModule.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/di/ClientModule.kt
@@ -5,6 +5,9 @@ import io.ktor.client.engine.cio.CIO
 import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
 import io.ktor.serialization.kotlinx.json.json
 import kotlinx.serialization.json.Json
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.modules.contextual
+import network.bisq.mobile.utils.ByteArrayAsBase64Serializer
 import org.koin.dsl.module
 
 
@@ -12,7 +15,12 @@ val clientModule = module {
     single {
         HttpClient(CIO) {
             install(ContentNegotiation) {
-                json(Json { ignoreUnknownKeys = true })
+                json(Json {
+                    serializersModule = SerializersModule {
+                        contextual(ByteArrayAsBase64Serializer)
+                    }
+                    ignoreUnknownKeys = true
+                })
             }
         }
     }
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
index 7340ee63..a8d0b74d 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/ApiRequestService.kt
@@ -6,19 +6,21 @@ import io.ktor.client.call.body
 import io.ktor.client.request.get
 import io.ktor.client.request.post
 import io.ktor.client.request.setBody
-import io.ktor.client.statement.bodyAsText
 import io.ktor.http.contentType
 
-class ApiRequestService(private val httpClient: HttpClient, host: String) {
+class ApiRequestService(val httpClient: HttpClient, host: String) {
     private val log = Logger.withTag(this::class.simpleName ?: "ApiRequestService")
+
     private var baseUrl = "http://$host:8082/api/v1/"
 
-    suspend fun get(path: String): String {
-        return httpClient.get(baseUrl + path).bodyAsText()
+    fun endpoint(path: String) = baseUrl + path
+
+    suspend inline fun <reified T> get(path: String): T {
+        return httpClient.get(endpoint(path)).body()
     }
 
-    suspend fun post(path: String, requestBody: Any): String {
-        return httpClient.post(baseUrl + path) {
+    suspend inline fun <reified T> post(path: String, requestBody: Any): T {
+        return httpClient.post(endpoint(path)) {
             contentType(io.ktor.http.ContentType.Application.Json)
             setBody(requestBody)
         }.body()
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
index 0f405153..61dc8a7e 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/ClientUserProfileServiceFacade.kt
@@ -3,6 +3,7 @@ package network.bisq.mobile.domain.client.main.user_profile
 import co.touchlab.kermit.Logger
 import kotlinx.coroutines.delay
 import kotlinx.datetime.Clock
+import network.bisq.mobile.client.replicated_model.user.identity.PreparedData
 import network.bisq.mobile.client.replicated_model.user.profile.UserProfile
 import network.bisq.mobile.client.user_profile.UserProfileResponse
 import network.bisq.mobile.domain.user_profile.UserProfileServiceFacade
@@ -14,7 +15,7 @@ class ClientUserProfileServiceFacade(private val apiGateway: UserProfileApiGatew
     UserProfileServiceFacade {
     private val log = Logger.withTag(this::class.simpleName ?: "UserProfileServiceFacade")
 
-    private var preparedDataAsJson: String? = null
+    private var preparedData: PreparedData? = null
 
     override suspend fun hasUserProfile(): Boolean {
         return getUserIdentityIds().isNotEmpty()
@@ -23,28 +24,25 @@ class ClientUserProfileServiceFacade(private val apiGateway: UserProfileApiGatew
     override suspend fun generateKeyPair(result: (String, String) -> Unit) {
         try {
             val ts = Clock.System.now().toEpochMilliseconds()
-            val response = apiGateway.requestPreparedData()
-            preparedDataAsJson = response.first
-            val preparedData = response.second
-
+            val preparedData = apiGateway.requestPreparedData()
             createSimulatedDelay(Clock.System.now().toEpochMilliseconds() - ts)
-
             result(preparedData.id, preparedData.nym)
+            this.preparedData = preparedData
         } catch (e: Exception) {
             log.e { e.toString() }
         }
     }
 
     override suspend fun createAndPublishNewUserProfile(nickName: String) {
-        preparedDataAsJson?.let { preparedDataAsJson ->
+        preparedData?.let { preparedData ->
             try {
                 val response: UserProfileResponse =
                     apiGateway.createAndPublishNewUserProfile(
                         nickName,
-                        preparedDataAsJson
+                        preparedData
                     )
-                this.preparedDataAsJson = null
-                log.i { "Call to createAndPublishNewUserProfile successful. userProfileId = $response.userProfileId" }
+                this.preparedData = null
+                log.i { "Call to createAndPublishNewUserProfile successful. userProfileId = ${response.userProfileId}" }
             } catch (e: Exception) {
                 log.e { e.toString() }
             }
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/CreateUserIdentityRequest.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/CreateUserIdentityRequest.kt
index e7a9ecac..69c97df3 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/CreateUserIdentityRequest.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/CreateUserIdentityRequest.kt
@@ -1,11 +1,12 @@
 package network.bisq.mobile.domain.client.main.user_profile
 
 import kotlinx.serialization.Serializable
+import network.bisq.mobile.client.replicated_model.user.identity.PreparedData
 
 @Serializable
 data class CreateUserIdentityRequest(
     val nickName: String,
     val terms: String = "",
     val statement: String = "",
-    val preparedDataJson: String
+    val preparedData: PreparedData
 )
\ No newline at end of file
diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
index eb6e13e3..fd9407e1 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
@@ -1,56 +1,38 @@
 package network.bisq.mobile.domain.client.main.user_profile
 
 import co.touchlab.kermit.Logger
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.modules.SerializersModule
-import kotlinx.serialization.modules.contextual
 import network.bisq.mobile.client.replicated_model.user.identity.PreparedData
 import network.bisq.mobile.client.replicated_model.user.profile.UserProfile
 import network.bisq.mobile.client.service.ApiRequestService
 import network.bisq.mobile.client.user_profile.UserProfileResponse
-import network.bisq.mobile.utils.ByteArrayAsBase64Serializer
 
 class UserProfileApiGateway(
     private val apiRequestService: ApiRequestService
 ) {
     private val log = Logger.withTag(this::class.simpleName ?: "UserProfileApiGateway")
 
-    private val json = Json { ignoreUnknownKeys = true }
-    private val jsonWithByteArraySerializer = Json {
-        serializersModule = SerializersModule {
-            contextual(ByteArrayAsBase64Serializer)
-        }
-        ignoreUnknownKeys = true
-    }
-
-    suspend fun requestPreparedData(): Pair<String, PreparedData> {
-        val response = apiRequestService.get("user-identity/prepared-data")
-        return Pair(response, jsonWithByteArraySerializer.decodeFromString<PreparedData>(response))
+    suspend fun requestPreparedData(): PreparedData {
+        return apiRequestService.get("user-identity/prepared-data")
     }
 
     suspend fun createAndPublishNewUserProfile(
         nickName: String,
-        preparedDataAsJson: String
-    ): UserProfileResponse {
+        preparedData: PreparedData
+    ):  UserProfileResponse {
         val createUserIdentityRequest = CreateUserIdentityRequest(
             nickName,
             "",
             "",
-            preparedDataAsJson
+            preparedData
         )
-        val response =
-            apiRequestService.post("user-identity/user-identities", createUserIdentityRequest)
-        return json.decodeFromString(response)
+        return apiRequestService.post("user-identity/user-identities", createUserIdentityRequest)
     }
 
     suspend fun getUserIdentityIds(): List<String> {
-        val response = apiRequestService.get("user-identity/ids")
-        return json.decodeFromString(response)
+        return apiRequestService.get("user-identity/ids")
     }
 
-
     suspend fun getSelectedUserProfile(): UserProfile {
-        val response = apiRequestService.get("user-identity/selected/user-profile")
-        return jsonWithByteArraySerializer.decodeFromString<UserProfile>(response)
+        return apiRequestService.get("user-identity/selected/user-profile")
     }
 }
\ No newline at end of file

From 727e5ffe0121fee38fdf2165f68e9566fd12fa10 Mon Sep 17 00:00:00 2001
From: HenrikJannsen <boilingfrog@gmx.com>
Date: Thu, 21 Nov 2024 17:32:40 +0700
Subject: [PATCH 25/25] Adjust to API changes

---
 .../client/user_profile/UserProfileApiGateway.kt      | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
index fd9407e1..0d132d97 100644
--- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
+++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/user_profile/UserProfileApiGateway.kt
@@ -10,9 +10,10 @@ class UserProfileApiGateway(
     private val apiRequestService: ApiRequestService
 ) {
     private val log = Logger.withTag(this::class.simpleName ?: "UserProfileApiGateway")
-
+    private val basePath = "user-identities"
     suspend fun requestPreparedData(): PreparedData {
-        return apiRequestService.get("user-identity/prepared-data")
+
+        return apiRequestService.get("$basePath/prepared-data")
     }
 
     suspend fun createAndPublishNewUserProfile(
@@ -25,14 +26,14 @@ class UserProfileApiGateway(
             "",
             preparedData
         )
-        return apiRequestService.post("user-identity/user-identities", createUserIdentityRequest)
+        return apiRequestService.post(basePath, createUserIdentityRequest)
     }
 
     suspend fun getUserIdentityIds(): List<String> {
-        return apiRequestService.get("user-identity/ids")
+        return apiRequestService.get("$basePath/ids")
     }
 
     suspend fun getSelectedUserProfile(): UserProfile {
-        return apiRequestService.get("user-identity/selected/user-profile")
+        return apiRequestService.get("$basePath/selected/user-profile")
     }
 }
\ No newline at end of file