Extract login and credentials verification logic into usecases
This commit is contained in:
parent
511a36516b
commit
fac5a55251
20 changed files with 149 additions and 73 deletions
|
@ -8,8 +8,8 @@ import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import ing.bikeshedengineer.debtpirate.presentation.theme.DebtPirateTheme
|
import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme
|
||||||
import ing.bikeshedengineer.debtpirate.presentation.ui.auth.AuthScreen
|
import ing.bikeshedengineer.debtpirate.auth.presentation.AuthScreen
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -26,9 +26,7 @@ class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
DebtPirateTheme {
|
DebtPirateTheme {
|
||||||
NavHost(navController = navController, startDestination = AuthRoute) {
|
NavHost(navController = navController, startDestination = AuthRoute) {
|
||||||
composable<AuthRoute> {
|
composable<AuthRoute>() { AuthScreen() }
|
||||||
AuthScreen()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package ing.bikeshedengineer.debtpirate.data.remote.endpoint
|
package ing.bikeshedengineer.debtpirate.auth.data.remote.endpoint
|
||||||
|
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.model.ApiResponse
|
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginRequest
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginResponse
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginResponse
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
|
@ -1,3 +1,3 @@
|
||||||
package ing.bikeshedengineer.debtpirate.data.remote.model.auth
|
package ing.bikeshedengineer.debtpirate.auth.data.remote.model
|
||||||
|
|
||||||
data class AuthLoginRequest(val username: String, val password: String)
|
data class AuthLoginRequest(val username: String, val password: String)
|
|
@ -1,4 +1,4 @@
|
||||||
package ing.bikeshedengineer.debtpirate.data.remote.model.auth
|
package ing.bikeshedengineer.debtpirate.auth.data.remote.model
|
||||||
|
|
||||||
data class AuthLoginResponse(
|
data class AuthLoginResponse(
|
||||||
val userId: Int,
|
val userId: Int,
|
|
@ -0,0 +1,8 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.auth.data.remote.repository
|
||||||
|
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginResponse
|
||||||
|
|
||||||
|
interface AuthRepository {
|
||||||
|
suspend fun submitLoginRequest(credentials: AuthLoginRequest): AuthLoginResponse
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.auth.di
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepository
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.domain.repository.AuthRepositoryImpl
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.SubmitLoginCredentialsUseCase
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.ValidateLoginCredentialsUseCase
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
object AuthDiModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideAuthRepository(httpClient: Retrofit): AuthRepository {
|
||||||
|
return AuthRepositoryImpl(httpClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSubmitLoginCredentialsUseCase(authRepository: AuthRepository): SubmitLoginCredentialsUseCase {
|
||||||
|
return SubmitLoginCredentialsUseCase(authRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideValidateLoginCredentialsUseCase() = ValidateLoginCredentialsUseCase()
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
package ing.bikeshedengineer.debtpirate.domain.repository
|
package ing.bikeshedengineer.debtpirate.auth.domain.repository
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.endpoint.AuthEndpoint
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.endpoint.AuthEndpoint
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.model.ApiResponse
|
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginRequest
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginResponse
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginResponse
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.repository.AuthRepository
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
|
@ -0,0 +1,24 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.auth.domain.usecase
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginResponse
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepository
|
||||||
|
|
||||||
|
class SubmitLoginCredentialsUseCase(
|
||||||
|
private val authRepository: AuthRepository
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(username: String, password: String): AuthLoginResponse {
|
||||||
|
val credentials = AuthLoginRequest(username, password)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val response = authRepository.submitLoginRequest(credentials)
|
||||||
|
Log.d("AuthScreen", "Login successful! $response")
|
||||||
|
return response
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
// TODO...
|
||||||
|
Log.e("AuthScreen", err.message!!)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.auth.domain.usecase
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
sealed class LoginCredentialsValidationResult {
|
||||||
|
object EmptyCredentials : LoginCredentialsValidationResult()
|
||||||
|
object EmptyUsernameField : LoginCredentialsValidationResult()
|
||||||
|
object EmptyPasswordField : LoginCredentialsValidationResult()
|
||||||
|
object ValidCredentials : LoginCredentialsValidationResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValidateLoginCredentialsUseCase {
|
||||||
|
operator fun invoke(username: String, password: String): LoginCredentialsValidationResult {
|
||||||
|
return if (username.isEmpty() && password.isEmpty()) {
|
||||||
|
LoginCredentialsValidationResult.EmptyCredentials;
|
||||||
|
} else if (username.isEmpty() && password.isNotEmpty()) {
|
||||||
|
LoginCredentialsValidationResult.EmptyUsernameField;
|
||||||
|
} else if (username.isNotEmpty() && password.isEmpty()) {
|
||||||
|
LoginCredentialsValidationResult.EmptyPasswordField
|
||||||
|
} else {
|
||||||
|
LoginCredentialsValidationResult.ValidCredentials
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package ing.bikeshedengineer.debtpirate.presentation.ui.auth
|
package ing.bikeshedengineer.debtpirate.auth.presentation
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
|
@ -1,4 +1,4 @@
|
||||||
package ing.bikeshedengineer.debtpirate.presentation.ui.auth
|
package ing.bikeshedengineer.debtpirate.auth.presentation
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
|
@ -6,24 +6,41 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import ing.bikeshedengineer.debtpirate.PrefsDataStore
|
import ing.bikeshedengineer.debtpirate.PrefsDataStore
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginRequest
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.repository.AuthRepository
|
import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepository
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.LoginCredentialsValidationResult
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.SubmitLoginCredentialsUseCase
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.ValidateLoginCredentialsUseCase
|
||||||
import ing.bikeshedengineer.debtpirate.domain.model.Token
|
import ing.bikeshedengineer.debtpirate.domain.model.Token
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
enum class InvalidField {
|
||||||
|
Username,
|
||||||
|
Password
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class FormState {
|
||||||
|
class Pristine : FormState()
|
||||||
|
class Valid : FormState()
|
||||||
|
class Invalid(reason: InvalidReason) : FormState()
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class InvalidReason {
|
||||||
|
MissingUsernameValue,
|
||||||
|
MissingPasswordValue,
|
||||||
|
PasswordTooShort
|
||||||
|
}
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AuthScreenViewModel @Inject constructor(
|
class AuthScreenViewModel @Inject constructor(
|
||||||
private val authRepository: AuthRepository,
|
private val authRepository: AuthRepository,
|
||||||
private val prefsStore: DataStore<PrefsDataStore>
|
private val prefsStore: DataStore<PrefsDataStore>,
|
||||||
|
private val submitLoginCredentials: SubmitLoginCredentialsUseCase,
|
||||||
|
private val validateLoginCredentials: ValidateLoginCredentialsUseCase,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val tag: String
|
|
||||||
get() {
|
|
||||||
return javaClass.simpleName
|
|
||||||
}
|
|
||||||
|
|
||||||
// private val storeLoginData = StoreLoginDataUseCase(dataStore)
|
// private val storeLoginData = StoreLoginDataUseCase(dataStore)
|
||||||
|
|
||||||
private val _username = MutableStateFlow("")
|
private val _username = MutableStateFlow("")
|
||||||
|
@ -41,16 +58,21 @@ class AuthScreenViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun submitLoginRequest() {
|
fun submitLoginRequest() {
|
||||||
val credentials = AuthLoginRequest(this._username.value, this._password.value)
|
when (validateLoginCredentials(username.value, password.value)) {
|
||||||
viewModelScope.launch {
|
is LoginCredentialsValidationResult.ValidCredentials -> {
|
||||||
try {
|
viewModelScope.launch {
|
||||||
val response = authRepository.submitLoginRequest(credentials)
|
try {
|
||||||
Log.i(tag, "Login successful! $response")
|
val result = submitLoginCredentials(username.value, password.value)
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
|
// TODO...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
// TODO...
|
// TODO...
|
||||||
Log.e(tag, err.message!!)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeAuthData(userId: Int, sessionToken: Token, authToken: Token) {
|
fun storeAuthData(userId: Int, sessionToken: Token, authToken: Token) {
|
|
@ -1,3 +1,3 @@
|
||||||
package ing.bikeshedengineer.debtpirate.data.remote.model
|
package ing.bikeshedengineer.debtpirate.data.remote
|
||||||
|
|
||||||
data class ApiResponse<T>(val data: T?, val error: String?)
|
data class ApiResponse<T>(val data: T?, val error: String?)
|
|
@ -1,4 +0,0 @@
|
||||||
package ing.bikeshedengineer.debtpirate.data.remote.client
|
|
||||||
|
|
||||||
class AuthEndpointClient {
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package ing.bikeshedengineer.debtpirate.data.remote.repository
|
|
||||||
|
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginRequest
|
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginResponse
|
|
||||||
|
|
||||||
interface AuthRepository {
|
|
||||||
suspend fun submitLoginRequest(credentials: AuthLoginRequest): AuthLoginResponse
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ing.bikeshedengineer.debtpirate.di.modules
|
package ing.bikeshedengineer.debtpirate.di
|
||||||
|
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
|
@ -1,4 +1,4 @@
|
||||||
package ing.bikeshedengineer.debtpirate.di.modules
|
package ing.bikeshedengineer.debtpirate.di
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
|
@ -1,21 +0,0 @@
|
||||||
package ing.bikeshedengineer.debtpirate.di.modules
|
|
||||||
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.repository.AuthRepository
|
|
||||||
import ing.bikeshedengineer.debtpirate.domain.repository.AuthRepositoryImpl
|
|
||||||
import retrofit2.Retrofit
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
object AuthRepositoryModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideAuthRepository(httpClient: Retrofit): AuthRepository {
|
|
||||||
return AuthRepositoryImpl(httpClient)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ing.bikeshedengineer.debtpirate.presentation.theme
|
package ing.bikeshedengineer.debtpirate.theme
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ing.bikeshedengineer.debtpirate.presentation.theme
|
package ing.bikeshedengineer.debtpirate.theme
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
|
@ -1,4 +1,4 @@
|
||||||
package ing.bikeshedengineer.debtpirate.presentation.theme
|
package ing.bikeshedengineer.debtpirate.theme
|
||||||
|
|
||||||
import androidx.compose.material3.Typography
|
import androidx.compose.material3.Typography
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
Loading…
Add table
Reference in a new issue