Create new user registration processes
This commit is contained in:
parent
149ce13e3e
commit
4847c1bb21
32 changed files with 581 additions and 175 deletions
|
@ -31,12 +31,12 @@ protobuf {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "ing.bikeshedengineer.debtpirate"
|
namespace = "ing.bikeshedengineer.debtpirate"
|
||||||
compileSdk = 34
|
compileSdk = 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "ing.bikeshedengineer.debtpirate"
|
applicationId = "ing.bikeshedengineer.debtpirate"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 34
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
|
|
||||||
|
|
|
@ -16,15 +16,11 @@ import androidx.navigation.compose.rememberNavController
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import ing.bikeshedengineer.debtpirate.auth.presentation.login.LoginScreen
|
import ing.bikeshedengineer.debtpirate.auth.presentation.login.LoginScreen
|
||||||
import ing.bikeshedengineer.debtpirate.auth.presentation.register.RegistrationScreen
|
import ing.bikeshedengineer.debtpirate.auth.presentation.register.RegistrationScreen
|
||||||
import ing.bikeshedengineer.debtpirate.domain.usecase.pref.UpdateCurrentRouteUseCase
|
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.NavigationAction
|
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||||
import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme
|
import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -33,33 +29,12 @@ class MainActivity : ComponentActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var navigator: Navigator
|
lateinit var navigator: Navigator
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var updateCurrentRoute: UpdateCurrentRouteUseCase
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
ObserveAsEvents(navigator.navigationActions) { action ->
|
|
||||||
when (action) {
|
|
||||||
is NavigationAction.Navigate -> {
|
|
||||||
navController.navigate(action.destination) {
|
|
||||||
val scope = CoroutineScope(context = Dispatchers.IO)
|
|
||||||
scope.launch {
|
|
||||||
updateCurrentRoute(action.destination)
|
|
||||||
}
|
|
||||||
action.navOptions(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NavigationAction.NavigateUp -> {
|
|
||||||
navController.navigateUp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DebtPirateTheme {
|
DebtPirateTheme {
|
||||||
NavHost(navController = navController, startDestination = Destination.AuthGraph) {
|
NavHost(navController = navController, startDestination = Destination.AuthGraph) {
|
||||||
navigation<Destination.AuthGraph>(startDestination = Destination.RegistrationScreen) {
|
navigation<Destination.AuthGraph>(startDestination = Destination.RegistrationScreen) {
|
||||||
|
@ -73,7 +48,7 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun <T> ObserveAsEvents(flow: Flow<T>, onEvent: (T) -> Unit) {
|
private fun <T> _ObserveAsEvents(flow: Flow<T>, onEvent: (T) -> Unit) {
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
LaunchedEffect(lifecycleOwner.lifecycle) {
|
LaunchedEffect(lifecycleOwner.lifecycle) {
|
||||||
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
package ing.bikeshedengineer.debtpirate.auth.data.remote.endpoint
|
|
||||||
|
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
|
||||||
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest
|
|
||||||
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginResponse
|
|
||||||
import retrofit2.Response
|
|
||||||
import retrofit2.http.Body
|
|
||||||
import retrofit2.http.POST
|
|
||||||
|
|
||||||
interface AuthEndpoint {
|
|
||||||
@POST("auth/login")
|
|
||||||
suspend fun submitLoginRequest(@Body credentials: AuthLoginRequest): Response<ApiResponse<AuthLoginResponse>>
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
package ing.bikeshedengineer.debtpirate.auth.data.remote.model
|
|
||||||
|
|
||||||
data class AuthLoginRequest(val emailAddress: String, val password: String)
|
|
|
@ -1,9 +0,0 @@
|
||||||
package ing.bikeshedengineer.debtpirate.auth.data.remote.model
|
|
||||||
|
|
||||||
data class AuthLoginResponse(
|
|
||||||
val userId: Int,
|
|
||||||
val session: AuthLoginTokenData,
|
|
||||||
val auth: AuthLoginTokenData
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AuthLoginTokenData(val token: String, val expiresAt: String)
|
|
|
@ -1,8 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package ing.bikeshedengineer.debtpirate.auth.di
|
|
||||||
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.components.ViewModelComponent
|
|
||||||
import dagger.hilt.android.scopes.ViewModelScoped
|
|
||||||
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
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(ViewModelComponent::class)
|
|
||||||
object AuthDiModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@ViewModelScoped
|
|
||||||
fun provideAuthRepository(httpClient: Retrofit): AuthRepository {
|
|
||||||
return AuthRepositoryImpl(httpClient)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@ViewModelScoped
|
|
||||||
fun provideSubmitLoginCredentialsUseCase(authRepository: AuthRepository): SubmitLoginCredentialsUseCase {
|
|
||||||
return SubmitLoginCredentialsUseCase(authRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@ViewModelScoped
|
|
||||||
fun provideValidateLoginCredentialsUseCase() = ValidateLoginCredentialsUseCase()
|
|
||||||
}
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.auth.di
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.components.ViewModelComponent
|
||||||
|
import dagger.hilt.android.scopes.ViewModelScoped
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.usecase.SubmitAccountRegistrationRequestUseCase
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.usecase.SubmitLoginCredentialsUseCase
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.usecase.ValidateLoginCredentialsUseCase
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.usecase.ValidateNewAccountRegistrationUseCase
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.repository.AuthRepository
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.repository.UserRepository
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(ViewModelComponent::class)
|
||||||
|
object AuthScreensDiModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ViewModelScoped
|
||||||
|
fun provideSubmitLoginCredentialsUseCase(authRepository: AuthRepository): SubmitLoginCredentialsUseCase {
|
||||||
|
return SubmitLoginCredentialsUseCase(authRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ViewModelScoped
|
||||||
|
fun provideValidateLoginCredentialsUseCase() = ValidateLoginCredentialsUseCase()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ViewModelScoped
|
||||||
|
fun provideValidateNewAccountRegistrationUseCase() = ValidateNewAccountRegistrationUseCase()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ViewModelScoped
|
||||||
|
fun provideSubmitAccountRegistrationUseCase(userRepository: UserRepository): SubmitAccountRegistrationRequestUseCase {
|
||||||
|
return SubmitAccountRegistrationRequestUseCase(userRepository)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
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(emailAddress: String, password: String): AuthLoginResponse {
|
|
||||||
val credentials = AuthLoginRequest(emailAddress, 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -39,7 +39,6 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.navigation.NavController
|
|
||||||
import ing.bikeshedengineer.debtpirate.R
|
import ing.bikeshedengineer.debtpirate.R
|
||||||
|
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
|
@ -67,7 +66,7 @@ fun LoginScreen(
|
||||||
|
|
||||||
LoginComponent(
|
LoginComponent(
|
||||||
emailAddress = emailAddress,
|
emailAddress = emailAddress,
|
||||||
onUpdateEmailAddress = viewModel::updateUsername,
|
onUpdateEmailAddress = viewModel::updateEmailAddress,
|
||||||
password = password,
|
password = password,
|
||||||
onUpdatePassword = viewModel::updatePassword,
|
onUpdatePassword = viewModel::updatePassword,
|
||||||
submitLoginRequest = viewModel::submitLoginRequest
|
submitLoginRequest = viewModel::submitLoginRequest
|
||||||
|
@ -128,6 +127,7 @@ private fun LoginComponent(
|
||||||
},
|
},
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
|
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
keyboardType = KeyboardType.Password,
|
keyboardType = KeyboardType.Password,
|
||||||
imeAction = ImeAction.Send
|
imeAction = ImeAction.Send
|
||||||
|
|
|
@ -5,10 +5,9 @@ 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.auth.data.remote.repository.AuthRepository
|
import ing.bikeshedengineer.debtpirate.auth.usecase.LoginCredentialsValidationResult
|
||||||
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.LoginCredentialsValidationResult
|
import ing.bikeshedengineer.debtpirate.auth.usecase.SubmitLoginCredentialsUseCase
|
||||||
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.SubmitLoginCredentialsUseCase
|
import ing.bikeshedengineer.debtpirate.auth.usecase.ValidateLoginCredentialsUseCase
|
||||||
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.ValidateLoginCredentialsUseCase
|
|
||||||
import ing.bikeshedengineer.debtpirate.domain.model.Token
|
import ing.bikeshedengineer.debtpirate.domain.model.Token
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||||
|
@ -50,8 +49,8 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
private val _password = MutableStateFlow("")
|
private val _password = MutableStateFlow("")
|
||||||
val password = _password.asStateFlow()
|
val password = _password.asStateFlow()
|
||||||
|
|
||||||
fun updateUsername(username: String) {
|
fun updateEmailAddress(emailAddress: String) {
|
||||||
_emailAddress.value = username
|
_emailAddress.value = emailAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updatePassword(password: String) {
|
fun updatePassword(password: String) {
|
||||||
|
|
|
@ -26,6 +26,8 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
@ -35,6 +37,7 @@ import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
|
@ -59,7 +62,41 @@ fun RegistrationScreen(viewModel: RegistrationScreenViewModel = hiltViewModel<Re
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
RegistrationComponent()
|
val emailAddress = viewModel.emailAddress.collectAsState()
|
||||||
|
val emailAddressError = viewModel.emailAddressError.collectAsState()
|
||||||
|
val isInvalidEmailAddress = viewModel.isInvalidEmailAddress.collectAsState(false)
|
||||||
|
|
||||||
|
val name = viewModel.name.collectAsState()
|
||||||
|
val nameError = viewModel.nameError.collectAsState()
|
||||||
|
val isInvalidName = viewModel.isInvalidName.collectAsState(false)
|
||||||
|
|
||||||
|
val password = viewModel.password.collectAsState()
|
||||||
|
val passwordError = viewModel.passwordError.collectAsState()
|
||||||
|
val isInvalidPassword = viewModel.isInvalidPassword.collectAsState(false)
|
||||||
|
|
||||||
|
val confirmPassword = viewModel.confirmPassword.collectAsState()
|
||||||
|
val confirmPasswordError = viewModel.confirmPasswordError.collectAsState()
|
||||||
|
val isInvalidConfirmPassword = viewModel.isInvalidConfirmPassword.collectAsState(false)
|
||||||
|
|
||||||
|
RegistrationComponent(
|
||||||
|
emailAddress = emailAddress,
|
||||||
|
emailAddressError = emailAddressError,
|
||||||
|
isInvalidEmailAddress = isInvalidEmailAddress,
|
||||||
|
updateEmailAddress = viewModel::updateEmailAddress,
|
||||||
|
name = name,
|
||||||
|
nameError = nameError,
|
||||||
|
isInvalidName = isInvalidName,
|
||||||
|
updateName = viewModel::updateName,
|
||||||
|
password = password,
|
||||||
|
passwordError = passwordError,
|
||||||
|
isInvalidPassword = isInvalidPassword,
|
||||||
|
updatePassword = viewModel::updatePassword,
|
||||||
|
confirmPassword = confirmPassword,
|
||||||
|
confirmPasswordError = confirmPasswordError,
|
||||||
|
isInvalidConfirmPassword = isInvalidConfirmPassword,
|
||||||
|
updateConfirmPassword = viewModel::updateConfirmPassword,
|
||||||
|
registerNewAccount = viewModel::registerNewAccount
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,44 +128,68 @@ private fun RegistrationTopAppBar(onNavigateUp: () -> Unit, modifier: Modifier =
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RegistrationComponent(modifier: Modifier = Modifier) {
|
private fun RegistrationComponent(
|
||||||
|
emailAddress: State<String>,
|
||||||
|
emailAddressError: State<String>,
|
||||||
|
isInvalidEmailAddress: State<Boolean>,
|
||||||
|
updateEmailAddress: (String) -> Unit,
|
||||||
|
name: State<String>,
|
||||||
|
nameError: State<String>,
|
||||||
|
isInvalidName: State<Boolean>,
|
||||||
|
updateName: (String) -> Unit,
|
||||||
|
password: State<String>,
|
||||||
|
passwordError: State<String>,
|
||||||
|
isInvalidPassword: State<Boolean>,
|
||||||
|
updatePassword: (String) -> Unit,
|
||||||
|
confirmPassword: State<String>,
|
||||||
|
confirmPasswordError: State<String>,
|
||||||
|
isInvalidConfirmPassword: State<Boolean>,
|
||||||
|
updateConfirmPassword: (String) -> Unit,
|
||||||
|
registerNewAccount: (String, String, String, String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
Column(modifier = modifier.verticalScroll(rememberScrollState())) {
|
Column(modifier = modifier.verticalScroll(rememberScrollState())) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = "",
|
value = emailAddress.value,
|
||||||
label = { Text(text = stringResource(R.string.auth__email)) },
|
label = { Text(text = stringResource(R.string.auth__email)) },
|
||||||
placeholder = { Text(text = stringResource(R.string.auth__email)) },
|
placeholder = { Text(text = stringResource(R.string.auth__email)) },
|
||||||
|
supportingText = { Text(text = emailAddressError.value) },
|
||||||
leadingIcon = { Icon(Icons.Outlined.Mail, stringResource(R.string.auth__email)) },
|
leadingIcon = { Icon(Icons.Outlined.Mail, stringResource(R.string.auth__email)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
isError = isInvalidEmailAddress.value,
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.None,
|
capitalization = KeyboardCapitalization.None,
|
||||||
keyboardType = KeyboardType.Email,
|
keyboardType = KeyboardType.Email,
|
||||||
imeAction = ImeAction.Next
|
imeAction = ImeAction.Next
|
||||||
),
|
),
|
||||||
onValueChange = {},
|
onValueChange = updateEmailAddress,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = "",
|
value = name.value,
|
||||||
label = { Text(text = stringResource(R.string.auth__name)) },
|
label = { Text(text = stringResource(R.string.auth__name)) },
|
||||||
placeholder = { Text(text = stringResource(R.string.auth__name)) },
|
placeholder = { Text(text = stringResource(R.string.auth__name)) },
|
||||||
|
supportingText = { Text(text = nameError.value) },
|
||||||
leadingIcon = { Icon(Icons.Outlined.Person, stringResource(R.string.auth__name)) },
|
leadingIcon = { Icon(Icons.Outlined.Person, stringResource(R.string.auth__name)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
isError = isInvalidName.value,
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.Words,
|
capitalization = KeyboardCapitalization.Words,
|
||||||
keyboardType = KeyboardType.Text,
|
keyboardType = KeyboardType.Text,
|
||||||
imeAction = ImeAction.Next
|
imeAction = ImeAction.Next
|
||||||
),
|
),
|
||||||
onValueChange = {},
|
onValueChange = updateName,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(PaddingValues(top = 4.dp))
|
.padding(PaddingValues(top = 4.dp))
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = "",
|
value = password.value,
|
||||||
label = { Text(text = stringResource(R.string.auth__password)) },
|
label = { Text(text = stringResource(R.string.auth__password)) },
|
||||||
placeholder = { Text(text = stringResource(R.string.auth__password)) },
|
placeholder = { Text(text = stringResource(R.string.auth__password)) },
|
||||||
|
supportingText = { Text(text = passwordError.value) },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Outlined.Password,
|
Icons.Outlined.Password,
|
||||||
|
@ -136,21 +197,24 @@ private fun RegistrationComponent(modifier: Modifier = Modifier) {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
isError = isInvalidPassword.value,
|
||||||
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.None,
|
capitalization = KeyboardCapitalization.None,
|
||||||
keyboardType = KeyboardType.Password,
|
keyboardType = KeyboardType.Password,
|
||||||
imeAction = ImeAction.Next
|
imeAction = ImeAction.Next
|
||||||
),
|
),
|
||||||
onValueChange = {},
|
onValueChange = updatePassword,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(PaddingValues(top = 4.dp))
|
.padding(PaddingValues(top = 4.dp))
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = "",
|
value = confirmPassword.value,
|
||||||
label = { Text(text = stringResource(R.string.auth__confirmPassword)) },
|
label = { Text(text = stringResource(R.string.auth__confirmPassword)) },
|
||||||
placeholder = { Text(text = stringResource(R.string.auth__confirmPassword)) },
|
placeholder = { Text(text = stringResource(R.string.auth__confirmPassword)) },
|
||||||
|
supportingText = { Text(text = confirmPasswordError.value) },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Outlined.Password,
|
Icons.Outlined.Password,
|
||||||
|
@ -158,12 +222,14 @@ private fun RegistrationComponent(modifier: Modifier = Modifier) {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
isError = isInvalidConfirmPassword.value,
|
||||||
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.None,
|
capitalization = KeyboardCapitalization.None,
|
||||||
keyboardType = KeyboardType.Password,
|
keyboardType = KeyboardType.Password,
|
||||||
imeAction = ImeAction.Done
|
imeAction = ImeAction.Done
|
||||||
),
|
),
|
||||||
onValueChange = {},
|
onValueChange = updateConfirmPassword,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(PaddingValues(top = 4.dp))
|
.padding(PaddingValues(top = 4.dp))
|
||||||
|
@ -190,14 +256,33 @@ private fun RegistrationComponent(modifier: Modifier = Modifier) {
|
||||||
softWrap = true
|
softWrap = true
|
||||||
)
|
)
|
||||||
|
|
||||||
RegisterButton()
|
RegisterButton(
|
||||||
|
emailAddress,
|
||||||
|
name,
|
||||||
|
password,
|
||||||
|
confirmPassword,
|
||||||
|
registerNewAccount
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RegisterButton() {
|
private fun RegisterButton(
|
||||||
|
emailAddress: State<String>,
|
||||||
|
name: State<String>,
|
||||||
|
password: State<String>,
|
||||||
|
confirmPassword: State<String>,
|
||||||
|
registerNewAccount: (String, String, String, String) -> Unit
|
||||||
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = { },
|
onClick = {
|
||||||
|
registerNewAccount(
|
||||||
|
emailAddress.value,
|
||||||
|
name.value,
|
||||||
|
password.value,
|
||||||
|
confirmPassword.value
|
||||||
|
)
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -3,17 +3,140 @@ package ing.bikeshedengineer.debtpirate.auth.presentation.register
|
||||||
import androidx.lifecycle.ViewModel
|
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.auth.usecase.NewAccountRegistrationValidationResult
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.usecase.SubmitAccountRegistrationRequestUseCase
|
||||||
|
import ing.bikeshedengineer.debtpirate.auth.usecase.ValidateNewAccountRegistrationUseCase
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class RegistrationScreenViewModel @Inject constructor(
|
class RegistrationScreenViewModel @Inject constructor(
|
||||||
private val navigator: Navigator
|
private val navigator: Navigator,
|
||||||
|
private val validateNewAccount: ValidateNewAccountRegistrationUseCase,
|
||||||
|
private val submitAccountRegistrationRequest: SubmitAccountRegistrationRequestUseCase
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
fun navigateUp() {
|
fun navigateUp() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
navigator.navigateUp()
|
navigator.navigateUp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val _emailAddress = MutableStateFlow("")
|
||||||
|
val emailAddress = _emailAddress.asStateFlow()
|
||||||
|
|
||||||
|
private val _emailAddressError = MutableStateFlow("")
|
||||||
|
val emailAddressError = _emailAddressError.asStateFlow()
|
||||||
|
val isInvalidEmailAddress = _emailAddressError.asStateFlow().map { it.isNotEmpty() }
|
||||||
|
|
||||||
|
private val _name = MutableStateFlow("")
|
||||||
|
val name = _name.asStateFlow()
|
||||||
|
|
||||||
|
private val _nameError = MutableStateFlow("")
|
||||||
|
val nameError = _nameError.asStateFlow()
|
||||||
|
val isInvalidName = _nameError.asStateFlow().map { it.isNotEmpty() }
|
||||||
|
|
||||||
|
private val _password = MutableStateFlow("")
|
||||||
|
val password = _password.asStateFlow()
|
||||||
|
|
||||||
|
private val _passwordError = MutableStateFlow("")
|
||||||
|
val passwordError = _passwordError.asStateFlow()
|
||||||
|
val isInvalidPassword = _passwordError.asStateFlow().map { it.isNotEmpty() }
|
||||||
|
|
||||||
|
private val _confirmPassword = MutableStateFlow("")
|
||||||
|
val confirmPassword = _confirmPassword.asStateFlow()
|
||||||
|
|
||||||
|
private val _confirmPasswordError = MutableStateFlow("")
|
||||||
|
val confirmPasswordError = _confirmPasswordError.asStateFlow()
|
||||||
|
val isInvalidConfirmPassword = _confirmPasswordError.asStateFlow().map { it.isNotEmpty() }
|
||||||
|
|
||||||
|
fun updateEmailAddress(emailAddress: String) {
|
||||||
|
_emailAddress.value = emailAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateName(name: String) {
|
||||||
|
_name.value = name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updatePassword(password: String) {
|
||||||
|
_password.value = password
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateConfirmPassword(confirmPassword: String) {
|
||||||
|
_confirmPassword.value = confirmPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerNewAccount(
|
||||||
|
emailAddress: String,
|
||||||
|
name: String,
|
||||||
|
password: String,
|
||||||
|
confirmPassword: String
|
||||||
|
) {
|
||||||
|
val fieldsAreValid =
|
||||||
|
validateRegistrationFields(emailAddress, name, password, confirmPassword)
|
||||||
|
if (fieldsAreValid) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val result = submitAccountRegistrationRequest(emailAddress, name, confirmPassword)
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
// TODO...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateRegistrationFields(
|
||||||
|
emailAddress: String,
|
||||||
|
name: String,
|
||||||
|
password: String,
|
||||||
|
confirmPassword: String
|
||||||
|
): Boolean {
|
||||||
|
val validationResults = validateNewAccount(emailAddress, name, password, confirmPassword)
|
||||||
|
validationResults.forEach {
|
||||||
|
if (it == NewAccountRegistrationValidationResult.EmptyEmailAddressField) {
|
||||||
|
_emailAddressError.value = "Enter an email address"
|
||||||
|
} else if (it == NewAccountRegistrationValidationResult.InvalidEmailAddress) {
|
||||||
|
_emailAddressError.value = "Enter a valid email address"
|
||||||
|
} else if (it == NewAccountRegistrationValidationResult.ValidEmailAddress) {
|
||||||
|
_emailAddressError.value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it == NewAccountRegistrationValidationResult.EmptyNameField) {
|
||||||
|
_nameError.value = "Enter your name"
|
||||||
|
} else if (it == NewAccountRegistrationValidationResult.ValidName) {
|
||||||
|
_nameError.value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it == NewAccountRegistrationValidationResult.EmptyPasswordField) {
|
||||||
|
_passwordError.value = "Enter a password"
|
||||||
|
} else if (it == NewAccountRegistrationValidationResult.PasswordTooShort) {
|
||||||
|
_passwordError.value = "Password must be more than 8 characters"
|
||||||
|
} else if (it == NewAccountRegistrationValidationResult.ValidPassword) {
|
||||||
|
_passwordError.value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it == NewAccountRegistrationValidationResult.EmptyConfirmPasswordField) {
|
||||||
|
_confirmPasswordError.value = "Enter a password"
|
||||||
|
} else if (it == NewAccountRegistrationValidationResult.PasswordsDontMatch) {
|
||||||
|
_confirmPasswordError.value = "Passwords don't match"
|
||||||
|
} else if (it == NewAccountRegistrationValidationResult.ValidConfirmPassword) {
|
||||||
|
_confirmPasswordError.value = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validationResults.filter {
|
||||||
|
when (it) {
|
||||||
|
NewAccountRegistrationValidationResult.ValidEmailAddress,
|
||||||
|
NewAccountRegistrationValidationResult.ValidName,
|
||||||
|
NewAccountRegistrationValidationResult.ValidPassword,
|
||||||
|
NewAccountRegistrationValidationResult.ValidConfirmPassword
|
||||||
|
-> false
|
||||||
|
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}.isEmpty()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.auth.usecase
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostRequest
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostResponse
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.repository.UserRepository
|
||||||
|
|
||||||
|
class SubmitAccountRegistrationRequestUseCase(private val userRepository: UserRepository) {
|
||||||
|
suspend operator fun invoke(
|
||||||
|
emailAddress: String,
|
||||||
|
name: String,
|
||||||
|
password: String
|
||||||
|
): UserCreatePostResponse {
|
||||||
|
val request = UserCreatePostRequest(emailAddress, name, password)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val response = userRepository.submitCreateUserRequest(request)
|
||||||
|
Log.d("RegistrationScreen", "Account registration successful! $response")
|
||||||
|
return response
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
// TODO...
|
||||||
|
Log.e("RegistrationScreen", "$err")
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.auth.usecase
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostRequest
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostResponse
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.repository.AuthRepository
|
||||||
|
|
||||||
|
class SubmitLoginCredentialsUseCase(
|
||||||
|
private val authRepository: AuthRepository
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(emailAddress: String, password: String): AuthLoginPostResponse {
|
||||||
|
val credentials = AuthLoginPostRequest(emailAddress, password)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val response = authRepository.submitAuthLoginRequest(credentials)
|
||||||
|
Log.d("AuthScreen", "Login successful! $response")
|
||||||
|
return response
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
// TODO...
|
||||||
|
Log.e("AuthScreen", err.message!!)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package ing.bikeshedengineer.debtpirate.auth.domain.usecase
|
package ing.bikeshedengineer.debtpirate.auth.usecase
|
||||||
|
|
||||||
sealed class LoginCredentialsValidationResult {
|
sealed class LoginCredentialsValidationResult {
|
||||||
object EmptyCredentials : LoginCredentialsValidationResult()
|
object EmptyCredentials : LoginCredentialsValidationResult()
|
|
@ -0,0 +1,59 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.auth.usecase
|
||||||
|
|
||||||
|
import android.util.Patterns
|
||||||
|
|
||||||
|
enum class NewAccountRegistrationValidationResult {
|
||||||
|
EmptyEmailAddressField,
|
||||||
|
InvalidEmailAddress,
|
||||||
|
ValidEmailAddress,
|
||||||
|
EmptyNameField,
|
||||||
|
ValidName,
|
||||||
|
EmptyPasswordField,
|
||||||
|
PasswordTooShort,
|
||||||
|
ValidPassword,
|
||||||
|
EmptyConfirmPasswordField,
|
||||||
|
PasswordsDontMatch,
|
||||||
|
ValidConfirmPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValidateNewAccountRegistrationUseCase {
|
||||||
|
operator fun invoke(
|
||||||
|
emailAddress: String,
|
||||||
|
name: String,
|
||||||
|
password: String,
|
||||||
|
confirmPassword: String
|
||||||
|
): List<NewAccountRegistrationValidationResult> {
|
||||||
|
var resultsList = mutableListOf<NewAccountRegistrationValidationResult>()
|
||||||
|
if (emailAddress.isEmpty()) {
|
||||||
|
resultsList.add(NewAccountRegistrationValidationResult.EmptyEmailAddressField)
|
||||||
|
} else if (!Patterns.EMAIL_ADDRESS.matcher(emailAddress).matches()) {
|
||||||
|
resultsList.add(NewAccountRegistrationValidationResult.InvalidEmailAddress)
|
||||||
|
} else {
|
||||||
|
resultsList.add(NewAccountRegistrationValidationResult.ValidEmailAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
resultsList.add(NewAccountRegistrationValidationResult.EmptyNameField)
|
||||||
|
} else {
|
||||||
|
resultsList.add(NewAccountRegistrationValidationResult.ValidName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.isEmpty()) {
|
||||||
|
resultsList.add(NewAccountRegistrationValidationResult.EmptyPasswordField)
|
||||||
|
} else if (password.length <= 8) {
|
||||||
|
resultsList.add(NewAccountRegistrationValidationResult.PasswordTooShort)
|
||||||
|
} else {
|
||||||
|
resultsList.add(NewAccountRegistrationValidationResult.ValidPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirmPassword.isEmpty()) {
|
||||||
|
resultsList.add(NewAccountRegistrationValidationResult.EmptyConfirmPasswordField)
|
||||||
|
} else if (confirmPassword != password) {
|
||||||
|
resultsList.add(NewAccountRegistrationValidationResult.PasswordsDontMatch)
|
||||||
|
} else {
|
||||||
|
resultsList.add(NewAccountRegistrationValidationResult.ValidConfirmPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultsList.toList()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.data.remote.endpoint
|
||||||
|
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostRequest
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostResponse
|
||||||
|
import retrofit2.Response
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
interface AuthEndpoint {
|
||||||
|
@POST("auth/login")
|
||||||
|
suspend fun submitAuthLoginRequest(@Body credentials: AuthLoginPostRequest): Response<ApiResponse<AuthLoginPostResponse>>
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.data.remote.endpoint
|
||||||
|
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostRequest
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostResponse
|
||||||
|
import retrofit2.Response
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
interface UserEndpoint {
|
||||||
|
@POST("user")
|
||||||
|
suspend fun submitUserPostRequest(@Body request: UserCreatePostRequest): Response<ApiResponse<UserCreatePostResponse>>
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.data.remote.model.auth
|
||||||
|
|
||||||
|
data class AuthLoginPostRequest(val emailAddress: String, val password: String)
|
|
@ -0,0 +1,9 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.data.remote.model.auth
|
||||||
|
|
||||||
|
data class AuthLoginPostResponse(
|
||||||
|
val userId: Int,
|
||||||
|
val session: AuthLoginPostResponseTokenData,
|
||||||
|
val auth: AuthLoginPostResponseTokenData
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AuthLoginPostResponseTokenData(val token: String, val expiresAt: String)
|
|
@ -0,0 +1,3 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.data.remote.model.user
|
||||||
|
|
||||||
|
data class UserCreatePostRequest(val email: String, val name: String, val password: String)
|
|
@ -0,0 +1,12 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.data.remote.model.user
|
||||||
|
|
||||||
|
import com.google.gson.annotations.JsonAdapter
|
||||||
|
import ing.bikeshedengineer.debtpirate.domain.adapter.OffsetDateTimeAdapter
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
|
data class UserCreatePostResponse(
|
||||||
|
val userId: Int,
|
||||||
|
@JsonAdapter(OffsetDateTimeAdapter::class)
|
||||||
|
val expiresAt: OffsetDateTime,
|
||||||
|
val sessionToken: String? = null
|
||||||
|
)
|
|
@ -0,0 +1,9 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.data.repository
|
||||||
|
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostRequest
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostResponse
|
||||||
|
|
||||||
|
|
||||||
|
interface AuthRepository {
|
||||||
|
suspend fun submitAuthLoginRequest(credentials: AuthLoginPostRequest): AuthLoginPostResponse
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.data.repository
|
||||||
|
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostRequest
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostResponse
|
||||||
|
|
||||||
|
interface UserRepository {
|
||||||
|
suspend fun submitCreateUserRequest(request: UserCreatePostRequest): UserCreatePostResponse
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.di.repository
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.components.ViewModelComponent
|
||||||
|
import dagger.hilt.android.scopes.ViewModelScoped
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.repository.AuthRepository
|
||||||
|
import ing.bikeshedengineer.debtpirate.domain.repository.AuthRepositoryImpl
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(ViewModelComponent::class)
|
||||||
|
class AuthRepositoryModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ViewModelScoped
|
||||||
|
fun provideAuthRepository(httpClient: Retrofit): AuthRepository {
|
||||||
|
return AuthRepositoryImpl(httpClient)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.di.repository
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.components.ViewModelComponent
|
||||||
|
import dagger.hilt.android.scopes.ViewModelScoped
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.repository.UserRepository
|
||||||
|
import ing.bikeshedengineer.debtpirate.domain.repository.UserRepositoryImpl
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(ViewModelComponent::class)
|
||||||
|
class UserRepositoryModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ViewModelScoped
|
||||||
|
fun provideUserRepository(retrofit: Retrofit): UserRepository {
|
||||||
|
return UserRepositoryImpl(retrofit)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.domain.adapter
|
||||||
|
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonToken
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.time.format.DateTimeFormatterBuilder
|
||||||
|
|
||||||
|
class OffsetDateTimeAdapter : TypeAdapter<OffsetDateTime>() {
|
||||||
|
val rfc3339 = DateTimeFormatterBuilder()
|
||||||
|
.append(DateTimeFormatter.ISO_LOCAL_DATE)
|
||||||
|
.appendLiteral('T')
|
||||||
|
.append(DateTimeFormatter.ISO_LOCAL_TIME)
|
||||||
|
.optionalStart()
|
||||||
|
.appendOffset("+HHMM", "Z")
|
||||||
|
.optionalEnd()
|
||||||
|
.toFormatter()
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter?, value: OffsetDateTime?) {
|
||||||
|
if (value == null) {
|
||||||
|
out?.nullValue()
|
||||||
|
} else {
|
||||||
|
out?.value(rfc3339.format(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(reader: JsonReader?): OffsetDateTime? {
|
||||||
|
if (reader?.peek() == JsonToken.NULL) {
|
||||||
|
reader.nextNull()
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return OffsetDateTime.parse(reader?.nextString(), rfc3339)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
package ing.bikeshedengineer.debtpirate.auth.domain.repository
|
package ing.bikeshedengineer.debtpirate.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.auth.data.remote.endpoint.AuthEndpoint
|
|
||||||
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
|
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.endpoint.AuthEndpoint
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostRequest
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostResponse
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.repository.AuthRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
|
@ -14,9 +14,9 @@ import retrofit2.Retrofit
|
||||||
class AuthRepositoryImpl(private val httpClient: Retrofit) : AuthRepository {
|
class AuthRepositoryImpl(private val httpClient: Retrofit) : AuthRepository {
|
||||||
private val authEndpoint: AuthEndpoint = this.httpClient.create(AuthEndpoint::class.java)
|
private val authEndpoint: AuthEndpoint = this.httpClient.create(AuthEndpoint::class.java)
|
||||||
|
|
||||||
override suspend fun submitLoginRequest(credentials: AuthLoginRequest): AuthLoginResponse {
|
override suspend fun submitAuthLoginRequest(credentials: AuthLoginPostRequest): AuthLoginPostResponse {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
val response = authEndpoint.submitLoginRequest(credentials)
|
val response = authEndpoint.submitAuthLoginRequest(credentials)
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val body = response.body()
|
val body = response.body()
|
|
@ -0,0 +1,36 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.domain.repository
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.endpoint.UserEndpoint
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostRequest
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostResponse
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.repository.UserRepository
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
|
class UserRepositoryImpl(httpClient: Retrofit) : UserRepository {
|
||||||
|
private val userEndpoint: UserEndpoint = httpClient.create(UserEndpoint::class.java)
|
||||||
|
|
||||||
|
override suspend fun submitCreateUserRequest(request: UserCreatePostRequest): UserCreatePostResponse {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val response = userEndpoint.submitUserPostRequest(request)
|
||||||
|
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val body = response.body()
|
||||||
|
Log.d("Registration", "$body")
|
||||||
|
return@withContext body!!.data!!
|
||||||
|
} else {
|
||||||
|
val gson = Gson()
|
||||||
|
val errorType = object : TypeToken<ApiResponse<Unit>>() {}.type
|
||||||
|
val body =
|
||||||
|
gson.fromJson<ApiResponse<Unit>>(response.errorBody()!!.charStream(), errorType)
|
||||||
|
|
||||||
|
throw Throwable(body!!.error!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +0,0 @@
|
||||||
package ing.bikeshedengineer.debtpirate.domain.usecase.pref
|
|
||||||
|
|
||||||
import androidx.datastore.core.DataStore
|
|
||||||
import ing.bikeshedengineer.debtpirate.PrefsDataStore
|
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class UpdateCurrentRouteUseCase @Inject constructor(
|
|
||||||
private val store: DataStore<PrefsDataStore>
|
|
||||||
) {
|
|
||||||
suspend operator fun invoke(destination: Destination) {
|
|
||||||
store.updateData { data ->
|
|
||||||
data.toBuilder()
|
|
||||||
.setCurrentRoute(destination.toString())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +1,23 @@
|
||||||
[versions]
|
[versions]
|
||||||
activityCompose = "1.9.3"
|
activityCompose = "1.9.3"
|
||||||
agp = "8.7.1"
|
agp = "8.7.2"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
composeBom = "2024.10.00"
|
composeBom = "2024.10.01"
|
||||||
coreKtx = "1.13.1"
|
coreKtx = "1.15.0"
|
||||||
datastore = "1.1.1"
|
datastore = "1.1.1"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
hilt = "2.51.1"
|
hilt = "2.51.1"
|
||||||
iconsExtended = "1.7.3"
|
iconsExtended = "1.7.5"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.2.1"
|
junitVersion = "1.2.1"
|
||||||
kotlin = "2.0.10"
|
kotlin = "2.0.10"
|
||||||
kotlinxSerializationJson = "1.7.1"
|
kotlinxSerializationJson = "1.7.1"
|
||||||
lifecycleRuntimeCompose = "2.8.6"
|
lifecycleRuntimeCompose = "2.8.7"
|
||||||
lifecycleRuntimeKtx = "2.8.6"
|
lifecycleRuntimeKtx = "2.8.7"
|
||||||
lifecycleViewModelKtx = "2.8.6"
|
lifecycleViewModelKtx = "2.8.7"
|
||||||
lifecycleViewmodelCompose = "2.8.6"
|
lifecycleViewmodelCompose = "2.8.7"
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
material3 = "1.3.0"
|
material3 = "1.4.0-alpha03"
|
||||||
navigation = "2.8.3"
|
navigation = "2.8.3"
|
||||||
protobuf = "0.9.4"
|
protobuf = "0.9.4"
|
||||||
protoLite = "3.21.11"
|
protoLite = "3.21.11"
|
||||||
|
|
Loading…
Add table
Reference in a new issue