Fix showing login/logout button on initial screens

This commit is contained in:
Kirill Kamakin
2022-04-04 19:34:21 +05:00
parent c98feceab4
commit fb10333c2c
5 changed files with 59 additions and 63 deletions

View File

@@ -51,7 +51,7 @@ class MainActivity : AppCompatActivity() {
private fun listenToAuthStatuses() { private fun listenToAuthStatuses() {
Timber.v("listenToAuthStatuses() called") Timber.v("listenToAuthStatuses() called")
authViewModel.authenticationState.observe(this, ::onAuthStateUpdate) authViewModel.authenticationStateLive.observe(this, ::onAuthStateUpdate)
} }
private fun onAuthStateUpdate(authState: AuthenticationState) { private fun onAuthStateUpdate(authState: AuthenticationState) {
@@ -71,12 +71,9 @@ class MainActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
Timber.v("onOptionsItemSelected() called with: item = $item") Timber.v("onOptionsItemSelected() called with: item = $item")
val result = when (item.itemId) { val result = when (item.itemId) {
R.id.logout -> { R.id.logout, R.id.login -> {
authViewModel.logout() // When user clicks logout they don't want to be authorized
true authViewModel.authRequested = item.itemId == R.id.login
}
R.id.login -> {
authViewModel.enableLoginRequest()
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)

View File

@@ -5,13 +5,12 @@ import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import by.kirich1409.viewbindingdelegate.viewBinding import by.kirich1409.viewbindingdelegate.viewBinding
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import gq.kirmanak.mealient.R import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.data.network.NetworkError.Unauthorized import gq.kirmanak.mealient.data.network.NetworkError
import gq.kirmanak.mealient.databinding.FragmentAuthenticationBinding import gq.kirmanak.mealient.databinding.FragmentAuthenticationBinding
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
import gq.kirmanak.mealient.extensions.executeOnceOnBackPressed import gq.kirmanak.mealient.extensions.executeOnceOnBackPressed
@@ -22,14 +21,10 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
private val binding by viewBinding(FragmentAuthenticationBinding::bind) private val binding by viewBinding(FragmentAuthenticationBinding::bind)
private val viewModel by activityViewModels<AuthenticationViewModel>() private val viewModel by activityViewModels<AuthenticationViewModel>()
private val authStatuses: LiveData<AuthenticationState>
get() = viewModel.authenticationState
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState") Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState")
authStatuses.observe(this, ::onAuthStatusChange) executeOnceOnBackPressed { viewModel.authRequested = false }
executeOnceOnBackPressed { viewModel.disableLoginRequest() }
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -38,13 +33,7 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
binding.button.setOnClickListener { onLoginClicked() } binding.button.setOnClickListener { onLoginClicked() }
(requireActivity() as? AppCompatActivity)?.supportActionBar?.title = (requireActivity() as? AppCompatActivity)?.supportActionBar?.title =
getString(R.string.app_name) getString(R.string.app_name)
} viewModel.authenticationResult.observe(viewLifecycleOwner, ::onAuthenticationResult)
private fun onAuthStatusChange(isAuthenticated: AuthenticationState) {
Timber.v("onAuthStatusChange() called with: isAuthenticated = $isAuthenticated")
if (isAuthenticated == AuthenticationState.AUTHORIZED) {
findNavController().popBackStack()
}
} }
private fun onLoginClicked(): Unit = with(binding) { private fun onLoginClicked(): Unit = with(binding) {
@@ -59,14 +48,21 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
} ?: return } ?: return
button.isClickable = false button.isClickable = false
viewModel.authenticate(email, pass).observe(viewLifecycleOwner) { viewModel.authenticate(email, pass)
Timber.d("onLoginClicked: result $it") }
passwordInputLayout.error = when (it.exceptionOrNull()) {
is Unauthorized -> getString(R.string.fragment_authentication_credentials_incorrect)
else -> null
}
button.isClickable = true private fun onAuthenticationResult(result: Result<Unit>) {
Timber.v("onAuthenticationResult() called with: result = $result")
if (result.isSuccess) {
findNavController().popBackStack()
return
} }
binding.passwordInputLayout.error = when (result.exceptionOrNull()) {
is NetworkError.Unauthorized -> getString(R.string.fragment_authentication_credentials_incorrect)
else -> null
}
binding.button.isClickable = true
} }
} }

View File

@@ -5,16 +5,19 @@ import timber.log.Timber
enum class AuthenticationState { enum class AuthenticationState {
AUTHORIZED, AUTHORIZED,
AUTH_REQUESTED, AUTH_REQUESTED,
UNAUTHORIZED; UNAUTHORIZED,
UNKNOWN;
companion object { companion object {
fun determineState( fun determineState(
isLoginRequested: Boolean, isLoginRequested: Boolean,
showLoginButton: Boolean,
isAuthorized: Boolean, isAuthorized: Boolean,
): AuthenticationState { ): AuthenticationState {
Timber.v("determineState() called with: isLoginRequested = $isLoginRequested, isAuthorized = $isAuthorized") Timber.v("determineState() called with: isLoginRequested = $isLoginRequested, showLoginButton = $showLoginButton, isAuthorized = $isAuthorized")
val result = when { val result = when {
!showLoginButton -> UNKNOWN
isAuthorized -> AUTHORIZED isAuthorized -> AUTHORIZED
isLoginRequested -> AUTH_REQUESTED isLoginRequested -> AUTH_REQUESTED
else -> UNAUTHORIZED else -> UNAUTHORIZED

View File

@@ -4,6 +4,7 @@ import androidx.lifecycle.*
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import gq.kirmanak.mealient.data.auth.AuthRepo import gq.kirmanak.mealient.data.auth.AuthRepo
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@@ -14,48 +15,44 @@ class AuthenticationViewModel @Inject constructor(
private val authRepo: AuthRepo, private val authRepo: AuthRepo,
) : ViewModel() { ) : ViewModel() {
private val loginRequestsFlow = MutableStateFlow(false) private val authRequestsFlow = MutableStateFlow(false)
val authenticationState: LiveData<AuthenticationState> = loginRequestsFlow.combine( private val showLoginButtonFlow = MutableStateFlow(false)
flow = authRepo.isAuthorizedFlow, private val authenticationStateFlow = combine(
transform = AuthenticationState::determineState authRequestsFlow,
).asLiveData() showLoginButtonFlow,
authRepo.isAuthorizedFlow,
AuthenticationState::determineState
)
val authenticationStateLive: LiveData<AuthenticationState>
get() = authenticationStateFlow.asLiveData()
var authRequested: Boolean by authRequestsFlow::value
var showLoginButton: Boolean by showLoginButtonFlow::value
fun authenticate(username: String, password: String): LiveData<Result<Unit>> { private val _authenticationResult = MutableLiveData<Result<Unit>>()
val authenticationResult: LiveData<Result<Unit>>
get() = _authenticationResult
init {
viewModelScope.launch {
authRequestsFlow.collect { isRequested ->
// Clear auth token on logout request
if (!isRequested) authRepo.logout()
}
}
}
fun authenticate(username: String, password: String) {
Timber.v("authenticate() called with: username = $username, password = $password") Timber.v("authenticate() called with: username = $username, password = $password")
val result = MutableLiveData<Result<Unit>>()
viewModelScope.launch { viewModelScope.launch {
runCatching { runCatching {
authRepo.authenticate(username, password) authRepo.authenticate(username, password)
}.onFailure { }.onFailure {
Timber.e(it, "authenticate: can't authenticate") Timber.e(it, "authenticate: can't authenticate")
result.value = Result.failure(it) _authenticationResult.value = Result.failure(it)
}.onSuccess { }.onSuccess {
Timber.d("authenticate: authenticated") Timber.d("authenticate: authenticated")
result.value = Result.success(Unit) _authenticationResult.value = Result.success(Unit)
} }
} }
return result
}
fun logout() {
Timber.v("logout() called")
viewModelScope.launch {
loginRequestsFlow.emit(false)
authRepo.logout()
}
}
fun enableLoginRequest() {
Timber.v("enableLoginRequest() called")
updateIsLoginRequested(true)
}
fun disableLoginRequest() {
Timber.v("disableLoginRequest() called")
updateIsLoginRequested(false)
}
private fun updateIsLoginRequested(isRequested: Boolean) {
viewModelScope.launch { loginRequestsFlow.emit(isRequested) }
} }
} }

View File

@@ -27,7 +27,8 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
authViewModel.authenticationState.observe(this, ::onAuthStateChange) Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState")
authViewModel.authenticationStateLive.observe(this, ::onAuthStateChange)
} }
private fun onAuthStateChange(authenticationState: AuthenticationState) { private fun onAuthStateChange(authenticationState: AuthenticationState) {
@@ -40,6 +41,7 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState") Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
authViewModel.showLoginButton = true
setupRecipeAdapter() setupRecipeAdapter()
(requireActivity() as? AppCompatActivity)?.supportActionBar?.title = null (requireActivity() as? AppCompatActivity)?.supportActionBar?.title = null
} }
@@ -78,5 +80,6 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
Timber.v("onDestroyView() called") Timber.v("onDestroyView() called")
// Prevent RV leaking through mObservers list in adapter // Prevent RV leaking through mObservers list in adapter
binding.recipes.adapter = null binding.recipes.adapter = null
authViewModel.showLoginButton = false
} }
} }