Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/main/kotlin/authentication/Auth.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ interface Auth {
private val client: SupabaseClient,
private val coroutineScope: CoroutineScope,
) {
fun create(): Auth = if (Secrets.USE_LOCAL_MOCKS) LocalAuth else RemoteAuth(client, coroutineScope)
private val remote by lazy { RemoteAuth(client, coroutineScope) }

fun create(): Auth = if (Secrets.USE_LOCAL_MOCKS) LocalAuth else remote

fun reader(): ReadAuth = if (Secrets.USE_LOCAL_MOCKS) LocalAuth else remote
}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/authentication/AuthState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package authentication

sealed interface AuthState {
data class SignIn(val user: String) : AuthState

data object InvalidCredentials : AuthState

data object Authenticating : AuthState

data object InitState : AuthState
}
5 changes: 3 additions & 2 deletions src/main/kotlin/authentication/Authentication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.koin.dsl.module

val authenticationModule =
module {
factory { Auth.Factory(get(), CoroutineScope(SupervisorJob() + get<RokyDispatchers>().io)) }
factory<Auth> { get<Auth.Factory>().create() }
single { Auth.Factory(get(), CoroutineScope(SupervisorJob() + get<RokyDispatchers>().io)) }
single<Auth> { get<Auth.Factory>().create() }
single<ReadAuth> { get<Auth.Factory>().reader() }
}
32 changes: 22 additions & 10 deletions src/main/kotlin/authentication/LocalAuth.kt
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
package authentication

import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlin.time.Duration.Companion.seconds

object LocalAuth : Auth {
private var isLoggedIn: Boolean = false
object LocalAuth : Auth, ReadAuth {
private val _state: MutableStateFlow<AuthState> = MutableStateFlow(AuthState.InitState)
val state: StateFlow<AuthState> = _state.asStateFlow()

override suspend fun login(
email: String,
password: String,
) {
delay(3.seconds)
println("local Auth login")
isLoggedIn = (email in validUsers && password == PASSWORD)
val state =
if (email in validUsers && password == PASSWORD) {
AuthState.SignIn(
email,
)
} else {
AuthState.InvalidCredentials
}
this._state.value = state
}

override suspend fun logout() {
println("local Auth logout")
isLoggedIn = false
_state.value = AuthState.InitState
}

override suspend fun isLoggedIn(): Boolean {
println("local local is logged in")
return isLoggedIn
}
override suspend fun isLoggedIn(): Boolean = _state.value is AuthState.SignIn

private val validUsers =
listOf("Robert", "Dunia", "Tom", "Max", "Casper", "Ed", "Kai", "Laura", "Niamh", "Sofia")
private const val PASSWORD = "ILoveRoky"

override fun state(): Flow<AuthState> = state

override fun getState(): AuthState = state.value
}
9 changes: 9 additions & 0 deletions src/main/kotlin/authentication/ReadAuth.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package authentication

import kotlinx.coroutines.flow.Flow

interface ReadAuth {
fun state(): Flow<AuthState>

fun getState(): AuthState
}
20 changes: 5 additions & 15 deletions src/main/kotlin/authentication/RemoteAuth.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,23 @@ import io.github.jan.supabase.auth.auth
import io.github.jan.supabase.auth.providers.builtin.Email
import io.github.jan.supabase.auth.status.SessionStatus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

class RemoteAuth(
private val client: SupabaseClient,
private val scope: CoroutineScope,
) : Auth {
) : Auth, ReadAuth {
private val _state: MutableStateFlow<AuthState> = MutableStateFlow(AuthState.InitState)
val state: StateFlow<AuthState> = _state.asStateFlow()

init {
scope.launch {
client.auth.sessionStatus.collect {
_state.value = it.toRokyState()
println("Current State: ${state.value}")
}
}
}
Expand All @@ -46,10 +45,7 @@ class RemoteAuth(
println("Remote Auth logout")
}

override suspend fun isLoggedIn(): Boolean {
println("Remote Auth is logged in")
return state.value is AuthState.SignIn
}
override suspend fun isLoggedIn(): Boolean = state.value is AuthState.SignIn

companion object {
fun SessionStatus.toRokyState(): AuthState =
Expand All @@ -62,14 +58,8 @@ class RemoteAuth(
}
}
}
}

sealed interface AuthState {
data class SignIn(val user: String) : AuthState

data object InvalidCredentials : AuthState

data object Authenticating : AuthState
override fun state(): Flow<AuthState> = state

data object InitState : AuthState
override fun getState(): AuthState = state.value
}
7 changes: 6 additions & 1 deletion src/main/kotlin/login/Login.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ val loginModules =
module {
scope<LoginWindow> {
scoped {
LoginPresenter(windowScope = get<LoginWindow>().windowScope, dispatchers = get(), login = get())
LoginPresenter(
windowScope = get<LoginWindow>().windowScope,
dispatchers = get(),
login = get(),
authState = get(),
)
}
scopedOf(::LoginUseCase)
}
Expand Down
25 changes: 24 additions & 1 deletion src/main/kotlin/login/LoginPresenter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package login

import arch.Presenter
import arch.RokyDispatchers
import authentication.AuthState
import authentication.AuthState.*
import authentication.ReadAuth
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
Expand All @@ -12,6 +15,7 @@ import login.LoginViewState.Idle
class LoginPresenter(
private val windowScope: CoroutineScope,
private val login: LoginUseCase,
private val authState: ReadAuth,
dispatchers: RokyDispatchers,
) : Presenter<LoginView>(dispatchers) {
private val state: MutableStateFlow<LoginViewState> = MutableStateFlow(Idle())
Expand All @@ -20,8 +24,21 @@ class LoginPresenter(
windowScope.launch(dispatchers.main) {
state.collect(::show)
}
windowScope.launch(dispatchers.io) {
authState.state().collect {
state.value = it.toLoginViewState()
}
}
}

private fun AuthState.toLoginViewState(): LoginViewState =
when (this) {
Authenticating -> Idle(status = AUTHENTICATING)
InitState -> Idle()
InvalidCredentials -> Idle(status = LOGIN_FAILURE)
is SignIn -> Idle(status = LOGIN_SUCCESS)
}

override fun onDetach(view: LoginView) {
TODO("Not yet implemented")
}
Expand All @@ -36,13 +53,19 @@ class LoginPresenter(
with(event) {
windowScope.launch(dispatchers.io) {
state.value = Authenticating(username, password, AUTHENTICATING)
state.value = login(this@with)
with(login(event)) {
if (!isSuccessful) {
state.value = Idle(status = message.orEmpty())
}
}
}
}

private fun show(state: LoginViewState) = withView { it.show(state) }

companion object {
const val AUTHENTICATING = "Authenticating…"
const val LOGIN_SUCCESS = "Username and password is OK."
const val LOGIN_FAILURE = "Could not authenticate."
}
}
32 changes: 19 additions & 13 deletions src/main/kotlin/login/LoginUseCase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,35 @@ package login

import authentication.Auth
import login.LoginEvent.Login
import login.LoginViewState.Idle

class LoginUseCase(
private val authenticator: Auth,
) {
suspend operator fun invoke(event: Login): LoginViewState =
suspend operator fun invoke(event: Login): LoginResult =
with(event) {
val status =
when {
username.isBlank() -> ERROR_USERNAME
password.isBlank() -> ERROR_PASSWORD
else -> {
authenticator.login(username, password)
if (authenticator.isLoggedIn()) LOGIN_SUCCESS else LOGIN_FAILURE
}
when {
username.isBlank() -> LoginResult.fail(ERROR_USERNAME)
password.isBlank() -> LoginResult.fail(ERROR_PASSWORD)
else -> {
authenticator.login(username, password)
LoginResult.ok()
}
return Idle(userName = event.username, password = "", status = status)
}
}

data class LoginResult(
val isSuccessful: Boolean,
val message: String?,
) {
companion object {
fun ok(): LoginResult = LoginResult(true, null)

fun fail(message: String): LoginResult = LoginResult(false, message)
}
}

companion object {
const val ERROR_USERNAME = "Username cannot be blank."
const val ERROR_PASSWORD = "Password cannot be blank."
const val LOGIN_SUCCESS = "Username and password is OK."
const val LOGIN_FAILURE = "Could not authenticate."
}
}
Loading