From 04b4c5ac9341ece212732330b18a3e1193b5f7e9 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 8 Sep 2022 12:48:28 -0700 Subject: [PATCH 01/13] add pending user request --- .../app/omnivore/omnivore/RESTNetworker.kt | 10 ++++ .../omnivore/ui/auth/LoginViewModel.kt | 49 +++++++++++++++---- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt index c9e238e67..824eb42d9 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt @@ -12,6 +12,10 @@ data class AuthPayload( val authToken: String ) +data class PendingUserAuthPayload( + val pendingUserToken: String, +) + data class SignInParams( val token: String, val provider: String, // APPLE or GOOGLE @@ -41,6 +45,12 @@ interface AuthProviderLoginSubmit { suspend fun submitAuthProviderLogin(@Body params: SignInParams): Response } +interface PendingUserSubmit { + @Headers("Content-Type: application/json") + @POST("/api/mobile-auth/sign-up") + suspend fun submitPendingUser(@Body params: SignInParams): Response +} + object RetrofitHelper { fun getInstance(): Retrofit { return Retrofit.Builder().baseUrl(Constants.apiURL) 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 2b2f682eb..4a0618d9d 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 @@ -1,11 +1,12 @@ package app.omnivore.omnivore.ui.auth -import android.content.ContentValues -import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.lifecycle.* +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope import app.omnivore.omnivore.* import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.common.api.ApiException @@ -121,15 +122,43 @@ class LoginViewModel @Inject constructor( if (result.body()?.authToken != null) { datastoreRepo.putString(DatastoreKeys.omnivoreAuthToken, result.body()?.authToken!!) - } else { - errorMessage = "Something went wrong. Please check your credentials and try again" - } - if (result.body()?.authCookieString != null) { - datastoreRepo.putString( - DatastoreKeys.omnivoreAuthCookieString, result.body()?.authCookieString!! - ) + if (result.body()?.authCookieString != null) { + datastoreRepo.putString( + DatastoreKeys.omnivoreAuthCookieString, result.body()?.authCookieString!! + ) + } + } else { + when (result.code()) { + 401, 403 -> { + // This is a new user so they should go through the new user flow + submitAuthProviderPayloadForPendingToken(params = params) + } + 418 -> { + // Show pending email state + errorMessage = "Something went wrong. Please check your credentials and try again" + } + else -> { + errorMessage = "Something went wrong. Please check your credentials and try again" + } + } } } } + + private suspend fun submitAuthProviderPayloadForPendingToken(params: SignInParams) { + isLoading = true + errorMessage = null + + val request = RetrofitHelper.getInstance().create(PendingUserSubmit::class.java) + val result = request.submitPendingUser(params) + + isLoading = false + + if (result.body()?.pendingUserToken != null) { + // store in datastore + } else { + errorMessage = "Something went wrong. Please check your credentials and try again" + } + } } From 868a24a608cdd8a506484c78b453f84f7ba1cdb9 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 8 Sep 2022 13:06:12 -0700 Subject: [PATCH 02/13] observe regsitration state from login view model --- .../omnivore/omnivore/ui/auth/EmailLogin.kt | 4 +-- .../omnivore/ui/auth/LoginViewModel.kt | 17 +++++++---- .../omnivore/ui/auth/WelcomeScreen.kt | 30 ++++++------------- .../omnivore/ui/home/HomeViewModel.kt | 1 - 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt index 99b41f432..6945ed327 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt @@ -24,7 +24,7 @@ import androidx.compose.ui.unit.dp @SuppressLint("CoroutineCreationDuringComposition") @Composable -fun EmailLoginView(viewModel: LoginViewModel, onAuthProviderButtonTap: () -> Unit) { +fun EmailLoginView(viewModel: LoginViewModel) { var email by rememberSaveable { mutableStateOf("") } var password by rememberSaveable { mutableStateOf("") } @@ -53,7 +53,7 @@ fun EmailLoginView(viewModel: LoginViewModel, onAuthProviderButtonTap: () -> Uni text = AnnotatedString("Return to Social Login"), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), - onClick = { onAuthProviderButtonTap() } + onClick = { viewModel.showSocialLogin() } ) } Spacer(modifier = Modifier.weight(1.0F)) 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 4a0618d9d..ea58de14a 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 @@ -3,10 +3,7 @@ package app.omnivore.omnivore.ui.auth import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.asLiveData -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.* import app.omnivore.omnivore.* import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.common.api.ApiException @@ -19,7 +16,7 @@ import javax.inject.Inject enum class RegistrationState { - AuthProviderButtons, + SocialLogin, EmailSignIn } @@ -38,10 +35,20 @@ class LoginViewModel @Inject constructor( .distinctUntilChanged() .asLiveData() + val registrationStateLiveData = MutableLiveData(RegistrationState.SocialLogin) + fun getAuthCookieString(): String? = runBlocking { datastoreRepo.getString(DatastoreKeys.omnivoreAuthCookieString) } + fun showSocialLogin() { + registrationStateLiveData.value = RegistrationState.SocialLogin + } + + fun showEmailSignIn() { + registrationStateLiveData.value = RegistrationState.EmailSignIn + } + fun login(email: String, password: String) { val emailLogin = RetrofitHelper.getInstance().create(EmailLoginSubmit::class.java) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt index 8f9c5fe8a..cdcab04e0 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -35,11 +36,9 @@ fun WelcomeScreen(viewModel: LoginViewModel) { @SuppressLint("CoroutineCreationDuringComposition") @Composable fun WelcomeScreenContent(viewModel: LoginViewModel) { - var registrationState by rememberSaveable { mutableStateOf(RegistrationState.AuthProviderButtons) } - - val onRegistrationStateChange = { state: RegistrationState -> - registrationState = state - } + val registrationState: RegistrationState by viewModel + .registrationStateLiveData + .observeAsState(RegistrationState.SocialLogin) val snackBarHostState = remember { SnackbarHostState() } val coroutineScope = rememberCoroutineScope() @@ -62,14 +61,9 @@ fun WelcomeScreenContent(viewModel: LoginViewModel) { when(registrationState) { RegistrationState.EmailSignIn -> { - EmailLoginView( - viewModel = viewModel, - onAuthProviderButtonTap = { - onRegistrationStateChange(RegistrationState.AuthProviderButtons) - } - ) + EmailLoginView(viewModel = viewModel) } - RegistrationState.AuthProviderButtons -> { + RegistrationState.SocialLogin -> { Text( text = stringResource(id = R.string.welcome_title), style = MaterialTheme.typography.headlineLarge @@ -84,10 +78,7 @@ fun WelcomeScreenContent(viewModel: LoginViewModel) { Spacer(modifier = Modifier.height(50.dp)) - AuthProviderView( - viewModel = viewModel, - onEmailButtonTap = { onRegistrationStateChange(RegistrationState.EmailSignIn) } - ) + AuthProviderView(viewModel = viewModel) } } @@ -112,10 +103,7 @@ fun WelcomeScreenContent(viewModel: LoginViewModel) { } @Composable -fun AuthProviderView( - viewModel: LoginViewModel, - onEmailButtonTap: () -> Unit -) { +fun AuthProviderView(viewModel: LoginViewModel) { val isGoogleAuthAvailable: Boolean = GoogleApiAvailability .getInstance() .isGooglePlayServicesAvailable(LocalContext.current) == 0 @@ -138,7 +126,7 @@ fun AuthProviderView( text = AnnotatedString("Continue with Email"), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), - onClick = { onEmailButtonTap() } + onClick = { viewModel.showEmailSignIn() } ) } Spacer(modifier = Modifier.weight(1.0F)) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt index 5d4ab6074..85ba6ecec 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt @@ -1,6 +1,5 @@ package app.omnivore.omnivore.ui.home -import android.util.Log import androidx.core.net.toUri import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel From 7d1f1ef6fcc0216c7962d651dfa53ffb01f7e803 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 8 Sep 2022 13:49:34 -0700 Subject: [PATCH 03/13] add create user profile view --- .../java/app/omnivore/omnivore/Constants.kt | 1 + .../omnivore/omnivore/DatastoreRepository.kt | 6 + .../omnivore/ui/auth/CreateUserProfile.kt | 127 ++++++++++++++++++ .../omnivore/ui/auth/LoginViewModel.kt | 20 ++- .../omnivore/ui/auth/WelcomeScreen.kt | 3 + 5 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/CreateUserProfile.kt diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Constants.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Constants.kt index b351e3ecf..3b219d6a3 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Constants.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Constants.kt @@ -9,6 +9,7 @@ object Constants { object DatastoreKeys { const val omnivoreAuthToken = "omnivoreAuthToken" const val omnivoreAuthCookieString = "omnivoreAuthCookieString" + const val omnivorePendingUserToken = "omnivorePendingUserToken" } object AppleConstants { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DatastoreRepository.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DatastoreRepository.kt index 7392ffeb9..725054822 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DatastoreRepository.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DatastoreRepository.kt @@ -16,6 +16,7 @@ interface DatastoreRepository { suspend fun putInt(key: String, value: Int) suspend fun getString(key: String): String? suspend fun getInt(key: String): Int? + suspend fun clearValue(key: String) } class OmnivoreDatastore @Inject constructor( @@ -55,6 +56,11 @@ class OmnivoreDatastore @Inject constructor( context.dataStore.edit { it.clear() } } + override suspend fun clearValue(key: String) { + val preferencesKey = stringPreferencesKey(key) + context.dataStore.edit { it.remove(preferencesKey) } + } + override val hasAuthTokenFlow: Flow = context .dataStore.data.map { preferences -> val key = stringPreferencesKey(DatastoreKeys.omnivoreAuthToken) 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 new file mode 100644 index 000000000..c8229c9a2 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/CreateUserProfile.kt @@ -0,0 +1,127 @@ +package app.omnivore.omnivore.ui.auth + +import android.annotation.SuppressLint +import android.widget.Toast +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.ClickableText +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import app.omnivore.omnivore.R + +@SuppressLint("CoroutineCreationDuringComposition") +@Composable +fun CreateUserProfileView(viewModel: LoginViewModel) { + var name by rememberSaveable { mutableStateOf("") } + var username by rememberSaveable { mutableStateOf("") } + + Row( + horizontalArrangement = Arrangement.Center + ) { + Spacer(modifier = Modifier.weight(1.0F)) + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Create Your Profile", + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier.padding(bottom = 8.dp) + ) + UserProfileFields( + name = name, + username = username, + onNameChange = { name = it }, + onUsernameChange = { username = it }, + onSubmit = { } // TODO: add viewModel function + ) + + // TODO: add a activity indicator (maybe after a delay?) + if (viewModel.isLoading) { + Text("Loading...") + } + + ClickableText( + text = AnnotatedString("Cancel Sign Up"), + style = MaterialTheme.typography.titleMedium + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { viewModel.cancelNewUserSignUp() } + ) + } + Spacer(modifier = Modifier.weight(1.0F)) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun UserProfileFields( + name: String, + username: String, + onNameChange: (String) -> Unit, + onUsernameChange: (String) -> Unit, + onSubmit: () -> Unit +) { + val context = LocalContext.current + val focusManager = LocalFocusManager.current + + Column( + modifier = Modifier + .fillMaxWidth() + .height(300.dp), + 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") }, + label = { Text(text = "Name") }, + onValueChange = onNameChange, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + 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() + } + }, colors = ButtonDefaults.buttonColors( + contentColor = Color(0xFF3D3D3D), + containerColor = Color(0xffffd234) + ) + ) { + Text( + text = "Submit", + modifier = Modifier.padding(horizontal = 100.dp) + ) + } + } +} 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 ea58de14a..62391b05d 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 @@ -17,7 +17,8 @@ import javax.inject.Inject enum class RegistrationState { SocialLogin, - EmailSignIn + EmailSignIn, + PendingUser } @HiltViewModel @@ -35,7 +36,7 @@ class LoginViewModel @Inject constructor( .distinctUntilChanged() .asLiveData() - val registrationStateLiveData = MutableLiveData(RegistrationState.SocialLogin) + val registrationStateLiveData = MutableLiveData(RegistrationState.PendingUser) // TODO: set back to social login fun getAuthCookieString(): String? = runBlocking { datastoreRepo.getString(DatastoreKeys.omnivoreAuthCookieString) @@ -49,6 +50,13 @@ class LoginViewModel @Inject constructor( registrationStateLiveData.value = RegistrationState.EmailSignIn } + fun cancelNewUserSignUp() { + viewModelScope.launch { + datastoreRepo.clearValue(DatastoreKeys.omnivorePendingUserToken) + } + showSocialLogin() + } + fun login(email: String, password: String) { val emailLogin = RetrofitHelper.getInstance().create(EmailLoginSubmit::class.java) @@ -163,9 +171,15 @@ class LoginViewModel @Inject constructor( isLoading = false if (result.body()?.pendingUserToken != null) { - // store in datastore + datastoreRepo.putString( + DatastoreKeys.omnivorePendingUserToken, result.body()?.pendingUserToken!! + ) + registrationStateLiveData.value = RegistrationState.PendingUser } else { errorMessage = "Something went wrong. Please check your credentials and try again" } } } + + +// TODO: set registrationState to pending if user has pending token diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt index cdcab04e0..2996c81c4 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt @@ -80,6 +80,9 @@ fun WelcomeScreenContent(viewModel: LoginViewModel) { AuthProviderView(viewModel = viewModel) } + RegistrationState.PendingUser -> { + CreateUserProfileView(viewModel = viewModel) + } } Spacer(modifier = Modifier.weight(1.0F)) From 721226c6cc51b16aef6c6cd6d1ab2b180a981a4b Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 8 Sep 2022 15:49:56 -0700 Subject: [PATCH 04/13] add validate username graphql file --- .../Omnivore/app/src/main/graphql/ValidateUsername.graphql | 3 +++ .../java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 android/Omnivore/app/src/main/graphql/ValidateUsername.graphql diff --git a/android/Omnivore/app/src/main/graphql/ValidateUsername.graphql b/android/Omnivore/app/src/main/graphql/ValidateUsername.graphql new file mode 100644 index 000000000..79938a855 --- /dev/null +++ b/android/Omnivore/app/src/main/graphql/ValidateUsername.graphql @@ -0,0 +1,3 @@ +query ValidateUsername($username: String!) { + validateUsername(username: $username) +} 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 62391b05d..158ab91c1 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 @@ -36,7 +36,7 @@ class LoginViewModel @Inject constructor( .distinctUntilChanged() .asLiveData() - val registrationStateLiveData = MutableLiveData(RegistrationState.PendingUser) // TODO: set back to social login + val registrationStateLiveData = MutableLiveData(RegistrationState.SocialLogin) fun getAuthCookieString(): String? = runBlocking { datastoreRepo.getString(DatastoreKeys.omnivoreAuthCookieString) @@ -180,6 +180,3 @@ class LoginViewModel @Inject constructor( } } } - - -// TODO: set registrationState to pending if user has pending token From 20f618a1e6a1b776bb767fd4645012c53a73282d Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 8 Sep 2022 21:36:33 -0700 Subject: [PATCH 05/13] 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) From 1e93e462b988a3f51832b1fc3f99ad4de1eced85 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 8 Sep 2022 21:56:53 -0700 Subject: [PATCH 06/13] add create-account call for user sign up --- .../app/omnivore/omnivore/RESTNetworker.kt | 16 ++++++++ .../omnivore/ui/auth/CreateUserProfile.kt | 2 +- .../omnivore/ui/auth/LoginViewModel.kt | 39 ++++++++++++++++++- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt index 824eb42d9..bf58a49d4 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt @@ -33,6 +33,16 @@ data class EmailLoginCredentials( val password: String ) +data class CreateAccountParams( + val pendingUserToken: String, + val userProfile: UserProfile +) + +data class UserProfile( + val username: String, + val name: String +) + interface EmailLoginSubmit { @Headers("Content-Type: application/json") @POST("/api/mobile-auth/email-sign-in") @@ -51,6 +61,12 @@ interface PendingUserSubmit { suspend fun submitPendingUser(@Body params: SignInParams): Response } +interface CreateAccountSubmit { + @Headers("Content-Type: application/json") + @POST("/api/mobile-auth/create-account") + suspend fun submitCreateAccount(@Body params: CreateAccountParams): Response +} + object RetrofitHelper { fun getInstance(): Retrofit { return Retrofit.Builder().baseUrl(Constants.apiURL) 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 1da7e33ae..989213264 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 @@ -51,7 +51,7 @@ fun CreateUserProfileView(viewModel: LoginViewModel) { username = it viewModel.validateUsername(it) }, - onSubmit = { } // TODO: add viewModel function + onSubmit = { viewModel.submitProfile(username = username, name = name) } ) // TODO: add a activity indicator (maybe after a delay?) 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 d174770be..bbc6cee8b 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 @@ -49,7 +49,7 @@ class LoginViewModel @Inject constructor( .distinctUntilChanged() .asLiveData() - val registrationStateLiveData = MutableLiveData(RegistrationState.PendingUser) // TODO: switch back to Social + val registrationStateLiveData = MutableLiveData(RegistrationState.SocialLogin) fun getAuthCookieString(): String? = runBlocking { datastoreRepo.getString(DatastoreKeys.omnivoreAuthCookieString) @@ -141,6 +141,43 @@ class LoginViewModel @Inject constructor( } } + private fun getPendingAuthToken(): String? = runBlocking { + datastoreRepo.getString(DatastoreKeys.omnivorePendingUserToken) + } + + fun submitProfile(username: String, name: String) { + viewModelScope.launch { + val request = RetrofitHelper.getInstance().create(CreateAccountSubmit::class.java) + + isLoading = true + errorMessage = null + + val pendingUserToken = getPendingAuthToken() ?: "" + + val userProfile = UserProfile(name = name, username = username) + val params = CreateAccountParams( + pendingUserToken = pendingUserToken, + userProfile = userProfile + ) + + val result = request.submitCreateAccount(params) + + isLoading = false + + if (result.body()?.authToken != null) { + datastoreRepo.putString(DatastoreKeys.omnivoreAuthToken, result.body()?.authToken!!) + } else { + errorMessage = "Something went wrong. Please check your email/password and try again" + } + + if (result.body()?.authCookieString != null) { + datastoreRepo.putString( + DatastoreKeys.omnivoreAuthCookieString, result.body()?.authCookieString!! + ) + } + } + } + fun handleAppleToken(authToken: String) { submitAuthProviderPayload( params = SignInParams(token = authToken, provider = "APPLE") From bf000e665c639c36c49258a1561eef6705cb67a1 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 9 Sep 2022 10:42:37 -0700 Subject: [PATCH 07/13] validate username pattern. show checkmark when name is valid --- .../omnivore/ui/auth/CreateUserProfile.kt | 20 ++++++++++++++++--- .../omnivore/ui/auth/LoginViewModel.kt | 11 +++++++++- .../main/res/xml/data_extraction_rules.xml | 2 +- 3 files changed, 28 insertions(+), 5 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 989213264..153d3804e 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 @@ -6,6 +6,9 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.Search import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -18,9 +21,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import app.omnivore.omnivore.R +import org.intellij.lang.annotations.JdkConstants @SuppressLint("CoroutineCreationDuringComposition") @Composable @@ -76,7 +81,7 @@ fun UserProfileFields( name: String, username: String, usernameValidationErrorMessage: String?, - showUsernameAsAvailable: Boolean, // TODO: use this to add green checkmark + showUsernameAsAvailable: Boolean, onNameChange: (String) -> Unit, onUsernameChange: (String) -> Unit, onSubmit: () -> Unit @@ -110,7 +115,15 @@ fun UserProfileFields( label = { Text(text = "Username") }, onValueChange = onUsernameChange, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + trailingIcon = { + if (showUsernameAsAvailable) { + Icon( + imageVector = Icons.Filled.CheckCircle, + contentDescription = null + ) + } + } ) if (usernameValidationErrorMessage != null) { @@ -118,6 +131,7 @@ fun UserProfileFields( text = usernameValidationErrorMessage!!, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.error, + textAlign = TextAlign.Center ) } } @@ -130,7 +144,7 @@ fun UserProfileFields( } else { Toast.makeText( context, - "Please enter a valid username and password.", + "Please enter a valid name and username.", Toast.LENGTH_SHORT ).show() } 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 bbc6cee8b..f7b5ab8d2 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 @@ -18,6 +18,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import java.util.regex.Pattern import javax.inject.Inject enum class RegistrationState { @@ -89,7 +90,15 @@ class LoginViewModel @Inject constructor( return@launch } - // TODO: check email regex + val isValidPattern = Pattern.compile("^[a-z0-9][a-z0-9_]+[a-z0-9]$") + .matcher(potentialUsername) + .matches() + + if (!isValidPattern) { + usernameValidationErrorMessage = "Username can contain only letters and numbers" + hasValidUsername = false + return@launch + } val apolloClient = ApolloClient.Builder() .serverUrl("${Constants.apiURL}/api/graphql") diff --git a/android/Omnivore/app/src/main/res/xml/data_extraction_rules.xml b/android/Omnivore/app/src/main/res/xml/data_extraction_rules.xml index 0c4f95cab..e59428ef8 100644 --- a/android/Omnivore/app/src/main/res/xml/data_extraction_rules.xml +++ b/android/Omnivore/app/src/main/res/xml/data_extraction_rules.xml @@ -5,7 +5,7 @@ --> - From bfbcf9b339ab28f234377973a2abe56aed01e2a0 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 9 Sep 2022 10:57:54 -0700 Subject: [PATCH 08/13] stub in email signup view --- .../omnivore/omnivore/ui/auth/EmailLogin.kt | 7 + .../omnivore/ui/auth/EmailSignUpView.kt | 129 ++++++++++++++++++ .../omnivore/ui/auth/LoginViewModel.kt | 5 + .../omnivore/ui/auth/WelcomeScreen.kt | 3 + 4 files changed, 144 insertions(+) create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt index 6945ed327..09c2ff8d2 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt @@ -55,6 +55,13 @@ fun EmailLoginView(viewModel: LoginViewModel) { .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { viewModel.showSocialLogin() } ) + + ClickableText( + text = AnnotatedString("Don't have an account?"), + style = MaterialTheme.typography.titleMedium + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { viewModel.showEmailSignUp() } + ) } Spacer(modifier = Modifier.weight(1.0F)) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt new file mode 100644 index 000000000..30184e10f --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt @@ -0,0 +1,129 @@ +package app.omnivore.omnivore.ui.auth + +import android.annotation.SuppressLint +import android.widget.Toast +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.ClickableText +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp + +@SuppressLint("CoroutineCreationDuringComposition") +@Composable +fun EmailSignUpView(viewModel: LoginViewModel) { + var email by rememberSaveable { mutableStateOf("") } + var password by rememberSaveable { mutableStateOf("") } + + Row( + horizontalArrangement = Arrangement.Center + ) { + Spacer(modifier = Modifier.weight(1.0F)) + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + EmailSignUpFields( + email, + password, + onEmailChange = { email = it }, + onPasswordChange = { password = it }, + onLoginClick = { viewModel.login(email, password) } + ) + + // TODO: add a activity indicator (maybe after a delay?) + if (viewModel.isLoading) { + Text("Loading...") + } + + ClickableText( + text = AnnotatedString("Return to Social Login"), + style = MaterialTheme.typography.titleMedium + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { viewModel.showSocialLogin() } + ) + + ClickableText( + text = AnnotatedString("Already have an account?"), + style = MaterialTheme.typography.titleMedium + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { viewModel.showEmailSignIn() } + ) + } + Spacer(modifier = Modifier.weight(1.0F)) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EmailSignUpFields( + email: String, + password: String, + onEmailChange: (String) -> Unit, + onPasswordChange: (String) -> Unit, + onLoginClick: () -> Unit +) { + val context = LocalContext.current + val focusManager = LocalFocusManager.current + + Column( + modifier = Modifier + .fillMaxWidth() + .height(300.dp), + verticalArrangement = Arrangement.spacedBy(25.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + OutlinedTextField( + value = email, + placeholder = { Text(text = "user@email.com") }, + label = { Text(text = "Email") }, + onValueChange = onEmailChange, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) + ) + + OutlinedTextField( + value = password, + placeholder = { Text(text = "Password") }, + label = { Text(text = "Password") }, + onValueChange = onPasswordChange, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) + ) + + Button(onClick = { + if (email.isNotBlank() && password.isNotBlank()) { + onLoginClick() + focusManager.clearFocus() + } else { + Toast.makeText( + context, + "Please enter an email address and password.", + Toast.LENGTH_SHORT + ).show() + } + }, colors = ButtonDefaults.buttonColors( + contentColor = Color(0xFF3D3D3D), + containerColor = Color(0xffffd234) + ) + ) { + Text( + text = "Login", + modifier = Modifier.padding(horizontal = 100.dp) + ) + } + } +} 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 f7b5ab8d2..187b8d280 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 @@ -24,6 +24,7 @@ import javax.inject.Inject enum class RegistrationState { SocialLogin, EmailSignIn, + EmailSignUp, PendingUser } @@ -64,6 +65,10 @@ class LoginViewModel @Inject constructor( registrationStateLiveData.value = RegistrationState.EmailSignIn } + fun showEmailSignUp() { + registrationStateLiveData.value = RegistrationState.EmailSignUp + } + fun cancelNewUserSignUp() { viewModelScope.launch { datastoreRepo.clearValue(DatastoreKeys.omnivorePendingUserToken) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt index 2996c81c4..7f497d556 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt @@ -63,6 +63,9 @@ fun WelcomeScreenContent(viewModel: LoginViewModel) { RegistrationState.EmailSignIn -> { EmailLoginView(viewModel = viewModel) } + RegistrationState.EmailSignUp -> { + EmailSignUpView(viewModel = viewModel) + } RegistrationState.SocialLogin -> { Text( text = stringResource(id = R.string.welcome_title), From 1024cb5a314a18b90fb70073737d0552c0142ef5 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 9 Sep 2022 11:48:32 -0700 Subject: [PATCH 09/13] add name and username fields to email signup form --- .../omnivore/ui/auth/EmailSignUpView.kt | 83 +++++++++++++++++-- .../omnivore/ui/auth/LoginViewModel.kt | 9 ++ 2 files changed, 84 insertions(+), 8 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt index 30184e10f..e7bea14d2 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt @@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -18,6 +20,7 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp @@ -26,6 +29,8 @@ import androidx.compose.ui.unit.dp fun EmailSignUpView(viewModel: LoginViewModel) { var email by rememberSaveable { mutableStateOf("") } var password by rememberSaveable { mutableStateOf("") } + var name by rememberSaveable { mutableStateOf("") } + var username by rememberSaveable { mutableStateOf("") } Row( horizontalArrangement = Arrangement.Center @@ -36,11 +41,27 @@ fun EmailSignUpView(viewModel: LoginViewModel) { horizontalAlignment = Alignment.CenterHorizontally ) { EmailSignUpFields( - email, - password, + email = email, + password = password, + name = name, + username = username, + usernameValidationErrorMessage = viewModel.usernameValidationErrorMessage, + showUsernameAsAvailable = viewModel.hasValidUsername, onEmailChange = { email = it }, onPasswordChange = { password = it }, - onLoginClick = { viewModel.login(email, password) } + onNameChange = { name = it }, + onUsernameChange = { + username = it + viewModel.validateUsername(it) + }, + onSubmit = { + viewModel.submitEmailSignUp( + email = email, + password = password, + username = username, + name = name + ) + } ) // TODO: add a activity indicator (maybe after a delay?) @@ -71,9 +92,15 @@ fun EmailSignUpView(viewModel: LoginViewModel) { fun EmailSignUpFields( email: String, password: String, + name: String, + username: String, + usernameValidationErrorMessage: String?, + showUsernameAsAvailable: Boolean, onEmailChange: (String) -> Unit, onPasswordChange: (String) -> Unit, - onLoginClick: () -> Unit + onNameChange: (String) -> Unit, + onUsernameChange: (String) -> Unit, + onSubmit: () -> Unit, ) { val context = LocalContext.current val focusManager = LocalFocusManager.current @@ -104,14 +131,54 @@ fun EmailSignUpFields( keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) ) + OutlinedTextField( + value = name, + placeholder = { Text(text = "Name") }, + label = { Text(text = "Name") }, + onValueChange = onNameChange, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) + ) + + 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() }), + trailingIcon = { + if (showUsernameAsAvailable) { + Icon( + imageVector = Icons.Filled.CheckCircle, + contentDescription = null + ) + } + } + ) + + if (usernameValidationErrorMessage != null) { + Text( + text = usernameValidationErrorMessage!!, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.error, + textAlign = TextAlign.Center + ) + } + } + Button(onClick = { - if (email.isNotBlank() && password.isNotBlank()) { - onLoginClick() + if (email.isNotBlank() && password.isNotBlank() && username.isNotBlank() && name.isNotBlank()) { + onSubmit() focusManager.clearFocus() } else { Toast.makeText( context, - "Please enter an email address and password.", + "Please complete all fields.", Toast.LENGTH_SHORT ).show() } @@ -121,7 +188,7 @@ fun EmailSignUpFields( ) ) { Text( - text = "Login", + text = "Sign Up", modifier = Modifier.padding(horizontal = 100.dp) ) } 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 187b8d280..117d06432 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 @@ -155,6 +155,15 @@ class LoginViewModel @Inject constructor( } } + fun submitEmailSignUp( + email: String, + password: String, + username: String, + name: String, + ) { + // TODO: make network call + } + private fun getPendingAuthToken(): String? = runBlocking { datastoreRepo.getString(DatastoreKeys.omnivorePendingUserToken) } From 86693309327dabe1a7d3d3471e8cc1115d763234 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Sat, 10 Sep 2022 07:27:16 -0700 Subject: [PATCH 10/13] add scroll to email sign up form --- .../java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt index e7bea14d2..43cb3d973 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt @@ -3,9 +3,11 @@ package app.omnivore.omnivore.ui.auth import android.annotation.SuppressLint import android.widget.Toast import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material3.* @@ -108,7 +110,7 @@ fun EmailSignUpFields( Column( modifier = Modifier .fillMaxWidth() - .height(300.dp), + .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(25.dp), horizontalAlignment = Alignment.CenterHorizontally ) { From e131e83fec3561de278bac972b4d6a5de610ed27 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Sun, 11 Sep 2022 21:42:36 -0700 Subject: [PATCH 11/13] add spacing between clickable text --- .../omnivore/omnivore/ui/auth/EmailLogin.kt | 40 +++++++++++++------ .../omnivore/ui/auth/EmailSignUpView.kt | 28 +++++++------ .../omnivore/ui/auth/LoginViewModel.kt | 4 ++ 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt index 09c2ff8d2..22a4a3ba5 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt @@ -1,6 +1,10 @@ package app.omnivore.omnivore.ui.auth import android.annotation.SuppressLint +import android.view.ViewGroup +import android.webkit.CookieManager +import android.webkit.WebView +import android.webkit.WebViewClient import android.widget.Toast import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.ClickableText @@ -21,6 +25,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView @SuppressLint("CoroutineCreationDuringComposition") @Composable @@ -49,19 +54,30 @@ fun EmailLoginView(viewModel: LoginViewModel) { Text("Loading...") } - ClickableText( - text = AnnotatedString("Return to Social Login"), - style = MaterialTheme.typography.titleMedium - .plus(TextStyle(textDecoration = TextDecoration.Underline)), - onClick = { viewModel.showSocialLogin() } - ) + Column( + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + ClickableText( + text = AnnotatedString("Return to Social Login"), + style = MaterialTheme.typography.titleMedium + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { viewModel.showSocialLogin() } + ) - ClickableText( - text = AnnotatedString("Don't have an account?"), - style = MaterialTheme.typography.titleMedium - .plus(TextStyle(textDecoration = TextDecoration.Underline)), - onClick = { viewModel.showEmailSignUp() } - ) + ClickableText( + text = AnnotatedString("Don't have an account?"), + style = MaterialTheme.typography.titleMedium + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { viewModel.showEmailSignUp() } + ) + + ClickableText( + text = AnnotatedString("Forgot your password?"), + style = MaterialTheme.typography.titleMedium + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { viewModel.showForgotPasswordView() } + ) + } } Spacer(modifier = Modifier.weight(1.0F)) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt index 43cb3d973..404ff791c 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt @@ -71,19 +71,23 @@ fun EmailSignUpView(viewModel: LoginViewModel) { Text("Loading...") } - ClickableText( - text = AnnotatedString("Return to Social Login"), - style = MaterialTheme.typography.titleMedium - .plus(TextStyle(textDecoration = TextDecoration.Underline)), - onClick = { viewModel.showSocialLogin() } - ) + Column( + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + ClickableText( + text = AnnotatedString("Return to Social Login"), + style = MaterialTheme.typography.titleMedium + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { viewModel.showSocialLogin() } + ) - ClickableText( - text = AnnotatedString("Already have an account?"), - style = MaterialTheme.typography.titleMedium - .plus(TextStyle(textDecoration = TextDecoration.Underline)), - onClick = { viewModel.showEmailSignIn() } - ) + ClickableText( + text = AnnotatedString("Already have an account?"), + style = MaterialTheme.typography.titleMedium + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { viewModel.showEmailSignIn() } + ) + } } Spacer(modifier = Modifier.weight(1.0F)) } 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 117d06432..cfb84ceab 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 @@ -69,6 +69,10 @@ class LoginViewModel @Inject constructor( registrationStateLiveData.value = RegistrationState.EmailSignUp } + fun showForgotPasswordView() { + // TODO: show webview + } + fun cancelNewUserSignUp() { viewModelScope.launch { datastoreRepo.clearValue(DatastoreKeys.omnivorePendingUserToken) From ae72de90d142c29bcb05688d6856be430767989e Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 15 Sep 2022 21:12:13 -0700 Subject: [PATCH 12/13] open forgot password link in chrome when user taps button --- .../main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt | 8 +++++++- .../java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt | 4 ---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt index 22a4a3ba5..bc3c1056a 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction @@ -26,10 +27,12 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import app.omnivore.omnivore.BuildConfig @SuppressLint("CoroutineCreationDuringComposition") @Composable fun EmailLoginView(viewModel: LoginViewModel) { + val uriHandler = LocalUriHandler.current var email by rememberSaveable { mutableStateOf("") } var password by rememberSaveable { mutableStateOf("") } @@ -75,7 +78,10 @@ fun EmailLoginView(viewModel: LoginViewModel) { text = AnnotatedString("Forgot your password?"), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), - onClick = { viewModel.showForgotPasswordView() } + onClick = { + val uri = "${BuildConfig.OMNIVORE_WEB_URL}/auth/forgot-password" + uriHandler.openUri(uri) + } ) } } 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 cfb84ceab..117d06432 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 @@ -69,10 +69,6 @@ class LoginViewModel @Inject constructor( registrationStateLiveData.value = RegistrationState.EmailSignUp } - fun showForgotPasswordView() { - // TODO: show webview - } - fun cancelNewUserSignUp() { viewModelScope.launch { datastoreRepo.clearValue(DatastoreKeys.omnivorePendingUserToken) From 85cbe5ac33fd6c051743d55cb5834d96ed3c8945 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 15 Sep 2022 22:12:46 -0700 Subject: [PATCH 13/13] handle email accounts that need verification --- .../app/omnivore/omnivore/RESTNetworker.kt | 13 +++++ .../omnivore/ui/auth/EmailSignUpView.kt | 49 +++++++++++++++++- .../omnivore/ui/auth/LoginViewModel.kt | 51 +++++++++++++++++-- 3 files changed, 109 insertions(+), 4 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt index bf58a49d4..11be50365 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/RESTNetworker.kt @@ -22,6 +22,13 @@ data class SignInParams( val source: String = "ANDROID" ) +data class EmailSignUpParams( + val email: String, + val password: String, + val username: String, + val name: String +) + data class EmailAuthPayload( val authCookieString: String?, val authToken: String?, @@ -67,6 +74,12 @@ interface CreateAccountSubmit { suspend fun submitCreateAccount(@Body params: CreateAccountParams): Response } +interface CreateEmailAccountSubmit { + @Headers("Content-Type: application/json") + @POST("/api/mobile-auth/email-sign-up") + suspend fun submitCreateEmailAccount(@Body params: EmailSignUpParams): Response +} + object RetrofitHelper { fun getInstance(): Retrofit { return Retrofit.Builder().baseUrl(Constants.apiURL) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt index 404ff791c..e9b486184 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt @@ -26,9 +26,56 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp -@SuppressLint("CoroutineCreationDuringComposition") @Composable fun EmailSignUpView(viewModel: LoginViewModel) { + if (viewModel.pendingEmailUserCreds != null) { + val email = viewModel.pendingEmailUserCreds?.email ?: "" + val password = viewModel.pendingEmailUserCreds?.password ?: "" + + val verificationMessage = "We've sent a verification email to ${email}. Please verify your email and then tap the button below." + + Row( + horizontalArrangement = Arrangement.Center + ) { + Spacer(modifier = Modifier.weight(1.0F)) + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = verificationMessage, + style = MaterialTheme.typography.titleMedium + ) + + Button(onClick = { + viewModel.login(email, password) + }, colors = ButtonDefaults.buttonColors( + contentColor = Color(0xFF3D3D3D), + containerColor = Color(0xffffd234) + ) + ) { + Text( + text = "Check Status", + modifier = Modifier.padding(horizontal = 100.dp) + ) + } + + ClickableText( + text = AnnotatedString("Use a different email?"), + style = MaterialTheme.typography.titleMedium + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { viewModel.showEmailSignUp() } + ) + } + } + } else { + EmailSignUpForm(viewModel = viewModel) + } +} + +@SuppressLint("CoroutineCreationDuringComposition") +@Composable +fun EmailSignUpForm(viewModel: LoginViewModel) { var email by rememberSaveable { mutableStateOf("") } var password by rememberSaveable { mutableStateOf("") } var name by rememberSaveable { mutableStateOf("") } 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 117d06432..ae058da27 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 @@ -4,6 +4,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.* +import androidx.lifecycle.viewmodel.compose.viewModel import app.omnivore.omnivore.* import app.omnivore.omnivore.graphql.generated.SearchQuery import app.omnivore.omnivore.graphql.generated.ValidateUsernameQuery @@ -28,6 +29,11 @@ enum class RegistrationState { PendingUser } +data class PendingEmailUserCreds( + val email: String, + val password: String +) + @HiltViewModel class LoginViewModel @Inject constructor( private val datastoreRepo: DatastoreRepository @@ -46,6 +52,9 @@ class LoginViewModel @Inject constructor( var usernameValidationErrorMessage by mutableStateOf(null) private set + var pendingEmailUserCreds by mutableStateOf(null) + private set + val hasAuthTokenLiveData: LiveData = datastoreRepo .hasAuthTokenFlow .distinctUntilChanged() @@ -58,24 +67,38 @@ class LoginViewModel @Inject constructor( } fun showSocialLogin() { + resetState() registrationStateLiveData.value = RegistrationState.SocialLogin } fun showEmailSignIn() { + resetState() registrationStateLiveData.value = RegistrationState.EmailSignIn } - fun showEmailSignUp() { + fun showEmailSignUp(pendingCreds: PendingEmailUserCreds? = null) { + resetState() + pendingEmailUserCreds = pendingCreds registrationStateLiveData.value = RegistrationState.EmailSignUp } fun cancelNewUserSignUp() { + resetState() viewModelScope.launch { datastoreRepo.clearValue(DatastoreKeys.omnivorePendingUserToken) } showSocialLogin() } + private fun resetState() { + validateUsernameJob = null + isLoading = false + errorMessage = null + hasValidUsername = false + usernameValidationErrorMessage = null + pendingEmailUserCreds = null + } + fun validateUsername(potentialUsername: String) { validateUsernameJob?.cancel() @@ -137,7 +160,7 @@ class LoginViewModel @Inject constructor( isLoading = false if (result.body()?.pendingEmailVerification == true) { - errorMessage = "Email needs verification" + showEmailSignUp(pendingCreds = PendingEmailUserCreds(email = email, password = password)) return@launch } @@ -161,7 +184,29 @@ class LoginViewModel @Inject constructor( username: String, name: String, ) { - // TODO: make network call + viewModelScope.launch { + val request = RetrofitHelper.getInstance().create(CreateEmailAccountSubmit::class.java) + + isLoading = true + errorMessage = null + + val params = EmailSignUpParams( + email = email, + password = password, + name = name, + username = username + ) + + val result = request.submitCreateEmailAccount(params) + + isLoading = false + + if (result.errorBody() != null) { + errorMessage = "Something went wrong. Please check your and try again" + } else { + pendingEmailUserCreds = PendingEmailUserCreds(email, password) + } + } } private fun getPendingAuthToken(): String? = runBlocking {