From 177417c3db4304fdb13068d1ce477fa359689ff2 Mon Sep 17 00:00:00 2001
From: "michael.thiele" <thiele.michael2@gmail.com>
Date: Wed, 6 Sep 2023 14:45:01 +0200
Subject: [PATCH] merge with main

---
 build.gradle.kts                              |  1 +
 buildSrc/src/main/kotlin/Versions.kt          |  3 +-
 trixnity-messenger/build.gradle.kts           |  1 +
 .../initialsync/IosInitialSyncViewModel.kt    |  4 +-
 .../messenger/viewmodel/RootRouter.kt         | 20 ++++++
 .../messenger/viewmodel/RootViewModel.kt      | 11 +++
 .../connecting/AddMatrixAccountViewModel.kt   | 37 ++++++----
 .../connecting/PasswordLoginViewModel.kt      | 51 ++++++++------
 .../connecting/RegisterNewAccountViewModel.kt | 70 ++++++++++---------
 .../viewmodel/connecting/SSOLoginViewModel.kt | 45 +++++++-----
 .../viewmodel/settings/AppInfoViewModel.kt    |  2 -
 11 files changed, 156 insertions(+), 89 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 5547acb75..8bb51b50f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -14,6 +14,7 @@ buildscript {
 plugins {
     id("io.kotest.multiplatform") version Versions.kotest apply false
     id("com.google.devtools.ksp") version Versions.ksp apply false
+    id("org.jetbrains.dokka") version Versions.dokka
 }
 
 allprojects {
diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt
index 779912442..fcefb0a02 100644
--- a/buildSrc/src/main/kotlin/Versions.kt
+++ b/buildSrc/src/main/kotlin/Versions.kt
@@ -2,11 +2,12 @@ import org.gradle.api.JavaVersion
 import org.gradle.jvm.toolchain.JavaLanguageVersion
 
 object Versions {
-    const val trixnityMessenger = "1.0.4"
+    const val trixnityMessenger = "1.0.5"
 
     val kotlinJvmTarget = JavaVersion.VERSION_11
     const val trixnity = "3.10.3" // https://gitlab.com/trixnity/trixnity/-/releases
     const val kotlin = "1.8.22" // https://kotlinlang.org/
+    const val dokka = "1.8.20" // https://github.com/Kotlin/dokka/releases
     const val ksp = "1.8.22-1.0.11" // https://github.com/google/ksp/releases
     const val kotlinxCoroutines = "1.7.2" // https://github.com/Kotlin/kotlinx.coroutines/releases
     const val kotlinxSerialization = "1.5.1" // https://github.com/Kotlin/kotlinx.serialization/releases
diff --git a/trixnity-messenger/build.gradle.kts b/trixnity-messenger/build.gradle.kts
index d1440e295..04c9f7cb6 100644
--- a/trixnity-messenger/build.gradle.kts
+++ b/trixnity-messenger/build.gradle.kts
@@ -13,6 +13,7 @@ plugins {
 //    kotlin("native.cocoapods")
     id("com.google.devtools.ksp")
     `maven-publish`
+    id("org.jetbrains.dokka")
 }
 
 @OptIn(ExperimentalKotlinGradlePluginApi::class)
diff --git a/trixnity-messenger/src/appleMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/initialsync/IosInitialSyncViewModel.kt b/trixnity-messenger/src/appleMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/initialsync/IosInitialSyncViewModel.kt
index fd7c24d58..748585e70 100644
--- a/trixnity-messenger/src/appleMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/initialsync/IosInitialSyncViewModel.kt
+++ b/trixnity-messenger/src/appleMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/initialsync/IosInitialSyncViewModel.kt
@@ -1,11 +1,11 @@
 package de.connect2x.trixnity.messenger.viewmodel.initialsync
 
+import de.connect2x.trixnity.messenger.util.StateFlowClass
 import de.connect2x.trixnity.messenger.viewmodel.ViewModelContext
 import de.connect2x.trixnity.messenger.viewmodel.matrixClients
 import io.github.oshai.kotlinlogging.KotlinLogging
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 
 private val log = KotlinLogging.logger { }
@@ -40,7 +40,7 @@ class IosInitialSyncViewModel(
         job?.cancel()
     }
 
-    override val accountSyncStates: StateFlow<Map<String, AccountSync>>
+    override val accountSyncStates: StateFlowClass<Map<String, AccountSync>>
         get() = TODO("Not yet implemented")
 
     override fun cancel() {
diff --git a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/RootRouter.kt b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/RootRouter.kt
index d59c006a8..1af1dba62 100644
--- a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/RootRouter.kt
+++ b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/RootRouter.kt
@@ -8,6 +8,7 @@ import com.arkivanov.essenty.parcelable.Parcelize
 import com.benasher44.uuid.uuid4
 import de.connect2x.trixnity.messenger.*
 import de.connect2x.trixnity.messenger.util.*
+import de.connect2x.trixnity.messenger.viewmodel.RootRouter.RootWrapper
 import de.connect2x.trixnity.messenger.viewmodel.connecting.*
 import io.github.oshai.kotlinlogging.KotlinLogging
 import korlibs.io.async.launch
@@ -16,6 +17,25 @@ import org.koin.dsl.module
 
 private val log = KotlinLogging.logger { }
 
+/**
+ * root with these possible children:
+ * - [RootWrapper.MatrixClientInitialization]
+ * - [RootWrapper.MatrixClientLogout]
+ * - [RootWrapper.AddMatrixAccount]
+ * - [RootWrapper.PasswordLogin]
+ * - [RootWrapper.SSOLogin]
+ * - [RootWrapper.RegisterNewAccount]
+ * - [RootWrapper.StoreFailure]
+ * - [RootWrapper.Main]
+ *
+ * A typical flow for clients that have no login data locally is:
+ * show [RootWrapper.AddMatrixAccount] and choose a server. This determines the login methods. When
+ * [AddMatrixAccountViewModel.selectAddMatrixAccountMethod] is called with a
+ * [AddMatrixAccountViewModel.ServerDiscoveryState], the router switches to the corresponding child, e.g.,
+ * [RootWrapper.PasswordLogin].
+ *
+ * If the login is successful, the router switches to [RootWrapper.Main] and the messenger is shown.
+ */
 class RootRouter(
     private val viewModelContext: ViewModelContext,
     private val matrixClientService: MatrixClientService,
diff --git a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/RootViewModel.kt b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/RootViewModel.kt
index c25c9b9e5..5c6f22b26 100644
--- a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/RootViewModel.kt
+++ b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/RootViewModel.kt
@@ -84,4 +84,15 @@ open class RootViewModelImpl(
     override fun dragFileExit() {
         router.dragFileExit()
     }
+
+    // for iOS, since default parameters do not work there
+    companion object {
+        fun create(
+            componentContext: ComponentContext,
+            matrixClientService: MatrixClientService,
+            initialSyncOnceIsFinished: (Boolean) -> Unit,
+            koinApplication: KoinApplication
+        ): RootViewModel =
+            RootViewModelImpl(componentContext, matrixClientService, initialSyncOnceIsFinished, koinApplication)
+    }
 }
\ No newline at end of file
diff --git a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/AddMatrixAccountViewModel.kt b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/AddMatrixAccountViewModel.kt
index df879501d..2cbc02727 100644
--- a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/AddMatrixAccountViewModel.kt
+++ b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/AddMatrixAccountViewModel.kt
@@ -1,12 +1,17 @@
 package de.connect2x.trixnity.messenger.viewmodel.connecting
 
 import de.connect2x.trixnity.messenger.GetAccountNames
+import de.connect2x.trixnity.messenger.util.MutableStateFlowClass
+import de.connect2x.trixnity.messenger.util.StateFlowClass
+import de.connect2x.trixnity.messenger.util.asFlowClass
 import de.connect2x.trixnity.messenger.viewmodel.ViewModelContext
 import de.connect2x.trixnity.messenger.viewmodel.connecting.AddMatrixAccountViewModel.ServerDiscoveryState
 import de.connect2x.trixnity.messenger.viewmodel.i18n
 import io.github.oshai.kotlinlogging.KotlinLogging
 import io.ktor.client.*
 import io.ktor.util.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.flow.*
@@ -34,10 +39,10 @@ interface AddMatrixAccountViewModelFactory {
 }
 
 interface AddMatrixAccountViewModel {
-    val isFirstMatrixClient: StateFlow<Boolean?>
+    val isFirstMatrixClient: StateFlowClass<Boolean?>
 
-    val serverUrl: MutableStateFlow<String>
-    val serverDiscoveryState: StateFlow<ServerDiscoveryState>
+    val serverUrl: MutableStateFlowClass<String>
+    val serverDiscoveryState: StateFlowClass<ServerDiscoveryState>
 
     sealed interface ServerDiscoveryState {
         object None : ServerDiscoveryState
@@ -57,12 +62,13 @@ open class AddMatrixAccountViewModelImpl(
     private val onCancel: () -> Unit,
     private val httpClientFactory: (HttpClientConfig<*>.() -> Unit) -> HttpClient = { HttpClient(it) },
 ) : ViewModelContext by viewModelContext, AddMatrixAccountViewModel {
-    override val isFirstMatrixClient: StateFlow<Boolean?> =
+    override val isFirstMatrixClient: StateFlowClass<Boolean?> =
         flow { emit(get<GetAccountNames>()()) }
             .map { it.isEmpty() }
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+            .asFlowClass(coroutineScope)
 
-    final override val serverUrl = MutableStateFlow("")
+    final override val serverUrl = MutableStateFlow("").asFlowClass(coroutineScope)
 
     final override val serverDiscoveryState =
         serverUrl.debounce(1.seconds).transformLatest { serverUrl ->
@@ -127,11 +133,13 @@ open class AddMatrixAccountViewModelImpl(
                         }
                 }
             }
-        }.stateIn(
-            coroutineScope,
-            SharingStarted.WhileSubscribed(),
-            ServerDiscoveryState.None
-        )
+        }
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(),
+                ServerDiscoveryState.None
+            )
+            .asFlowClass(coroutineScope)
 
     override fun selectAddMatrixAccountMethod(addMatrixAccountMethod: AddMatrixAccountMethod) {
         onAddMatrixAccountMethod(addMatrixAccountMethod)
@@ -143,9 +151,12 @@ open class AddMatrixAccountViewModelImpl(
 }
 
 class PreviewAddMatrixAccountViewModel : AddMatrixAccountViewModel {
-    override val isFirstMatrixClient: StateFlow<Boolean?> = MutableStateFlow(true)
-    override val serverUrl: MutableStateFlow<String> = MutableStateFlow("")
-    override val serverDiscoveryState: StateFlow<ServerDiscoveryState> = MutableStateFlow(ServerDiscoveryState.None)
+    private val coroutineScope = CoroutineScope(Dispatchers.Default)
+    override val isFirstMatrixClient: MutableStateFlowClass<Boolean?> =
+        MutableStateFlow(true).asFlowClass(coroutineScope)
+    override val serverUrl: MutableStateFlowClass<String> = MutableStateFlow("").asFlowClass(coroutineScope)
+    override val serverDiscoveryState: MutableStateFlowClass<ServerDiscoveryState> =
+        MutableStateFlow(ServerDiscoveryState.None).asFlowClass(coroutineScope)
 
     override fun selectAddMatrixAccountMethod(addMatrixAccountMethod: AddMatrixAccountMethod) {
     }
diff --git a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/PasswordLoginViewModel.kt b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/PasswordLoginViewModel.kt
index 1e831394b..313a9a03f 100644
--- a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/PasswordLoginViewModel.kt
+++ b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/PasswordLoginViewModel.kt
@@ -2,10 +2,15 @@ package de.connect2x.trixnity.messenger.viewmodel.connecting
 
 import de.connect2x.trixnity.messenger.GetAccountNames
 import de.connect2x.trixnity.messenger.MatrixClientService
+import de.connect2x.trixnity.messenger.util.MutableStateFlowClass
+import de.connect2x.trixnity.messenger.util.StateFlowClass
+import de.connect2x.trixnity.messenger.util.asFlowClass
 import de.connect2x.trixnity.messenger.viewmodel.ViewModelContext
 import de.connect2x.trixnity.messenger.viewmodel.i18n
 import io.github.oshai.kotlinlogging.KotlinLogging
 import korlibs.io.async.launch
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.*
 import org.koin.core.component.get
 
@@ -31,16 +36,16 @@ interface PasswordLoginViewModelFactory {
 }
 
 interface PasswordLoginViewModel {
-    val isFirstMatrixClient: StateFlow<Boolean?>
+    val isFirstMatrixClient: StateFlowClass<Boolean?>
     val serverUrl: String
 
-    val canLogin: StateFlow<Boolean>
+    val canLogin: StateFlowClass<Boolean>
 
-    val accountName: MutableStateFlow<String>
-    val username: MutableStateFlow<String>
-    val password: MutableStateFlow<String>
+    val accountName: MutableStateFlowClass<String>
+    val username: MutableStateFlowClass<String>
+    val password: MutableStateFlowClass<String>
 
-    val addMatrixAccountState: StateFlow<AddMatrixAccountState>
+    val addMatrixAccountState: StateFlowClass<AddMatrixAccountState>
     fun tryLogin()
     fun back()
 }
@@ -55,17 +60,18 @@ open class PasswordLoginViewModelImpl(
 
     private val accountNames = channelFlow { send(get<GetAccountNames>()()) }
         .stateIn(coroutineScope, SharingStarted.Eagerly, null)
-    override val isFirstMatrixClient: StateFlow<Boolean?> = accountNames.map { it.isNullOrEmpty() }
+    override val isFirstMatrixClient: StateFlowClass<Boolean?> = accountNames.map { it.isNullOrEmpty() }
         .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+        .asFlowClass(coroutineScope)
 
-    override val accountName: MutableStateFlow<String> = MutableStateFlow(i18n.defaultAccountName())
-    final override val username: MutableStateFlow<String> = MutableStateFlow("")
-    final override val password: MutableStateFlow<String> = MutableStateFlow("")
+    override val accountName: MutableStateFlowClass<String> = MutableStateFlow(i18n.defaultAccountName()).asFlowClass(coroutineScope)
+    final override val username: MutableStateFlowClass<String> = MutableStateFlow("").asFlowClass(coroutineScope)
+    final override val password: MutableStateFlowClass<String> = MutableStateFlow("").asFlowClass(coroutineScope)
 
-    override val addMatrixAccountState: MutableStateFlow<AddMatrixAccountState> =
-        MutableStateFlow(AddMatrixAccountState.None)
+    override val addMatrixAccountState: MutableStateFlowClass<AddMatrixAccountState> =
+        MutableStateFlow(AddMatrixAccountState.None).asFlowClass(coroutineScope)
 
-    override val canLogin: StateFlow<Boolean> =
+    override val canLogin: StateFlowClass<Boolean> =
         combine(
             accountName,
             username,
@@ -77,7 +83,9 @@ open class PasswordLoginViewModelImpl(
                 addMatrixAccountState.value =
                     AddMatrixAccountState.Failure(i18n.accountAlreadyExistsLocally(accountName))
             accountAlreadyExists.not() && accountName.isNotBlank() && username.isNotBlank() && password.isNotBlank() && serverUrl.isNotBlank()
-        }.stateIn(coroutineScope, SharingStarted.Eagerly, false) // eagerly because value is used below
+        }
+            .stateIn(coroutineScope, SharingStarted.Eagerly, false) // eagerly because value is used below
+            .asFlowClass(coroutineScope)
 
     override fun tryLogin() {
         coroutineScope.launch {
@@ -104,14 +112,15 @@ open class PasswordLoginViewModelImpl(
 }
 
 class PreviewPasswordLoginViewModel : PasswordLoginViewModel {
+    private val coroutineScope = CoroutineScope(Dispatchers.Default)
     override val serverUrl: String = "https://timmy-messenger.de"
-    override val isFirstMatrixClient: StateFlow<Boolean?> = MutableStateFlow(false)
-    override val canLogin: StateFlow<Boolean> = MutableStateFlow(false)
-    override val accountName: MutableStateFlow<String> = MutableStateFlow("default")
-    override val username: MutableStateFlow<String> = MutableStateFlow("user")
-    override val password: MutableStateFlow<String> = MutableStateFlow("password")
-    override val addMatrixAccountState: StateFlow<AddMatrixAccountState> =
-        MutableStateFlow(AddMatrixAccountState.Failure("dino"))
+    override val isFirstMatrixClient: MutableStateFlowClass<Boolean?> = MutableStateFlow(false).asFlowClass(coroutineScope)
+    override val canLogin: MutableStateFlowClass<Boolean> = MutableStateFlow(false).asFlowClass(coroutineScope)
+    override val accountName: MutableStateFlowClass<String> = MutableStateFlow("default").asFlowClass(coroutineScope)
+    override val username: MutableStateFlowClass<String> = MutableStateFlow("user").asFlowClass(coroutineScope)
+    override val password: MutableStateFlowClass<String> = MutableStateFlow("password").asFlowClass(coroutineScope)
+    override val addMatrixAccountState: MutableStateFlowClass<AddMatrixAccountState> =
+        MutableStateFlow(AddMatrixAccountState.Failure("dino")).asFlowClass(coroutineScope)
 
     override fun tryLogin() {
     }
diff --git a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/RegisterNewAccountViewModel.kt b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/RegisterNewAccountViewModel.kt
index 1e67c19a0..60a6995ca 100644
--- a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/RegisterNewAccountViewModel.kt
+++ b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/RegisterNewAccountViewModel.kt
@@ -5,6 +5,7 @@ import de.connect2x.trixnity.messenger.MatrixClientService
 import de.connect2x.trixnity.messenger.deviceDisplayName
 import de.connect2x.trixnity.messenger.util.MutableStateFlowClass
 import de.connect2x.trixnity.messenger.util.StateFlowClass
+import de.connect2x.trixnity.messenger.util.asFlowClass
 import de.connect2x.trixnity.messenger.viewmodel.ViewModelContext
 import de.connect2x.trixnity.messenger.viewmodel.connecting.RegisterNewAccountViewModel.RegistrationState
 import de.connect2x.trixnity.messenger.viewmodel.i18n
@@ -13,6 +14,8 @@ import io.github.oshai.kotlinlogging.KotlinLogging
 import io.ktor.client.*
 import io.ktor.http.*
 import korlibs.io.async.launch
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.*
 import net.folivo.trixnity.clientserverapi.client.MatrixClientServerApiClientImpl
 import net.folivo.trixnity.clientserverapi.client.UIA
@@ -38,10 +41,10 @@ interface RegisterNewAccountViewModelFactory {
 }
 
 interface RegisterNewAccountViewModel {
-    val isFirstMatrixClient: StateFlow<Boolean?>
+    val isFirstMatrixClient: StateFlowClass<Boolean?>
 
-    val error: StateFlow<String?>
-    val registrationState: StateFlow<RegistrationState>
+    val error: StateFlowClass<String?>
+    val registrationState: StateFlowClass<RegistrationState>
 
     val registrationOptions: StateFlowClass<List<AuthenticationType>>
     val loadingRegistrationOptions: StateFlowClass<Boolean>
@@ -49,15 +52,15 @@ interface RegisterNewAccountViewModel {
 
     val serverUrl: String
 
-    val accountName: MutableStateFlow<String>
-    val username: MutableStateFlow<String>
-    val password: MutableStateFlow<String>
-    val displayName: MutableStateFlow<String?>
+    val accountName: MutableStateFlowClass<String>
+    val username: MutableStateFlowClass<String>
+    val password: MutableStateFlowClass<String>
+    val displayName: MutableStateFlowClass<String?>
 
     val registrationToken: MutableStateFlowClass<String>
 
-    val canRegisterNewUser: StateFlow<Boolean>
-    val addMatrixAccountState: StateFlow<AddMatrixAccountState>
+    val canRegisterNewUser: StateFlowClass<Boolean>
+    val addMatrixAccountState: StateFlowClass<AddMatrixAccountState>
 
     fun tryRegistration()
     fun back()
@@ -81,8 +84,9 @@ open class RegisterNewAccountViewModelImpl(
 
     private val accountNames = channelFlow { send(get<GetAccountNames>()()) }
         .stateIn(coroutineScope, SharingStarted.Eagerly, null)
-    override val isFirstMatrixClient: StateFlow<Boolean?> = accountNames.map { it.isNullOrEmpty() }
+    override val isFirstMatrixClient: StateFlowClass<Boolean?> = accountNames.map { it.isNullOrEmpty() }
         .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+        .asFlowClass(coroutineScope)
 
     override val error: MutableStateFlowClass<String?> = MutableStateFlow(null).asFlowClass(coroutineScope)
     override val registrationState: MutableStateFlowClass<RegistrationState> =
@@ -94,16 +98,16 @@ open class RegisterNewAccountViewModelImpl(
     override val selectedRegistration: MutableStateFlowClass<AuthenticationType?> =
         MutableStateFlow(null).asFlowClass(coroutineScope)
 
-    override val accountName: MutableStateFlow<String> = MutableStateFlow(i18n.defaultAccountName())
-    override val username: MutableStateFlow<String> = MutableStateFlow("")
-    override val displayName: MutableStateFlow<String?> = MutableStateFlow(null)
-    override val password: MutableStateFlow<String> = MutableStateFlow("")
+    override val accountName: MutableStateFlowClass<String> = MutableStateFlow(i18n.defaultAccountName()).asFlowClass(coroutineScope)
+    override val username: MutableStateFlowClass<String> = MutableStateFlow("").asFlowClass(coroutineScope)
+    override val displayName: MutableStateFlowClass<String?> = MutableStateFlow(null).asFlowClass(coroutineScope)
+    override val password: MutableStateFlowClass<String> = MutableStateFlow("").asFlowClass(coroutineScope)
 
-    override val registrationToken: MutableStateFlow<String> = MutableStateFlow("")
-    override val addMatrixAccountState: MutableStateFlow<AddMatrixAccountState> =
-        MutableStateFlow(AddMatrixAccountState.None)
+    override val registrationToken: MutableStateFlowClass<String> = MutableStateFlow("").asFlowClass(coroutineScope)
+    override val addMatrixAccountState: MutableStateFlowClass<AddMatrixAccountState> =
+        MutableStateFlow(AddMatrixAccountState.None).asFlowClass(coroutineScope)
 
-    override val canRegisterNewUser: StateFlow<Boolean> = combine(
+    override val canRegisterNewUser: StateFlowClass<Boolean> = combine(
         accountName, accountNames, username, password, selectedRegistration, registrationToken
     ) { accountName, existingAccountNames, username, password, selectedRegistration, registrationToken ->
         log.debug { "canRegisterNewUser: accountName=$accountName, existingAccountNames=$existingAccountNames, username=$username, selectedRegistration=$selectedRegistration, registrationToken=$registrationToken" }
@@ -114,7 +118,9 @@ open class RegisterNewAccountViewModelImpl(
             selectedRegistration == AuthenticationType.RegistrationToken && registrationToken.isBlank() -> false
             else -> true
         }
-    }.stateIn(coroutineScope, SharingStarted.Eagerly, false) // is used down below
+    }
+        .stateIn(coroutineScope, SharingStarted.Eagerly, false) // is used down below
+        .asFlowClass(coroutineScope)
 
     init {
         coroutineScope.launch {
@@ -315,20 +321,20 @@ class PreviewRegisterNewAccountViewModel : RegisterNewAccountViewModel {
             AuthenticationType.RegistrationToken,
             AuthenticationType.Password,
         )
-    )
-    override val loadingRegistrationOptions: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val selectedRegistration: MutableStateFlow<AuthenticationType?> =
-        MutableStateFlow(AuthenticationType.RegistrationToken)
-    override val isFirstMatrixClient: StateFlow<Boolean?> = MutableStateFlow(false)
-    override val accountName: MutableStateFlow<String> = MutableStateFlow("Standard")
+    ).asFlowClass(coroutineScope)
+    override val loadingRegistrationOptions: MutableStateFlowClass<Boolean> = MutableStateFlow(false).asFlowClass(coroutineScope)
+    override val selectedRegistration: MutableStateFlowClass<AuthenticationType?> =
+        MutableStateFlow(AuthenticationType.RegistrationToken).asFlowClass(coroutineScope)
+    override val isFirstMatrixClient: MutableStateFlowClass<Boolean?> = MutableStateFlow(false).asFlowClass(coroutineScope)
+    override val accountName: MutableStateFlowClass<String> = MutableStateFlow("Standard").asFlowClass(coroutineScope)
     override val serverUrl: String = "http://localhost:8008"
-    override val username: MutableStateFlow<String> = MutableStateFlow("user1")
-    override val password: MutableStateFlow<String> = MutableStateFlow("user1-password")
-    override val displayName: MutableStateFlow<String?> = MutableStateFlow(null)
-    override val registrationToken: MutableStateFlow<String> = MutableStateFlow("myRegistrationToken")
-    override val addMatrixAccountState: MutableStateFlow<AddMatrixAccountState> =
-        MutableStateFlow(AddMatrixAccountState.None)
-    override val canRegisterNewUser: MutableStateFlow<Boolean> = MutableStateFlow(true)
+    override val username: MutableStateFlowClass<String> = MutableStateFlow("user1").asFlowClass(coroutineScope)
+    override val password: MutableStateFlowClass<String> = MutableStateFlow("user1-password").asFlowClass(coroutineScope)
+    override val displayName: MutableStateFlowClass<String?> = MutableStateFlow(null).asFlowClass(coroutineScope)
+    override val registrationToken: MutableStateFlowClass<String> = MutableStateFlow("myRegistrationToken").asFlowClass(coroutineScope)
+    override val addMatrixAccountState: MutableStateFlowClass<AddMatrixAccountState> =
+        MutableStateFlow(AddMatrixAccountState.None).asFlowClass(coroutineScope)
+    override val canRegisterNewUser: MutableStateFlowClass<Boolean> = MutableStateFlow(true).asFlowClass(coroutineScope)
 
     override fun tryRegistration() {}
     override fun back() {}
diff --git a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/SSOLoginViewModel.kt b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/SSOLoginViewModel.kt
index 4f63345a5..8ac643955 100644
--- a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/SSOLoginViewModel.kt
+++ b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/connecting/SSOLoginViewModel.kt
@@ -3,13 +3,18 @@ package de.connect2x.trixnity.messenger.viewmodel.connecting
 import com.benasher44.uuid.uuid4
 import de.connect2x.trixnity.messenger.GetAccountNames
 import de.connect2x.trixnity.messenger.MatrixClientService
+import de.connect2x.trixnity.messenger.util.MutableStateFlowClass
+import de.connect2x.trixnity.messenger.util.StateFlowClass
 import de.connect2x.trixnity.messenger.util.UrlHandler
+import de.connect2x.trixnity.messenger.util.asFlowClass
 import de.connect2x.trixnity.messenger.viewmodel.ViewModelContext
 import de.connect2x.trixnity.messenger.viewmodel.i18n
 import de.connect2x.trixnity.messenger.viewmodel.settings.MessengerSettings
 import io.github.oshai.kotlinlogging.KotlinLogging
 import io.ktor.http.*
 import korlibs.io.async.launch
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.*
 import org.koin.core.component.get
 
@@ -39,15 +44,15 @@ interface SSOLoginViewModelFactory {
 }
 
 interface SSOLoginViewModel {
-    val isFirstMatrixClient: StateFlow<Boolean?>
+    val isFirstMatrixClient: StateFlowClass<Boolean?>
     val serverUrl: String
     val providerName: String
 
-    val canLogin: StateFlow<Boolean>
-    val addMatrixAccountState: StateFlow<AddMatrixAccountState>
+    val canLogin: StateFlowClass<Boolean>
+    val addMatrixAccountState: StateFlowClass<AddMatrixAccountState>
 
-    val accountName: MutableStateFlow<String>
-    val loginToken: MutableStateFlow<String>
+    val accountName: MutableStateFlowClass<String>
+    val loginToken: MutableStateFlowClass<String>
 
     val loginUrl: String
     fun tryLogin()
@@ -66,14 +71,15 @@ open class SSOLoginViewModelImpl(
 ) : ViewModelContext by viewModelContext, SSOLoginViewModel {
     private val accountNames = channelFlow { send(get<GetAccountNames>()()) }
         .stateIn(coroutineScope, SharingStarted.Eagerly, null)
-    override val isFirstMatrixClient: StateFlow<Boolean?> = accountNames.map { it.isNullOrEmpty() }
+    override val isFirstMatrixClient: StateFlowClass<Boolean?> = accountNames.map { it.isNullOrEmpty() }
         .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+        .asFlowClass(coroutineScope)
 
-    override val accountName: MutableStateFlow<String> = MutableStateFlow(i18n.defaultAccountName())
-    override val loginToken: MutableStateFlow<String> = MutableStateFlow("")
+    override val accountName: MutableStateFlowClass<String> = MutableStateFlow(i18n.defaultAccountName()).asFlowClass(coroutineScope)
+    override val loginToken: MutableStateFlowClass<String> = MutableStateFlow("").asFlowClass(coroutineScope)
 
-    override val addMatrixAccountState: MutableStateFlow<AddMatrixAccountState> =
-        MutableStateFlow(AddMatrixAccountState.None)
+    override val addMatrixAccountState: MutableStateFlowClass<AddMatrixAccountState> =
+        MutableStateFlow(AddMatrixAccountState.None).asFlowClass(coroutineScope)
     private val urlHandler = get<UrlHandler>()
     private val messengerSettings = get<MessengerSettings>()
 
@@ -100,7 +106,7 @@ open class SSOLoginViewModelImpl(
     override val loginUrl =
         Url("$serverUrl/_matrix/client/v3/login/sso/redirect/$providerId?redirectUrl=$redirectUrl").toString()
 
-    override val canLogin: StateFlow<Boolean> =
+    override val canLogin: StateFlowClass<Boolean> =
         combine(
             accountName,
             loginToken,
@@ -111,7 +117,9 @@ open class SSOLoginViewModelImpl(
                 addMatrixAccountState.value =
                     AddMatrixAccountState.Failure(i18n.accountAlreadyExistsLocally(accountName))
             accountAlreadyExists.not() && accountName.isNotBlank() && loginToken.isNotBlank() && serverUrl.isNotBlank()
-        }.stateIn(coroutineScope, SharingStarted.Eagerly, false) // eagerly because value is used below
+        }
+            .stateIn(coroutineScope, SharingStarted.Eagerly, false) // eagerly because value is used below
+            .asFlowClass(coroutineScope)
 
     override fun tryLogin() {
         coroutineScope.launch {
@@ -137,14 +145,15 @@ open class SSOLoginViewModelImpl(
 }
 
 class PreviewSSOLoginViewModel : SSOLoginViewModel {
+    private val coroutineScope = CoroutineScope(Dispatchers.Default)
     override val serverUrl: String = "https://timmy-messenger.de"
-    override val isFirstMatrixClient: StateFlow<Boolean?> = MutableStateFlow(false)
+    override val isFirstMatrixClient: MutableStateFlowClass<Boolean?> = MutableStateFlow(false).asFlowClass(coroutineScope)
     override val providerName: String = "Timmy"
-    override val canLogin: StateFlow<Boolean> = MutableStateFlow(false)
-    override val accountName: MutableStateFlow<String> = MutableStateFlow("default")
-    override val loginToken: MutableStateFlow<String> = MutableStateFlow("")
-    override val addMatrixAccountState: StateFlow<AddMatrixAccountState> =
-        MutableStateFlow(AddMatrixAccountState.Failure("dino"))
+    override val canLogin: MutableStateFlowClass<Boolean> = MutableStateFlow(false).asFlowClass(coroutineScope)
+    override val accountName: MutableStateFlowClass<String> = MutableStateFlow("default").asFlowClass(coroutineScope)
+    override val loginToken: MutableStateFlowClass<String> = MutableStateFlow("").asFlowClass(coroutineScope)
+    override val addMatrixAccountState: MutableStateFlowClass<AddMatrixAccountState> =
+        MutableStateFlow(AddMatrixAccountState.Failure("dino")).asFlowClass(coroutineScope)
 
     override val loginUrl: String =
         Url("$serverUrl/_matrix/client/v3/login/sso/redirect?redirectUrl=trixnity://sso").toString()
diff --git a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/settings/AppInfoViewModel.kt b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/settings/AppInfoViewModel.kt
index 306d8b0ba..9c742e037 100644
--- a/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/settings/AppInfoViewModel.kt
+++ b/trixnity-messenger/src/commonMain/kotlin/de/connect2x/trixnity/messenger/viewmodel/settings/AppInfoViewModel.kt
@@ -1,8 +1,6 @@
 package de.connect2x.trixnity.messenger.viewmodel.settings
 
 import com.arkivanov.essenty.backhandler.BackCallback
-import de.connect2x.trixnity.messenger.getLicenses
-import de.connect2x.trixnity.messenger.getVersion
 import de.connect2x.trixnity.messenger.util.MutableStateFlowClass
 import de.connect2x.trixnity.messenger.util.asFlowClass
 import de.connect2x.trixnity.messenger.viewmodel.ViewModelContext
-- 
GitLab