Store user credentials after registration
This commit is contained in:
parent
da1cebe02d
commit
a343f2e2a0
12 changed files with 209 additions and 87 deletions
|
@ -62,7 +62,7 @@ android {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix = ".d"
|
applicationIdSuffix = ".dev"
|
||||||
buildConfigField("String", "API_BASE_URL", "\"http://10.0.2.2:42069\"")
|
buildConfigField("String", "API_BASE_URL", "\"http://10.0.2.2:42069\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,8 @@ dependencies {
|
||||||
kapt(libs.hilt.kapt)
|
kapt(libs.hilt.kapt)
|
||||||
implementation(libs.hilt.compose)
|
implementation(libs.hilt.compose)
|
||||||
implementation(libs.google.fonts)
|
implementation(libs.google.fonts)
|
||||||
|
implementation(libs.androidx.credentials.core)
|
||||||
|
implementation(libs.androidx.credentials.compat)
|
||||||
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ing.bikeshedengineer.debtpirate.app.host
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
@ -28,10 +29,10 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
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
|
||||||
|
@ -40,13 +41,18 @@ 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 ing.bikeshedengineer.debtpirate.R
|
import ing.bikeshedengineer.debtpirate.domain.repository.AccountManager
|
||||||
|
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreen(
|
fun LoginScreen(
|
||||||
viewModel: LoginScreenViewModel = hiltViewModel<LoginScreenViewModel>()
|
viewModel: LoginScreenViewModel = hiltViewModel<LoginScreenViewModel>()
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current as ComponentActivity
|
||||||
|
val accountManager = remember {
|
||||||
|
AccountManager(context)
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
|
@ -67,13 +73,32 @@ fun LoginScreen(
|
||||||
|
|
||||||
LoginComponent(
|
LoginComponent(
|
||||||
emailAddress = emailAddress,
|
emailAddress = emailAddress,
|
||||||
onUpdateEmailAddress = viewModel::updateEmailAddress,
|
onUpdateEmailAddress = {
|
||||||
|
viewModel.updateState(
|
||||||
|
LoginScreenStateAction.UpdateEmailAddress(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
password = password,
|
password = password,
|
||||||
onUpdatePassword = viewModel::updatePassword,
|
onUpdatePassword = {
|
||||||
|
viewModel.updateState(
|
||||||
|
LoginScreenStateAction.UpdatePassword(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
submitLoginRequest = viewModel::submitLoginRequest
|
submitLoginRequest = viewModel::submitLoginRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
Separator(modifier = Modifier.padding(PaddingValues(top = 24.dp, bottom = 24.dp)))
|
Separator(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
PaddingValues(
|
||||||
|
top = 24.dp,
|
||||||
|
bottom = 24.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
RegisterButton(viewModel = viewModel)
|
RegisterButton(viewModel = viewModel)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||||
|
|
||||||
|
sealed interface LoginScreenStateAction {
|
||||||
|
data class UpdateEmailAddress(val emailAddress: String) : LoginScreenStateAction
|
||||||
|
data class UpdatePassword(val password: String) : LoginScreenStateAction
|
||||||
|
}
|
|
@ -49,12 +49,16 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
private val _password = MutableStateFlow("")
|
private val _password = MutableStateFlow("")
|
||||||
val password = _password.asStateFlow()
|
val password = _password.asStateFlow()
|
||||||
|
|
||||||
fun updateEmailAddress(emailAddress: String) {
|
fun updateState(action: LoginScreenStateAction) {
|
||||||
_emailAddress.value = emailAddress
|
when (action) {
|
||||||
}
|
is LoginScreenStateAction.UpdateEmailAddress -> {
|
||||||
|
_emailAddress.value = action.emailAddress
|
||||||
|
}
|
||||||
|
|
||||||
fun updatePassword(password: String) {
|
is LoginScreenStateAction.UpdatePassword -> {
|
||||||
_password.value = password
|
_password.value = action.password
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun submitLoginRequest() {
|
fun submitLoginRequest() {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register
|
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
@ -26,12 +28,15 @@ import androidx.compose.material3.TopAppBar
|
||||||
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.LaunchedEffect
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
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
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
@ -45,14 +50,41 @@ import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import ing.bikeshedengineer.debtpirate.R
|
import ing.bikeshedengineer.debtpirate.domain.model.AccountManagerResult
|
||||||
|
import ing.bikeshedengineer.debtpirate.domain.repository.AccountManager
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@Composable()
|
@Composable()
|
||||||
fun RegistrationScreen(viewModel: RegistrationScreenViewModel = hiltViewModel<RegistrationScreenViewModel>()) {
|
fun RegistrationScreen(
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
viewModel: RegistrationScreenViewModel = hiltViewModel<RegistrationScreenViewModel>()
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current as ComponentActivity
|
||||||
|
val accountManager = remember { AccountManager(context) }
|
||||||
|
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
LaunchedEffect(true) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.onRegistrationComplete.collect { credentials ->
|
||||||
|
val (emailAddress, password) = credentials
|
||||||
|
val result = accountManager.storeCredentials(emailAddress, password)
|
||||||
|
|
||||||
|
when (result) {
|
||||||
|
is AccountManagerResult.Unavailable -> {
|
||||||
|
viewModel.navigateUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d("RegistrationScreen", "$result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { RegistrationTopAppBar(onNavigateUp = viewModel::navigateUp) },
|
topBar = { RegistrationTopAppBar(onNavigateUp = viewModel::navigateUp) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -64,40 +96,8 @@ fun RegistrationScreen(viewModel: RegistrationScreenViewModel = hiltViewModel<Re
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
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(
|
RegistrationComponent(
|
||||||
emailAddress = emailAddress,
|
viewModel,
|
||||||
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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,25 +134,25 @@ private fun RegistrationTopAppBar(onNavigateUp: () -> Unit, modifier: Modifier =
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RegistrationComponent(
|
private fun RegistrationComponent(
|
||||||
emailAddress: State<String>,
|
viewModel: RegistrationScreenViewModel,
|
||||||
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
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
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)
|
||||||
|
|
||||||
Column(modifier = modifier.verticalScroll(rememberScrollState())) {
|
Column(modifier = modifier.verticalScroll(rememberScrollState())) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = emailAddress.value,
|
value = emailAddress.value,
|
||||||
|
@ -167,7 +167,7 @@ private fun RegistrationComponent(
|
||||||
keyboardType = KeyboardType.Email,
|
keyboardType = KeyboardType.Email,
|
||||||
imeAction = ImeAction.Next
|
imeAction = ImeAction.Next
|
||||||
),
|
),
|
||||||
onValueChange = updateEmailAddress,
|
onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdateEmailAddress(it)) },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -184,7 +184,7 @@ private fun RegistrationComponent(
|
||||||
keyboardType = KeyboardType.Text,
|
keyboardType = KeyboardType.Text,
|
||||||
imeAction = ImeAction.Next
|
imeAction = ImeAction.Next
|
||||||
),
|
),
|
||||||
onValueChange = updateName,
|
onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdateName(it)) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(PaddingValues(top = 4.dp))
|
.padding(PaddingValues(top = 4.dp))
|
||||||
|
@ -209,7 +209,7 @@ private fun RegistrationComponent(
|
||||||
keyboardType = KeyboardType.Password,
|
keyboardType = KeyboardType.Password,
|
||||||
imeAction = ImeAction.Next
|
imeAction = ImeAction.Next
|
||||||
),
|
),
|
||||||
onValueChange = updatePassword,
|
onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdatePassword(it)) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(PaddingValues(top = 4.dp))
|
.padding(PaddingValues(top = 4.dp))
|
||||||
|
@ -234,7 +234,7 @@ private fun RegistrationComponent(
|
||||||
keyboardType = KeyboardType.Password,
|
keyboardType = KeyboardType.Password,
|
||||||
imeAction = ImeAction.Done
|
imeAction = ImeAction.Done
|
||||||
),
|
),
|
||||||
onValueChange = updateConfirmPassword,
|
onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdateConfirmPassword(it)) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(PaddingValues(top = 4.dp))
|
.padding(PaddingValues(top = 4.dp))
|
||||||
|
@ -268,7 +268,7 @@ private fun RegistrationComponent(
|
||||||
name,
|
name,
|
||||||
password,
|
password,
|
||||||
confirmPassword,
|
confirmPassword,
|
||||||
registerNewAccount
|
registerNewAccount = viewModel::registerNewAccount
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register
|
||||||
|
|
||||||
|
sealed interface RegistrationScreenAction {
|
||||||
|
data class UpdateEmailAddress(val emailAddress: String) : RegistrationScreenAction
|
||||||
|
data class UpdateName(val name: String) : RegistrationScreenAction
|
||||||
|
data class UpdatePassword(val password: String) : RegistrationScreenAction
|
||||||
|
data class UpdateConfirmPassword(val confirmPassword: String) : RegistrationScreenAction
|
||||||
|
data object ResetFields : RegistrationScreenAction
|
||||||
|
data class RegisterNewUser(val emailAddress: String, val name: String, val password: String, val confirmPassword: String) : RegistrationScreenAction
|
||||||
|
}
|
|
@ -6,9 +6,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.NewAccountRegistrationValidationResult
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.NewAccountRegistrationValidationResult
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.SubmitAccountRegistrationRequestUseCase
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.SubmitAccountRegistrationRequestUseCase
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.ValidateNewAccountRegistrationUseCase
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.ValidateNewAccountRegistrationUseCase
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -18,7 +19,7 @@ import javax.inject.Inject
|
||||||
class RegistrationScreenViewModel @Inject constructor(
|
class RegistrationScreenViewModel @Inject constructor(
|
||||||
private val navigator: Navigator,
|
private val navigator: Navigator,
|
||||||
private val validateNewAccount: ValidateNewAccountRegistrationUseCase,
|
private val validateNewAccount: ValidateNewAccountRegistrationUseCase,
|
||||||
private val submitAccountRegistrationRequest: SubmitAccountRegistrationRequestUseCase
|
private val submitAccountRegistrationRequest: SubmitAccountRegistrationRequestUseCase,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
fun navigateUp() {
|
fun navigateUp() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
@ -54,20 +55,40 @@ class RegistrationScreenViewModel @Inject constructor(
|
||||||
val confirmPasswordError = _confirmPasswordError.asStateFlow()
|
val confirmPasswordError = _confirmPasswordError.asStateFlow()
|
||||||
val isInvalidConfirmPassword = _confirmPasswordError.asStateFlow().map { it.isNotEmpty() }
|
val isInvalidConfirmPassword = _confirmPasswordError.asStateFlow().map { it.isNotEmpty() }
|
||||||
|
|
||||||
fun updateEmailAddress(emailAddress: String) {
|
private val _onRegistrationComplete = MutableSharedFlow<Pair<String, String>>()
|
||||||
_emailAddress.value = emailAddress
|
val onRegistrationComplete = _onRegistrationComplete.asSharedFlow()
|
||||||
}
|
|
||||||
|
|
||||||
fun updateName(name: String) {
|
fun onAction(action: RegistrationScreenAction) {
|
||||||
_name.value = name
|
when (action) {
|
||||||
}
|
is RegistrationScreenAction.UpdateEmailAddress -> {
|
||||||
|
_emailAddress.value = action.emailAddress
|
||||||
|
}
|
||||||
|
|
||||||
fun updatePassword(password: String) {
|
is RegistrationScreenAction.UpdateName -> {
|
||||||
_password.value = password
|
_name.value = action.name
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateConfirmPassword(confirmPassword: String) {
|
is RegistrationScreenAction.UpdatePassword -> {
|
||||||
_confirmPassword.value = confirmPassword
|
_password.value = action.password
|
||||||
|
}
|
||||||
|
|
||||||
|
is RegistrationScreenAction.UpdateConfirmPassword -> {
|
||||||
|
_confirmPassword.value = action.confirmPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
is RegistrationScreenAction.ResetFields -> {
|
||||||
|
resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
is RegistrationScreenAction.RegisterNewUser -> {
|
||||||
|
registerNewAccount(
|
||||||
|
action.emailAddress,
|
||||||
|
action.name,
|
||||||
|
action.password,
|
||||||
|
action.confirmPassword
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun registerNewAccount(
|
fun registerNewAccount(
|
||||||
|
@ -81,10 +102,12 @@ class RegistrationScreenViewModel @Inject constructor(
|
||||||
if (fieldsAreValid) {
|
if (fieldsAreValid) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
|
// TODO: Store the registration result data in the store
|
||||||
val result =
|
val result =
|
||||||
submitAccountRegistrationRequest(emailAddress, name, confirmPassword)
|
submitAccountRegistrationRequest(emailAddress, name, confirmPassword)
|
||||||
|
|
||||||
navigator.navigate(Destination.LoginScreen)
|
_onRegistrationComplete.emit(Pair(emailAddress, confirmPassword))
|
||||||
|
|
||||||
resetFields()
|
resetFields()
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
// TODO...
|
// TODO...
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.domain.model
|
||||||
|
|
||||||
|
sealed interface AccountManagerResult {
|
||||||
|
data class Success(val username: String) : AccountManagerResult
|
||||||
|
data object Unavailable : AccountManagerResult
|
||||||
|
data object Canceled : AccountManagerResult
|
||||||
|
data object Failure : AccountManagerResult
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.domain.repository
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.credentials.CreatePasswordRequest
|
||||||
|
import androidx.credentials.CredentialManager
|
||||||
|
import androidx.credentials.exceptions.CreateCredentialCancellationException
|
||||||
|
import androidx.credentials.exceptions.CreateCredentialException
|
||||||
|
import androidx.credentials.exceptions.CreateCredentialNoCreateOptionException
|
||||||
|
import ing.bikeshedengineer.debtpirate.domain.model.AccountManagerResult
|
||||||
|
|
||||||
|
class AccountManager(private val context: Activity) {
|
||||||
|
private val credentialManager = CredentialManager.create(context)
|
||||||
|
|
||||||
|
suspend fun storeCredentials(
|
||||||
|
emailAddress: String,
|
||||||
|
password: String
|
||||||
|
): AccountManagerResult {
|
||||||
|
return try {
|
||||||
|
credentialManager.createCredential(
|
||||||
|
context, request = CreatePasswordRequest(
|
||||||
|
id = emailAddress,
|
||||||
|
password
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
AccountManagerResult.Success(emailAddress)
|
||||||
|
} catch (err: CreateCredentialNoCreateOptionException) {
|
||||||
|
Log.w(
|
||||||
|
"DebtPirate::AccountManager",
|
||||||
|
"Cannot store credentials; a Google account isn't associated with this device"
|
||||||
|
)
|
||||||
|
AccountManagerResult.Unavailable
|
||||||
|
} catch (err: CreateCredentialCancellationException) {
|
||||||
|
err.printStackTrace()
|
||||||
|
AccountManagerResult.Canceled
|
||||||
|
} catch (err: CreateCredentialException) {
|
||||||
|
err.printStackTrace()
|
||||||
|
AccountManagerResult.Failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,8 +44,6 @@ private val darkScheme = darkColorScheme(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DebtPirateTheme(
|
fun DebtPirateTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
|
||||||
dynamicColor: Boolean = true,
|
|
||||||
content: @Composable() () -> Unit
|
content: @Composable() () -> Unit
|
||||||
) {
|
) {
|
||||||
val colorScheme = darkScheme
|
val colorScheme = darkScheme
|
||||||
|
|
|
@ -25,12 +25,15 @@ okhttp = "4.10.0"
|
||||||
retrofit = "2.9.0"
|
retrofit = "2.9.0"
|
||||||
hiltNavigationCompose = "1.2.0"
|
hiltNavigationCompose = "1.2.0"
|
||||||
fonts = "1.7.5"
|
fonts = "1.7.5"
|
||||||
|
credentialManager = "1.5.0-beta01"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
androidx-credentials-core = { group = "androidx.credentials", name = "credentials", version.ref = "credentialManager" }
|
||||||
|
androidx-credentials-compat = { group = "androidx.credentials", name = "credentials-play-services-auth", version.ref = "credentialManager" }
|
||||||
androidx-datastore = { group = "androidx.datastore", name = "datastore", version.ref = "datastore" }
|
androidx-datastore = { group = "androidx.datastore", name = "datastore", version.ref = "datastore" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
|
|
Loading…
Add table
Reference in a new issue