almost complete rework of ui up to module ui
This commit is contained in:
client
features
auth
client
common
client
content
binary
client
src
jsMain
kotlin
dev
inmo
postssystem
features
content
binary
client
src
commonMain
kotlin
dev
inmo
postssystem
features
content
client
text
client
src
jsMain
kotlin
dev
inmo
postssystem
features
content
text
files
client
roles
client
manager
common
src
commonMain
kotlin
dev
inmo
postssystem
features
roles
manager
common
users
client
gradle
publicators/simple/client
services/posts/client
build.gradle
src
commonMain
kotlin
dev
inmo
postssystem
services
jsMain
kotlin
dev
inmo
postssystem
services
posts
client
targets/telegram/loader/client/src/commonMain/kotlin/dev/inmo/postssystem/targets/telegram/loader/client
@ -2,6 +2,7 @@ plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
alias(libs.plugins.compose)
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
@ -11,8 +12,14 @@ kotlin {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":postssystem.features.common.client")
|
||||
api project(":postssystem.features.roles.client")
|
||||
api project(":postssystem.features.status.client")
|
||||
api project(":postssystem.features.auth.common")
|
||||
}
|
||||
}
|
||||
clientMain {
|
||||
dependencies {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthorizedFeaturesModules.kt
Normal file
39
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthorizedFeaturesModules.kt
Normal file
@ -0,0 +1,39 @@
|
||||
package dev.inmo.postssystem.features.auth.client
|
||||
|
||||
import dev.inmo.micro_utils.common.Either
|
||||
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
|
||||
import dev.inmo.postssystem.features.auth.common.AuthKey
|
||||
import dev.inmo.postssystem.features.auth.common.AuthTokenInfo
|
||||
import dev.inmo.postssystem.features.common.common.AdditionalModules
|
||||
import dev.inmo.postssystem.features.status.client.StatusFeatureClient
|
||||
import dev.inmo.postssystem.features.users.common.User
|
||||
import io.ktor.client.HttpClient
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.module
|
||||
|
||||
fun createAuthorizedFeaturesDIModule(
|
||||
serverUrl: String,
|
||||
initialAuthKey: Either<AuthKey, AuthTokenInfo>,
|
||||
onAuthKeyUpdated: suspend (AuthTokenInfo) -> Unit,
|
||||
onUserRetrieved: suspend (User?) -> Unit,
|
||||
onAuthKeyInvalidated: suspend () -> Unit
|
||||
): Module {
|
||||
return module {
|
||||
single(AuthorizedQualifiers.CredsQualifier) { initialAuthKey }
|
||||
single(AuthorizedQualifiers.ServerUrlQualifier) { serverUrl }
|
||||
|
||||
single (createdAtStart = true) {
|
||||
HttpClient {
|
||||
installClientAuthenticator(serverUrl, get(), get(AuthorizedQualifiers.CredsQualifier), onAuthKeyUpdated, onUserRetrieved, onAuthKeyInvalidated)
|
||||
}
|
||||
}
|
||||
single { UnifiedRequester(get(), get()) }
|
||||
single { StatusFeatureClient(get(AuthorizedQualifiers.ServerUrlQualifier), get()) }
|
||||
|
||||
AdditionalModules.Authorized.modules.forEach {
|
||||
with(it) {
|
||||
load()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthorizedModuleLoader.kt
Normal file
15
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthorizedModuleLoader.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package dev.inmo.postssystem.features.auth.client
|
||||
|
||||
import dev.inmo.postssystem.features.common.common.AdditionalModules
|
||||
import dev.inmo.postssystem.features.common.common.ModuleLoader
|
||||
import org.koin.core.module.Module
|
||||
|
||||
private val AuthorizedAdditionalModules = AdditionalModules()
|
||||
val AdditionalModules.Companion.Authorized: AdditionalModules
|
||||
get() = AuthorizedAdditionalModules
|
||||
|
||||
fun AuthorizedModuleLoader(loadingBlock: Module.() -> Unit): ModuleLoader.ByCallback {
|
||||
val newModuleLoader = ModuleLoader.ByCallback(loadingBlock)
|
||||
AdditionalModules.Authorized.addModule(newModuleLoader)
|
||||
return newModuleLoader
|
||||
}
|
8
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthorizedQualifiers.kt
Normal file
8
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthorizedQualifiers.kt
Normal file
@ -0,0 +1,8 @@
|
||||
package dev.inmo.postssystem.features.auth.client
|
||||
|
||||
import org.koin.core.qualifier.StringQualifier
|
||||
|
||||
object AuthorizedQualifiers {
|
||||
val CredsQualifier = StringQualifier("creds")
|
||||
val ServerUrlQualifier = StringQualifier("serverUrl")
|
||||
}
|
59
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/BehindAuthUIFSMHandler.kt
Normal file
59
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/BehindAuthUIFSMHandler.kt
Normal file
@ -0,0 +1,59 @@
|
||||
package dev.inmo.postssystem.features.auth.client
|
||||
|
||||
import dev.inmo.micro_utils.fsm.common.CheckableHandlerHolder
|
||||
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||
import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder
|
||||
import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
|
||||
import dev.inmo.postssystem.features.auth.client.ui.AuthUIFSMState
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMHandler
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.parameter.ParametersHolder
|
||||
import org.koin.core.qualifier.Qualifier
|
||||
import org.koin.core.scope.Scope
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
// Костыль, в JS на момент Пн дек 6 14:19:29 +06 2021 если использовать strictlyOn генерируются
|
||||
// некорректные безымянные классы (у них отсутствует метод handleState)
|
||||
class DefaultStateHandlerWrapper<T : UIFSMState>(
|
||||
private val klass: KClass<out UIFSMHandler<T>>,
|
||||
private val koin: Koin,
|
||||
private val stateKlass: KClass<T>,
|
||||
private val qualifier: Qualifier? = null,
|
||||
private val parameters: ((T) -> ParametersHolder)? = null
|
||||
) : CheckableHandlerHolder<UIFSMState, UIFSMState> {
|
||||
override suspend fun StatesMachine<in UIFSMState>.handleState(state: UIFSMState): UIFSMState? {
|
||||
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
|
||||
val state = state as T
|
||||
return runCatching {
|
||||
val authSettings = koin.get<AuthSettings>()
|
||||
authSettings.loadingJob.join()
|
||||
if (authSettings.authorizedDIModule.value == null) {
|
||||
error("Can't perform state $state: Auth module was not initialized")
|
||||
} else {
|
||||
koin.get<UIFSMHandler<T>>(klass, qualifier, parameters ?.let { { it(state) } }).run {
|
||||
handleState(state)
|
||||
}
|
||||
}
|
||||
}.getOrElse { e ->
|
||||
e.printStackTrace()
|
||||
AuthUIFSMState(state)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun checkHandleable(state: UIFSMState): Boolean = stateKlass.isInstance(state)
|
||||
}
|
||||
inline fun <reified T : UIFSMState> FSMBuilder<UIFSMState>.registerAfterAuthHandler(
|
||||
koin: Koin,
|
||||
klass: KClass<out UIFSMHandler<T>>,
|
||||
qualifier: Qualifier? = null,
|
||||
noinline parameters: ((T) -> ParametersHolder)? = null
|
||||
) = add(
|
||||
DefaultStateHandlerWrapper<T>(
|
||||
klass,
|
||||
koin,
|
||||
T::class,
|
||||
qualifier,
|
||||
parameters
|
||||
)
|
||||
)
|
27
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/ModuleLoader.kt
Normal file
27
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/ModuleLoader.kt
Normal file
@ -0,0 +1,27 @@
|
||||
package dev.inmo.postssystem.features.auth.client
|
||||
|
||||
import dev.inmo.micro_utils.common.Optional
|
||||
import dev.inmo.micro_utils.common.optional
|
||||
import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
|
||||
import dev.inmo.postssystem.features.auth.client.settings.DefaultAuthSettings
|
||||
import dev.inmo.postssystem.features.auth.client.ui.*
|
||||
import dev.inmo.postssystem.features.common.common.*
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMExceptionHandler
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
|
||||
|
||||
val defaultModuleLoader = DefaultModuleLoader {
|
||||
single<AuthSettings> { DefaultAuthSettings(get(DefaultQualifiers.SettingsQualifier), get(), getKoin(), get()) }
|
||||
|
||||
singleWithRandomQualifier {
|
||||
UIFSMExceptionHandler { currentState, exception ->
|
||||
if (exception is AuthUnavailableException) {
|
||||
Optional.presented(AuthUIFSMState(currentState))
|
||||
} else {
|
||||
Optional.absent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
factory<AuthUIModel> { DefaultAuthUIModel(get(), get()) }
|
||||
factory { AuthUIViewModel(get()) }
|
||||
}
|
18
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/settings/AuthSettings.kt
Normal file
18
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/settings/AuthSettings.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package dev.inmo.postssystem.features.auth.client.settings
|
||||
|
||||
import dev.inmo.postssystem.features.auth.client.ui.AuthUIError
|
||||
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
||||
import dev.inmo.postssystem.features.roles.common.Role
|
||||
import dev.inmo.postssystem.features.users.common.User
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.koin.core.module.Module
|
||||
|
||||
interface AuthSettings {
|
||||
val authorizedDIModule: StateFlow<Module?>
|
||||
val user: StateFlow<User?>
|
||||
val userRoles: StateFlow<List<Role>>
|
||||
val loadingJob: Job
|
||||
|
||||
suspend fun auth(serverUrl: String, creds: AuthCreds): AuthUIError?
|
||||
}
|
118
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/settings/DefaultAuthSettings.kt
Normal file
118
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/settings/DefaultAuthSettings.kt
Normal file
@ -0,0 +1,118 @@
|
||||
package dev.inmo.postssystem.features.auth.client.settings
|
||||
|
||||
import dev.inmo.postssystem.features.auth.client.AuthUnavailableException
|
||||
import dev.inmo.postssystem.features.auth.client.ui.*
|
||||
import dev.inmo.postssystem.features.auth.common.*
|
||||
import dev.inmo.postssystem.features.roles.common.Role
|
||||
import dev.inmo.postssystem.features.roles.common.RolesStorage
|
||||
import dev.inmo.postssystem.features.status.client.StatusFeatureClient
|
||||
import dev.inmo.postssystem.features.users.common.User
|
||||
import dev.inmo.micro_utils.common.Either
|
||||
import dev.inmo.micro_utils.common.either
|
||||
import dev.inmo.micro_utils.coroutines.plus
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.postssystem.features.auth.client.createAuthorizedFeaturesDIModule
|
||||
import dev.inmo.postssystem.features.common.common.DBDropper
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.module.Module
|
||||
|
||||
data class DefaultAuthSettings(
|
||||
private val repo: KeyValueRepo<String, Any>,
|
||||
private val scope: CoroutineScope,
|
||||
private val koin: Koin,
|
||||
private val dbDropper: DBDropper
|
||||
) : AuthSettings {
|
||||
private val _authorizedDIModule = MutableStateFlow<Module?>(null)
|
||||
override val authorizedDIModule: StateFlow<Module?> = _authorizedDIModule.asStateFlow()
|
||||
private val _user = MutableStateFlow<User?>(null)
|
||||
override val user: StateFlow<User?> = _user.asStateFlow()
|
||||
private val _userRoles = MutableStateFlow<List<Role>>(emptyList())
|
||||
override val userRoles: StateFlow<List<Role>> = _userRoles.asStateFlow()
|
||||
|
||||
private suspend fun getCurrentServerURL() = repo.get(SERVER_URL_FIELD) as? String
|
||||
private suspend fun getCurrentUsername() = repo.get(USERNAME_FIELD) as? String
|
||||
private suspend fun getCurrentToken() = repo.get(TOKEN_FIELD) as? AuthTokenInfo
|
||||
|
||||
override val loadingJob: Job = scope.launch {
|
||||
val serverUrl = getCurrentServerURL() ?: return@launch
|
||||
val token = getCurrentToken() ?: return@launch
|
||||
updateModule(serverUrl, token.either())
|
||||
}
|
||||
|
||||
val rolesUpdatingJob = (user + authorizedDIModule).subscribeSafelyWithoutExceptions(scope) {
|
||||
val user = user.value
|
||||
|
||||
if (user == null || authorizedDIModule.value == null) {
|
||||
_userRoles.value = emptyList()
|
||||
} else {
|
||||
_userRoles.value = koin.get<RolesStorage<Role>>().getRoles(user.id)
|
||||
}
|
||||
println(user)
|
||||
println(userRoles.value)
|
||||
}
|
||||
|
||||
override suspend fun auth(serverUrl: String, creds: AuthCreds): AuthUIError? {
|
||||
return runCatching {
|
||||
if (getCurrentServerURL() != serverUrl || getCurrentUsername() != creds.username.string) {
|
||||
dbDropper()
|
||||
}
|
||||
repo.set(SERVER_URL_FIELD, serverUrl)
|
||||
repo.set(USERNAME_FIELD, creds.username.string)
|
||||
repo.unset(TOKEN_FIELD)
|
||||
updateModule(serverUrl, creds.either())
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
private suspend fun updateModule(
|
||||
serverUrl: String,
|
||||
initialAuthKey: Either<AuthKey, AuthTokenInfo>,
|
||||
): AuthUIError? {
|
||||
val currentModule = authorizedDIModule.value
|
||||
val newModule = createAuthorizedFeaturesDIModule(
|
||||
serverUrl,
|
||||
initialAuthKey,
|
||||
{
|
||||
repo.set(TOKEN_FIELD, it)
|
||||
},
|
||||
{
|
||||
_user.value = it
|
||||
}
|
||||
) {
|
||||
repo.unset(SERVER_URL_FIELD, USERNAME_FIELD, TOKEN_FIELD)
|
||||
_authorizedDIModule.value = null
|
||||
_user.value = null
|
||||
throw AuthUnavailableException
|
||||
}
|
||||
currentModule ?.let { koin.unloadModules(listOf(currentModule)) }
|
||||
koin.loadModules(listOf(newModule))
|
||||
val statusFeature = koin.get<StatusFeatureClient>()
|
||||
|
||||
val serverAvailable = statusFeature.checkServerStatus()
|
||||
val authCorrect = serverAvailable && runCatching {
|
||||
statusFeature.checkServerStatusWithAuth()
|
||||
}.getOrElse { false }
|
||||
if (!serverAvailable && !authCorrect) {
|
||||
koin.unloadModules(listOf(newModule))
|
||||
currentModule ?.let { koin.loadModules(listOf(currentModule)) }
|
||||
}
|
||||
return when {
|
||||
!serverAvailable -> ServerUnavailableAuthUIError
|
||||
!authCorrect -> AuthIncorrectAuthUIError
|
||||
else -> {
|
||||
_authorizedDIModule.value = newModule
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SERVER_URL_FIELD = "AuthServerURL"
|
||||
private const val USERNAME_FIELD = "AuthUsername"
|
||||
private const val TOKEN_FIELD = "AuthToken"
|
||||
}
|
||||
}
|
14
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/ui/AuthUIFSMState.kt
Normal file
14
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/ui/AuthUIFSMState.kt
Normal file
@ -0,0 +1,14 @@
|
||||
package dev.inmo.postssystem.features.auth.client.ui
|
||||
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AuthUIFSMState(
|
||||
override val from: UIFSMState?,
|
||||
override val context: String = "main"
|
||||
) : UIFSMState {
|
||||
companion object {
|
||||
val default = AuthUIFSMState(null)
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package dev.inmo.postssystem.features.auth.client.ui
|
||||
|
||||
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
||||
import dev.inmo.postssystem.features.common.common.UIModel
|
||||
import dev.inmo.postssystem.features.common.common.ui.UIModel
|
||||
|
||||
interface AuthUIModel : UIModel<AuthUIState> {
|
||||
suspend fun initAuth(serverUrl: String, creds: AuthCreds)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dev.inmo.postssystem.features.auth.client.ui
|
||||
|
||||
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
||||
import dev.inmo.postssystem.features.common.common.UIViewModel
|
||||
import dev.inmo.postssystem.features.common.common.ui.UIViewModel
|
||||
import dev.inmo.postssystem.features.users.common.Username
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
|
35
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/ui/DefaultAuthUIModel.kt
Normal file
35
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/ui/DefaultAuthUIModel.kt
Normal file
@ -0,0 +1,35 @@
|
||||
package dev.inmo.postssystem.features.auth.client.ui
|
||||
|
||||
import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
|
||||
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
||||
import dev.inmo.postssystem.features.common.common.ui.AbstractUIModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class DefaultAuthUIModel(
|
||||
private val scope: CoroutineScope,
|
||||
private val authSettings: AuthSettings
|
||||
) : AbstractUIModel<AuthUIState>(LoadingAuthUIState), AuthUIModel {
|
||||
init {
|
||||
scope.launch {
|
||||
_currentState.value = LoadingAuthUIState
|
||||
authSettings.loadingJob.join()
|
||||
if (authSettings.authorizedDIModule.value == null) {
|
||||
_currentState.value = DefaultInitAuthUIState
|
||||
} else {
|
||||
_currentState.value = AuthorizedAuthUIState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun initAuth(serverUrl: String, creds: AuthCreds) {
|
||||
_currentState.value = LoadingAuthUIState
|
||||
val authError = authSettings.auth(serverUrl, creds)
|
||||
if (authError == null) {
|
||||
_currentState.value = AuthorizedAuthUIState
|
||||
} else {
|
||||
_currentState.value = InitAuthUIState(authError)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
119
features/auth/client/src/jsMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthView.kt
Normal file
119
features/auth/client/src/jsMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthView.kt
Normal file
@ -0,0 +1,119 @@
|
||||
package dev.inmo.postssystem.features.auth.client
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import dev.inmo.jsuikit.elements.*
|
||||
import dev.inmo.jsuikit.modifiers.*
|
||||
import dev.inmo.jsuikit.utils.Attrs
|
||||
import dev.inmo.micro_utils.coroutines.compose.renderComposableAndLinkToContextAndRoot
|
||||
import dev.inmo.postssystem.features.auth.client.ui.*
|
||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||
import dev.inmo.postssystem.features.common.common.*
|
||||
import dev.inmo.postssystem.features.common.common.ui.JSView
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.*
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.dom.*
|
||||
import org.jetbrains.compose.web.attributes.InputType
|
||||
import org.jetbrains.compose.web.dom.Text
|
||||
import org.w3c.dom.*
|
||||
|
||||
val loader = DefaultModuleLoader {
|
||||
factory { AuthView(get(), get(DefaultQualifiers.UIScopeQualifier), getAllDistinct()) }
|
||||
singleWithRandomQualifier<UIFSMHandler.Registrator> {
|
||||
UIFSMHandler.Registrator {
|
||||
strictlyOn(get<AuthView>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AuthView(
|
||||
private val viewModel: AuthUIViewModel,
|
||||
private val uiScope: CoroutineScope,
|
||||
defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
|
||||
) : JSView<AuthUIFSMState>(defaultExceptionsHandlers) {
|
||||
|
||||
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
|
||||
htmlElement: HTMLElement,
|
||||
state: AuthUIFSMState
|
||||
): UIFSMState? {
|
||||
val completion = CompletableDeferred<UIFSMState?>()
|
||||
|
||||
val usernameState = mutableStateOf("")
|
||||
val passwordState = mutableStateOf("")
|
||||
val disabled = mutableStateOf(true)
|
||||
val errorText = mutableStateOf<String?>(null)
|
||||
|
||||
val root = htmlElement.appendElement("div") {}
|
||||
val composition = renderComposableAndLinkToContextAndRoot(root) {
|
||||
val authBtnDisabled = usernameState.value.isBlank() || passwordState.value.isBlank()
|
||||
|
||||
Flex(
|
||||
UIKitFlex.Alignment.Horizontal.Center
|
||||
) {
|
||||
Card(
|
||||
Attrs(UIKitText.Alignment.Horizontal.Center),
|
||||
bodyAttrs = Attrs(UIKitWidth.Fixed.Medium),
|
||||
) {
|
||||
CardTitle { Text("Log in") }
|
||||
|
||||
if (errorText.value != null) {
|
||||
CardBadge(Attrs(UIKitLabel.Error)) {
|
||||
Text(errorText.value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
TextField(
|
||||
InputType.Text,
|
||||
usernameState,
|
||||
disabled,
|
||||
"Username",
|
||||
)
|
||||
TextField(
|
||||
InputType.Password,
|
||||
passwordState,
|
||||
disabled,
|
||||
"Password"
|
||||
)
|
||||
|
||||
DefaultButton("Authorise", UIKitButton.Type.Primary, UIKitMargin.Small, disabled = authBtnDisabled) {
|
||||
it.nativeEvent.preventDefault()
|
||||
val serverUrl = document.location ?.run { "$hostname:$port" }
|
||||
if (serverUrl != null) {
|
||||
uiScope.launchSafelyWithoutExceptions { viewModel.initAuth(serverUrl, usernameState.value, passwordState.value) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val viewJob = viewModel.currentState.subscribeSafelyWithoutExceptions(uiScope) {
|
||||
when (it) {
|
||||
is InitAuthUIState -> {
|
||||
disabled.value = false
|
||||
|
||||
errorText.value = when (it.showError) {
|
||||
ServerUnavailableAuthUIError -> "Server unavailable"
|
||||
AuthIncorrectAuthUIError -> {
|
||||
passwordState.value = ""
|
||||
"Username or password is incorrect"
|
||||
}
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
LoadingAuthUIState -> {
|
||||
disabled.value = true
|
||||
errorText.value = null
|
||||
}
|
||||
AuthorizedAuthUIState -> {
|
||||
completion.complete(state.from)
|
||||
}
|
||||
}
|
||||
}
|
||||
return completion.await().also {
|
||||
composition.dispose()
|
||||
viewJob.cancel()
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user