From 20f618a1e6a1b776bb767fd4645012c53a73282d Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 8 Sep 2022 21:36:33 -0700 Subject: [PATCH] add username validation --- .../omnivore/ui/auth/CreateUserProfile.kt | 69 ++++++++++++------- .../omnivore/ui/auth/LoginViewModel.kt | 56 ++++++++++++++- 2 files changed, 99 insertions(+), 26 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/CreateUserProfile.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/CreateUserProfile.kt index c8229c9a2..1da7e33ae 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/CreateUserProfile.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/CreateUserProfile.kt @@ -44,8 +44,13 @@ fun CreateUserProfileView(viewModel: LoginViewModel) { UserProfileFields( name = name, username = username, + usernameValidationErrorMessage = viewModel.usernameValidationErrorMessage, + showUsernameAsAvailable = viewModel.hasValidUsername, onNameChange = { name = it }, - onUsernameChange = { username = it }, + onUsernameChange = { + username = it + viewModel.validateUsername(it) + }, onSubmit = { } // TODO: add viewModel function ) @@ -70,6 +75,8 @@ fun CreateUserProfileView(viewModel: LoginViewModel) { fun UserProfileFields( name: String, username: String, + usernameValidationErrorMessage: String?, + showUsernameAsAvailable: Boolean, // TODO: use this to add green checkmark onNameChange: (String) -> Unit, onUsernameChange: (String) -> Unit, onSubmit: () -> Unit @@ -84,15 +91,6 @@ fun UserProfileFields( verticalArrangement = Arrangement.spacedBy(25.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - OutlinedTextField( - value = username, - placeholder = { Text(text = "Username") }, - label = { Text(text = "Username") }, - onValueChange = onUsernameChange, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) - ) - OutlinedTextField( value = name, placeholder = { Text(text = "Name") }, @@ -102,21 +100,44 @@ fun UserProfileFields( keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) ) - Button(onClick = { - if (name.isNotBlank() && username.isNotBlank()) { - onSubmit() - focusManager.clearFocus() - } else { - Toast.makeText( - context, - "Please enter a valid username and password.", - Toast.LENGTH_SHORT - ).show() + Column( + verticalArrangement = Arrangement.spacedBy(5.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + OutlinedTextField( + value = username, + placeholder = { Text(text = "Username") }, + label = { Text(text = "Username") }, + onValueChange = onUsernameChange, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) + ) + + if (usernameValidationErrorMessage != null) { + Text( + text = usernameValidationErrorMessage!!, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.error, + ) } - }, colors = ButtonDefaults.buttonColors( - contentColor = Color(0xFF3D3D3D), - containerColor = Color(0xffffd234) - ) + } + + Button( + onClick = { + if (name.isNotBlank() && username.isNotBlank()) { + onSubmit() + focusManager.clearFocus() + } else { + Toast.makeText( + context, + "Please enter a valid username and password.", + Toast.LENGTH_SHORT + ).show() + } + }, colors = ButtonDefaults.buttonColors( + contentColor = Color(0xFF3D3D3D), + containerColor = Color(0xffffd234) + ) ) { Text( text = "Submit", diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt index 158ab91c1..d174770be 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt @@ -5,16 +5,21 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.* import app.omnivore.omnivore.* +import app.omnivore.omnivore.graphql.generated.SearchQuery +import app.omnivore.omnivore.graphql.generated.ValidateUsernameQuery +import com.apollographql.apollo3.ApolloClient +import com.apollographql.apollo3.api.Optional import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.common.api.ApiException import com.google.android.gms.tasks.Task import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import javax.inject.Inject - enum class RegistrationState { SocialLogin, EmailSignIn, @@ -25,18 +30,26 @@ enum class RegistrationState { class LoginViewModel @Inject constructor( private val datastoreRepo: DatastoreRepository ): ViewModel() { + private var validateUsernameJob: Job? = null + var isLoading by mutableStateOf(false) private set var errorMessage by mutableStateOf(null) private set + var hasValidUsername by mutableStateOf(false) + private set + + var usernameValidationErrorMessage by mutableStateOf(null) + private set + val hasAuthTokenLiveData: LiveData = datastoreRepo .hasAuthTokenFlow .distinctUntilChanged() .asLiveData() - val registrationStateLiveData = MutableLiveData(RegistrationState.SocialLogin) + val registrationStateLiveData = MutableLiveData(RegistrationState.PendingUser) // TODO: switch back to Social fun getAuthCookieString(): String? = runBlocking { datastoreRepo.getString(DatastoreKeys.omnivoreAuthCookieString) @@ -57,6 +70,45 @@ class LoginViewModel @Inject constructor( showSocialLogin() } + fun validateUsername(potentialUsername: String) { + validateUsernameJob?.cancel() + + validateUsernameJob = viewModelScope.launch { + delay(500) + + // Check the username requirements first + if (potentialUsername.isEmpty()) { + usernameValidationErrorMessage = null + hasValidUsername = false + return@launch + } + + if (potentialUsername.length < 4 || potentialUsername.length > 15) { + usernameValidationErrorMessage = "Username must be between 4 and 15 characters long." + hasValidUsername = false + return@launch + } + + // TODO: check email regex + + val apolloClient = ApolloClient.Builder() + .serverUrl("${Constants.apiURL}/api/graphql") + .build() + + val response = apolloClient.query( + ValidateUsernameQuery(username = potentialUsername) + ).execute() + + if (response.data?.validateUsername == true) { + usernameValidationErrorMessage = null + hasValidUsername = true + } else { + hasValidUsername = false + usernameValidationErrorMessage = "This username is not available." + } + } + } + fun login(email: String, password: String) { val emailLogin = RetrofitHelper.getInstance().create(EmailLoginSubmit::class.java)