show snackbar when login submission returns an error
This commit is contained in:
@ -13,10 +13,6 @@ import kotlinx.coroutines.flow.map
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
|
||||
name = Constants.dataStoreName
|
||||
)
|
||||
|
||||
interface DatastoreRepository {
|
||||
val hasAuthTokenFlow: Flow<Boolean>
|
||||
suspend fun clear()
|
||||
@ -29,6 +25,10 @@ interface DatastoreRepository {
|
||||
class OmnivoreDatastore @Inject constructor(
|
||||
private val context: Context
|
||||
) : DatastoreRepository {
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
|
||||
name = Constants.dataStoreName
|
||||
)
|
||||
|
||||
override suspend fun putString(key: String, value: String) {
|
||||
val preferencesKey = stringPreferencesKey(key)
|
||||
context.dataStore.edit { preferences ->
|
||||
|
||||
@ -15,6 +15,12 @@ import javax.inject.Inject
|
||||
class LoginViewModel @Inject constructor(
|
||||
private val datastoreRepo: DatastoreRepository
|
||||
): ViewModel() {
|
||||
var isLoading by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var errorMessage by mutableStateOf<String?>(null)
|
||||
private set
|
||||
|
||||
val hasAuthTokenLiveData: LiveData<Boolean> = datastoreRepo
|
||||
.hasAuthTokenFlow
|
||||
.distinctUntilChanged()
|
||||
@ -24,17 +30,24 @@ class LoginViewModel @Inject constructor(
|
||||
val emailLogin = RetrofitHelper.getInstance().create(EmailLoginSubmit::class.java)
|
||||
|
||||
viewModelScope.launch {
|
||||
isLoading = true
|
||||
errorMessage = null
|
||||
|
||||
val result = emailLogin.submitEmailLogin(
|
||||
EmailLoginCredentials(email = email, password = password)
|
||||
)
|
||||
|
||||
// TODO: bail early if email is pending
|
||||
// if (result.body()?.pendingEmailVerification == true) {
|
||||
// return void
|
||||
// }
|
||||
isLoading = false
|
||||
|
||||
if (result.body()?.pendingEmailVerification == true) {
|
||||
errorMessage = "Email needs verification"
|
||||
return@launch
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -42,10 +55,6 @@ class LoginViewModel @Inject constructor(
|
||||
DatastoreKeys.omnivoreAuthCookieString, result.body()?.authCookieString!!
|
||||
)
|
||||
}
|
||||
|
||||
datastoreRepo.getString(DatastoreKeys.omnivoreAuthToken)?.let {
|
||||
Log.d(ContentValues.TAG, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,4 +63,8 @@ class LoginViewModel @Inject constructor(
|
||||
datastoreRepo.clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun resetErrorMessage() {
|
||||
errorMessage = null
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
package app.omnivore.omnivore
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -21,10 +23,12 @@ import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.omnivore.omnivore.ui.theme.OmnivoreTheme
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
@ -37,15 +41,24 @@ class MainActivity : ComponentActivity() {
|
||||
OmnivoreTheme {
|
||||
// A surface container using the 'background' color from the theme
|
||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
|
||||
RootView(viewModel)
|
||||
PrimaryView(viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// animate the view up when keyboard appears
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
val rootView = findViewById<View>(android.R.id.content).rootView
|
||||
ViewCompat.setOnApplyWindowInsetsListener(rootView) { _, insets ->
|
||||
val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
|
||||
rootView.setPadding(0, 0, 0, imeHeight)
|
||||
insets
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RootView(viewModel: LoginViewModel) {
|
||||
fun PrimaryView(viewModel: LoginViewModel) {
|
||||
val hasAuthToken: Boolean by viewModel.hasAuthTokenLiveData.observeAsState(false)
|
||||
|
||||
if (hasAuthToken) {
|
||||
@ -74,10 +87,14 @@ fun LoggedInView(viewModel: LoginViewModel) {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CoroutineCreationDuringComposition")
|
||||
@Composable
|
||||
fun LoginView(viewModel: LoginViewModel) {
|
||||
var email by rememberSaveable { mutableStateOf("") }
|
||||
var password by rememberSaveable { mutableStateOf("") }
|
||||
val focusManager = LocalFocusManager.current
|
||||
val snackBarHostState = remember { SnackbarHostState() }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
@ -85,6 +102,7 @@ fun LoginView(viewModel: LoginViewModel) {
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colors.background)
|
||||
.fillMaxSize()
|
||||
.clickable { focusManager.clearFocus() }
|
||||
) {
|
||||
LoginFields(
|
||||
email,
|
||||
@ -93,6 +111,27 @@ fun LoginView(viewModel: LoginViewModel) {
|
||||
onPasswordChange = { password = it },
|
||||
onLoginClick = { viewModel.login(email, password) }
|
||||
)
|
||||
|
||||
// TODO: add a activity indicator (maybe after a delay?)
|
||||
if (viewModel.isLoading) {
|
||||
Text("Loading...")
|
||||
}
|
||||
|
||||
if (viewModel.errorMessage != null) {
|
||||
coroutineScope.launch {
|
||||
val result = snackBarHostState
|
||||
.showSnackbar(
|
||||
viewModel.errorMessage!!,
|
||||
actionLabel = "Dismiss",
|
||||
duration = SnackbarDuration.Indefinite
|
||||
)
|
||||
when (result) {
|
||||
SnackbarResult.ActionPerformed -> viewModel.resetErrorMessage()
|
||||
}
|
||||
}
|
||||
|
||||
SnackbarHost(hostState = snackBarHostState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,7 +181,7 @@ fun LoginFields(
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Please enter an email and password",
|
||||
"Please enter an email address and password.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user