From 4847c1bb21e0dbdb83faf452c456f923744b818c Mon Sep 17 00:00:00 2001 From: "Z. Charles Dziura" Date: Fri, 8 Nov 2024 12:38:37 -0500 Subject: [PATCH] Create new user registration processes --- app/app/build.gradle.kts | 4 +- .../debtpirate/app/host/MainActivity.kt | 27 +--- .../auth/data/remote/endpoint/AuthEndpoint.kt | 13 -- .../data/remote/model/AuthLoginRequest.kt | 3 - .../data/remote/model/AuthLoginResponse.kt | 9 -- .../data/remote/repository/AuthRepository.kt | 8 -- .../debtpirate/auth/di/AuthDiModule.kt | 33 ----- .../debtpirate/auth/di/AuthScreensDiModule.kt | 38 ++++++ .../usecase/SubmitLoginCredentialsUseCase.kt | 24 ---- .../auth/presentation/login/LoginScreen.kt | 4 +- .../login/LoginScreenViewModel.kt | 11 +- .../register/RegistrationScreen.kt | 111 ++++++++++++++-- .../register/RegistrationScreenViewModel.kt | 125 +++++++++++++++++- ...SubmitAccountRegistrationRequestUseCase.kt | 26 ++++ .../usecase/SubmitLoginCredentialsUseCase.kt | 24 ++++ .../ValidateLoginCredentialsUseCase.kt | 2 +- .../ValidateNewAccountRegistrationUseCase.kt | 59 +++++++++ .../data/remote/endpoint/AuthEndpoint.kt | 13 ++ .../data/remote/endpoint/UserEndpoint.kt | 13 ++ .../remote/model/auth/AuthLoginPostRequest.kt | 3 + .../model/auth/AuthLoginPostResponse.kt | 9 ++ .../model/user/UserCreatePostRequest.kt | 3 + .../model/user/UserCreatePostResponse.kt | 12 ++ .../data/repository/AuthRepository.kt | 9 ++ .../data/repository/UserRepository.kt | 8 ++ .../di/repository/AuthRepositoryModule.kt | 21 +++ .../di/repository/UserRepositoryModule.kt | 21 +++ .../domain/adapter/OffsetDateTimeAdapter.kt | 37 ++++++ .../domain/repository/AuthRepositoryImpl.kt | 14 +- .../domain/repository/UserRepositoryImpl.kt | 36 +++++ .../usecase/pref/UpdateCurrentRouteUseCase.kt | 18 --- app/gradle/libs.versions.toml | 18 +-- 32 files changed, 581 insertions(+), 175 deletions(-) delete mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/endpoint/AuthEndpoint.kt delete mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/model/AuthLoginRequest.kt delete mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/model/AuthLoginResponse.kt delete mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/repository/AuthRepository.kt delete mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/di/AuthDiModule.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/di/AuthScreensDiModule.kt delete mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/SubmitLoginCredentialsUseCase.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/SubmitAccountRegistrationRequestUseCase.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/SubmitLoginCredentialsUseCase.kt rename app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/{domain => }/usecase/ValidateLoginCredentialsUseCase.kt (93%) create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/ValidateNewAccountRegistrationUseCase.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/endpoint/AuthEndpoint.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/endpoint/UserEndpoint.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginPostRequest.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginPostResponse.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/user/UserCreatePostRequest.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/user/UserCreatePostResponse.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/AuthRepository.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/UserRepository.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/AuthRepositoryModule.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/UserRepositoryModule.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/adapter/OffsetDateTimeAdapter.kt rename app/app/src/main/java/ing/bikeshedengineer/debtpirate/{auth => }/domain/repository/AuthRepositoryImpl.kt (62%) create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/UserRepositoryImpl.kt delete mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/pref/UpdateCurrentRouteUseCase.kt diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index da9ea33..da9020b 100644 --- a/app/app/build.gradle.kts +++ b/app/app/build.gradle.kts @@ -31,12 +31,12 @@ protobuf { android { namespace = "ing.bikeshedengineer.debtpirate" - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "ing.bikeshedengineer.debtpirate" minSdk = 33 - targetSdk = 34 + targetSdk = 35 versionCode = 1 versionName = "1.0" diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt index 8457e99..201819b 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt @@ -16,15 +16,11 @@ import androidx.navigation.compose.rememberNavController import dagger.hilt.android.AndroidEntryPoint import ing.bikeshedengineer.debtpirate.auth.presentation.login.LoginScreen 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.NavigationAction import ing.bikeshedengineer.debtpirate.navigation.Navigator import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @@ -33,33 +29,12 @@ class MainActivity : ComponentActivity() { @Inject lateinit var navigator: Navigator - @Inject - lateinit var updateCurrentRoute: UpdateCurrentRouteUseCase - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { 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 { NavHost(navController = navController, startDestination = Destination.AuthGraph) { navigation(startDestination = Destination.RegistrationScreen) { @@ -73,7 +48,7 @@ class MainActivity : ComponentActivity() { } @Composable -private fun ObserveAsEvents(flow: Flow, onEvent: (T) -> Unit) { +private fun _ObserveAsEvents(flow: Flow, onEvent: (T) -> Unit) { val lifecycleOwner = LocalLifecycleOwner.current LaunchedEffect(lifecycleOwner.lifecycle) { lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/endpoint/AuthEndpoint.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/endpoint/AuthEndpoint.kt deleted file mode 100644 index 4fc11ec..0000000 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/endpoint/AuthEndpoint.kt +++ /dev/null @@ -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> -} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/model/AuthLoginRequest.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/model/AuthLoginRequest.kt deleted file mode 100644 index 9e740a1..0000000 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/model/AuthLoginRequest.kt +++ /dev/null @@ -1,3 +0,0 @@ -package ing.bikeshedengineer.debtpirate.auth.data.remote.model - -data class AuthLoginRequest(val emailAddress: String, val password: String) diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/model/AuthLoginResponse.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/model/AuthLoginResponse.kt deleted file mode 100644 index 90a8755..0000000 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/model/AuthLoginResponse.kt +++ /dev/null @@ -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) diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/repository/AuthRepository.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/repository/AuthRepository.kt deleted file mode 100644 index 47b9ae4..0000000 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/repository/AuthRepository.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/di/AuthDiModule.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/di/AuthDiModule.kt deleted file mode 100644 index 38aa071..0000000 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/di/AuthDiModule.kt +++ /dev/null @@ -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() -} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/di/AuthScreensDiModule.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/di/AuthScreensDiModule.kt new file mode 100644 index 0000000..8ebba25 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/di/AuthScreensDiModule.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/SubmitLoginCredentialsUseCase.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/SubmitLoginCredentialsUseCase.kt deleted file mode 100644 index 0e8de2d..0000000 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/SubmitLoginCredentialsUseCase.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt index 45a1d64..ed26213 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt @@ -39,7 +39,6 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import androidx.navigation.NavController import ing.bikeshedengineer.debtpirate.R @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @@ -67,7 +66,7 @@ fun LoginScreen( LoginComponent( emailAddress = emailAddress, - onUpdateEmailAddress = viewModel::updateUsername, + onUpdateEmailAddress = viewModel::updateEmailAddress, password = password, onUpdatePassword = viewModel::updatePassword, submitLoginRequest = viewModel::submitLoginRequest @@ -128,6 +127,7 @@ private fun LoginComponent( }, singleLine = true, visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Password, imeAction = ImeAction.Send diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt index a2fb88a..922a17e 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt @@ -5,10 +5,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import ing.bikeshedengineer.debtpirate.PrefsDataStore -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.auth.usecase.LoginCredentialsValidationResult +import ing.bikeshedengineer.debtpirate.auth.usecase.SubmitLoginCredentialsUseCase +import ing.bikeshedengineer.debtpirate.auth.usecase.ValidateLoginCredentialsUseCase import ing.bikeshedengineer.debtpirate.domain.model.Token import ing.bikeshedengineer.debtpirate.navigation.Destination import ing.bikeshedengineer.debtpirate.navigation.Navigator @@ -50,8 +49,8 @@ class LoginScreenViewModel @Inject constructor( private val _password = MutableStateFlow("") val password = _password.asStateFlow() - fun updateUsername(username: String) { - _emailAddress.value = username + fun updateEmailAddress(emailAddress: String) { + _emailAddress.value = emailAddress } fun updatePassword(password: String) { diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreen.kt index 656ebc5..14665c5 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreen.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreen.kt @@ -26,6 +26,8 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color 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.KeyboardCapitalization 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.TextOverflow import androidx.compose.ui.text.withStyle @@ -59,7 +62,41 @@ fun RegistrationScreen(viewModel: RegistrationScreenViewModel = hiltViewModel Unit, modifier: Modifier = } @Composable -private fun RegistrationComponent(modifier: Modifier = Modifier) { +private fun RegistrationComponent( + emailAddress: State, + emailAddressError: State, + isInvalidEmailAddress: State, + updateEmailAddress: (String) -> Unit, + name: State, + nameError: State, + isInvalidName: State, + updateName: (String) -> Unit, + password: State, + passwordError: State, + isInvalidPassword: State, + updatePassword: (String) -> Unit, + confirmPassword: State, + confirmPasswordError: State, + isInvalidConfirmPassword: State, + updateConfirmPassword: (String) -> Unit, + registerNewAccount: (String, String, String, String) -> Unit, + modifier: Modifier = Modifier +) { Column(modifier = modifier.verticalScroll(rememberScrollState())) { OutlinedTextField( - value = "", + value = emailAddress.value, label = { 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)) }, singleLine = true, + isError = isInvalidEmailAddress.value, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.None, keyboardType = KeyboardType.Email, imeAction = ImeAction.Next ), - onValueChange = {}, + onValueChange = updateEmailAddress, modifier = Modifier.fillMaxWidth() ) OutlinedTextField( - value = "", + value = name.value, label = { 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)) }, singleLine = true, + isError = isInvalidName.value, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.Words, keyboardType = KeyboardType.Text, imeAction = ImeAction.Next ), - onValueChange = {}, + onValueChange = updateName, modifier = Modifier .fillMaxWidth() .padding(PaddingValues(top = 4.dp)) ) OutlinedTextField( - value = "", + value = password.value, label = { Text(text = stringResource(R.string.auth__password)) }, placeholder = { Text(text = stringResource(R.string.auth__password)) }, + supportingText = { Text(text = passwordError.value) }, leadingIcon = { Icon( Icons.Outlined.Password, @@ -136,21 +197,24 @@ private fun RegistrationComponent(modifier: Modifier = Modifier) { ) }, singleLine = true, + isError = isInvalidPassword.value, + visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.None, keyboardType = KeyboardType.Password, imeAction = ImeAction.Next ), - onValueChange = {}, + onValueChange = updatePassword, modifier = Modifier .fillMaxWidth() .padding(PaddingValues(top = 4.dp)) ) OutlinedTextField( - value = "", + value = confirmPassword.value, label = { Text(text = stringResource(R.string.auth__confirmPassword)) }, placeholder = { Text(text = stringResource(R.string.auth__confirmPassword)) }, + supportingText = { Text(text = confirmPasswordError.value) }, leadingIcon = { Icon( Icons.Outlined.Password, @@ -158,12 +222,14 @@ private fun RegistrationComponent(modifier: Modifier = Modifier) { ) }, singleLine = true, + isError = isInvalidConfirmPassword.value, + visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.None, keyboardType = KeyboardType.Password, imeAction = ImeAction.Done ), - onValueChange = {}, + onValueChange = updateConfirmPassword, modifier = Modifier .fillMaxWidth() .padding(PaddingValues(top = 4.dp)) @@ -190,14 +256,33 @@ private fun RegistrationComponent(modifier: Modifier = Modifier) { softWrap = true ) - RegisterButton() + RegisterButton( + emailAddress, + name, + password, + confirmPassword, + registerNewAccount + ) } } @Composable -private fun RegisterButton() { +private fun RegisterButton( + emailAddress: State, + name: State, + password: State, + confirmPassword: State, + registerNewAccount: (String, String, String, String) -> Unit +) { Button( - onClick = { }, + onClick = { + registerNewAccount( + emailAddress.value, + name.value, + password.value, + confirmPassword.value + ) + }, modifier = Modifier .fillMaxWidth() ) { diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreenViewModel.kt index 9bcabde..21d2ad6 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreenViewModel.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreenViewModel.kt @@ -3,17 +3,140 @@ package ing.bikeshedengineer.debtpirate.auth.presentation.register import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope 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 kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class RegistrationScreenViewModel @Inject constructor( - private val navigator: Navigator + private val navigator: Navigator, + private val validateNewAccount: ValidateNewAccountRegistrationUseCase, + private val submitAccountRegistrationRequest: SubmitAccountRegistrationRequestUseCase ) : ViewModel() { fun navigateUp() { viewModelScope.launch { 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() + } } \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/SubmitAccountRegistrationRequestUseCase.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/SubmitAccountRegistrationRequestUseCase.kt new file mode 100644 index 0000000..853ec9b --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/SubmitAccountRegistrationRequestUseCase.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/SubmitLoginCredentialsUseCase.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/SubmitLoginCredentialsUseCase.kt new file mode 100644 index 0000000..3c1058d --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/SubmitLoginCredentialsUseCase.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/ValidateLoginCredentialsUseCase.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/ValidateLoginCredentialsUseCase.kt similarity index 93% rename from app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/ValidateLoginCredentialsUseCase.kt rename to app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/ValidateLoginCredentialsUseCase.kt index 5e2e437..c28ca2b 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/ValidateLoginCredentialsUseCase.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/ValidateLoginCredentialsUseCase.kt @@ -1,4 +1,4 @@ -package ing.bikeshedengineer.debtpirate.auth.domain.usecase +package ing.bikeshedengineer.debtpirate.auth.usecase sealed class LoginCredentialsValidationResult { object EmptyCredentials : LoginCredentialsValidationResult() diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/ValidateNewAccountRegistrationUseCase.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/ValidateNewAccountRegistrationUseCase.kt new file mode 100644 index 0000000..ade178c --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/usecase/ValidateNewAccountRegistrationUseCase.kt @@ -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 { + var resultsList = mutableListOf() + 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() + } +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/endpoint/AuthEndpoint.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/endpoint/AuthEndpoint.kt new file mode 100644 index 0000000..88160ba --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/endpoint/AuthEndpoint.kt @@ -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> +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/endpoint/UserEndpoint.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/endpoint/UserEndpoint.kt new file mode 100644 index 0000000..cf44bfd --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/endpoint/UserEndpoint.kt @@ -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> +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginPostRequest.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginPostRequest.kt new file mode 100644 index 0000000..2fefde4 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginPostRequest.kt @@ -0,0 +1,3 @@ +package ing.bikeshedengineer.debtpirate.data.remote.model.auth + +data class AuthLoginPostRequest(val emailAddress: String, val password: String) diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginPostResponse.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginPostResponse.kt new file mode 100644 index 0000000..ff08552 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginPostResponse.kt @@ -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) \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/user/UserCreatePostRequest.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/user/UserCreatePostRequest.kt new file mode 100644 index 0000000..c0c014f --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/user/UserCreatePostRequest.kt @@ -0,0 +1,3 @@ +package ing.bikeshedengineer.debtpirate.data.remote.model.user + +data class UserCreatePostRequest(val email: String, val name: String, val password: String) diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/user/UserCreatePostResponse.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/user/UserCreatePostResponse.kt new file mode 100644 index 0000000..4322417 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/user/UserCreatePostResponse.kt @@ -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 +) \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/AuthRepository.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/AuthRepository.kt new file mode 100644 index 0000000..c747019 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/AuthRepository.kt @@ -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 +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/UserRepository.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/UserRepository.kt new file mode 100644 index 0000000..0d7e3c2 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/repository/UserRepository.kt @@ -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 +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/AuthRepositoryModule.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/AuthRepositoryModule.kt new file mode 100644 index 0000000..0a6b8cf --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/AuthRepositoryModule.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/UserRepositoryModule.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/UserRepositoryModule.kt new file mode 100644 index 0000000..4d47321 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/repository/UserRepositoryModule.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/adapter/OffsetDateTimeAdapter.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/adapter/OffsetDateTimeAdapter.kt new file mode 100644 index 0000000..93e337e --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/adapter/OffsetDateTimeAdapter.kt @@ -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() { + 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) + } + } +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/repository/AuthRepositoryImpl.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AuthRepositoryImpl.kt similarity index 62% rename from app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/repository/AuthRepositoryImpl.kt rename to app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AuthRepositoryImpl.kt index 93c217b..c85cde5 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/repository/AuthRepositoryImpl.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AuthRepositoryImpl.kt @@ -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.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.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.withContext import retrofit2.Retrofit @@ -14,9 +14,9 @@ import retrofit2.Retrofit class AuthRepositoryImpl(private val httpClient: Retrofit) : AuthRepository { 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) { - val response = authEndpoint.submitLoginRequest(credentials) + val response = authEndpoint.submitAuthLoginRequest(credentials) if (response.isSuccessful) { val body = response.body() diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/UserRepositoryImpl.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/UserRepositoryImpl.kt new file mode 100644 index 0000000..591e419 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/UserRepositoryImpl.kt @@ -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>() {}.type + val body = + gson.fromJson>(response.errorBody()!!.charStream(), errorType) + + throw Throwable(body!!.error!!) + } + } + } +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/pref/UpdateCurrentRouteUseCase.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/pref/UpdateCurrentRouteUseCase.kt deleted file mode 100644 index 6fbe03b..0000000 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/pref/UpdateCurrentRouteUseCase.kt +++ /dev/null @@ -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 -) { - suspend operator fun invoke(destination: Destination) { - store.updateData { data -> - data.toBuilder() - .setCurrentRoute(destination.toString()) - .build() - } - } -} \ No newline at end of file diff --git a/app/gradle/libs.versions.toml b/app/gradle/libs.versions.toml index 60d13be..9dbf424 100644 --- a/app/gradle/libs.versions.toml +++ b/app/gradle/libs.versions.toml @@ -1,23 +1,23 @@ [versions] activityCompose = "1.9.3" -agp = "8.7.1" +agp = "8.7.2" appcompat = "1.7.0" -composeBom = "2024.10.00" -coreKtx = "1.13.1" +composeBom = "2024.10.01" +coreKtx = "1.15.0" datastore = "1.1.1" espressoCore = "3.6.1" hilt = "2.51.1" -iconsExtended = "1.7.3" +iconsExtended = "1.7.5" junit = "4.13.2" junitVersion = "1.2.1" kotlin = "2.0.10" kotlinxSerializationJson = "1.7.1" -lifecycleRuntimeCompose = "2.8.6" -lifecycleRuntimeKtx = "2.8.6" -lifecycleViewModelKtx = "2.8.6" -lifecycleViewmodelCompose = "2.8.6" +lifecycleRuntimeCompose = "2.8.7" +lifecycleRuntimeKtx = "2.8.7" +lifecycleViewModelKtx = "2.8.7" +lifecycleViewmodelCompose = "2.8.7" material = "1.12.0" -material3 = "1.3.0" +material3 = "1.4.0-alpha03" navigation = "2.8.3" protobuf = "0.9.4" protoLite = "3.21.11"