From fb10333c2c8d3975cee68f6c32d0321ec2f11e00 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Mon, 4 Apr 2022 19:34:21 +0500 Subject: [PATCH] Fix showing login/logout button on initial screens --- .../java/gq/kirmanak/mealient/MainActivity.kt | 11 ++-- .../ui/auth/AuthenticationFragment.kt | 38 ++++++------ .../mealient/ui/auth/AuthenticationState.kt | 7 ++- .../ui/auth/AuthenticationViewModel.kt | 61 +++++++++---------- .../mealient/ui/recipes/RecipesFragment.kt | 5 +- 5 files changed, 59 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/gq/kirmanak/mealient/MainActivity.kt b/app/src/main/java/gq/kirmanak/mealient/MainActivity.kt index 4d79231..1446e96 100644 --- a/app/src/main/java/gq/kirmanak/mealient/MainActivity.kt +++ b/app/src/main/java/gq/kirmanak/mealient/MainActivity.kt @@ -51,7 +51,7 @@ class MainActivity : AppCompatActivity() { private fun listenToAuthStatuses() { Timber.v("listenToAuthStatuses() called") - authViewModel.authenticationState.observe(this, ::onAuthStateUpdate) + authViewModel.authenticationStateLive.observe(this, ::onAuthStateUpdate) } private fun onAuthStateUpdate(authState: AuthenticationState) { @@ -71,12 +71,9 @@ class MainActivity : AppCompatActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { Timber.v("onOptionsItemSelected() called with: item = $item") val result = when (item.itemId) { - R.id.logout -> { - authViewModel.logout() - true - } - R.id.login -> { - authViewModel.enableLoginRequest() + R.id.logout, R.id.login -> { + // When user clicks logout they don't want to be authorized + authViewModel.authRequested = item.itemId == R.id.login true } else -> super.onOptionsItemSelected(item) diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt index 48d61f0..98e694f 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt @@ -5,13 +5,12 @@ import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.LiveData import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import by.kirich1409.viewbindingdelegate.viewBinding import dagger.hilt.android.AndroidEntryPoint 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.extensions.checkIfInputIsEmpty 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 viewModel by activityViewModels() - private val authStatuses: LiveData - get() = viewModel.authenticationState - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState") - authStatuses.observe(this, ::onAuthStatusChange) - executeOnceOnBackPressed { viewModel.disableLoginRequest() } + executeOnceOnBackPressed { viewModel.authRequested = false } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -38,13 +33,7 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) { binding.button.setOnClickListener { onLoginClicked() } (requireActivity() as? AppCompatActivity)?.supportActionBar?.title = getString(R.string.app_name) - } - - private fun onAuthStatusChange(isAuthenticated: AuthenticationState) { - Timber.v("onAuthStatusChange() called with: isAuthenticated = $isAuthenticated") - if (isAuthenticated == AuthenticationState.AUTHORIZED) { - findNavController().popBackStack() - } + viewModel.authenticationResult.observe(viewLifecycleOwner, ::onAuthenticationResult) } private fun onLoginClicked(): Unit = with(binding) { @@ -59,14 +48,21 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) { } ?: return button.isClickable = false - viewModel.authenticate(email, pass).observe(viewLifecycleOwner) { - Timber.d("onLoginClicked: result $it") - passwordInputLayout.error = when (it.exceptionOrNull()) { - is Unauthorized -> getString(R.string.fragment_authentication_credentials_incorrect) - else -> null - } + viewModel.authenticate(email, pass) + } - button.isClickable = true + private fun onAuthenticationResult(result: Result) { + 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 } } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationState.kt b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationState.kt index b9b4a6d..70ec901 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationState.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationState.kt @@ -5,16 +5,19 @@ import timber.log.Timber enum class AuthenticationState { AUTHORIZED, AUTH_REQUESTED, - UNAUTHORIZED; + UNAUTHORIZED, + UNKNOWN; companion object { fun determineState( isLoginRequested: Boolean, + showLoginButton: Boolean, isAuthorized: Boolean, ): AuthenticationState { - Timber.v("determineState() called with: isLoginRequested = $isLoginRequested, isAuthorized = $isAuthorized") + Timber.v("determineState() called with: isLoginRequested = $isLoginRequested, showLoginButton = $showLoginButton, isAuthorized = $isAuthorized") val result = when { + !showLoginButton -> UNKNOWN isAuthorized -> AUTHORIZED isLoginRequested -> AUTH_REQUESTED else -> UNAUTHORIZED diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationViewModel.kt index 472078d..51ee7ee 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel import gq.kirmanak.mealient.data.auth.AuthRepo import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import timber.log.Timber @@ -14,48 +15,44 @@ class AuthenticationViewModel @Inject constructor( private val authRepo: AuthRepo, ) : ViewModel() { - private val loginRequestsFlow = MutableStateFlow(false) - val authenticationState: LiveData = loginRequestsFlow.combine( - flow = authRepo.isAuthorizedFlow, - transform = AuthenticationState::determineState - ).asLiveData() + private val authRequestsFlow = MutableStateFlow(false) + private val showLoginButtonFlow = MutableStateFlow(false) + private val authenticationStateFlow = combine( + authRequestsFlow, + showLoginButtonFlow, + authRepo.isAuthorizedFlow, + AuthenticationState::determineState + ) + val authenticationStateLive: LiveData + get() = authenticationStateFlow.asLiveData() + var authRequested: Boolean by authRequestsFlow::value + var showLoginButton: Boolean by showLoginButtonFlow::value - fun authenticate(username: String, password: String): LiveData> { + private val _authenticationResult = MutableLiveData>() + val authenticationResult: LiveData> + 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") - val result = MutableLiveData>() viewModelScope.launch { runCatching { authRepo.authenticate(username, password) }.onFailure { Timber.e(it, "authenticate: can't authenticate") - result.value = Result.failure(it) + _authenticationResult.value = Result.failure(it) }.onSuccess { 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) } } } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipesFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipesFragment.kt index 4e68417..01e8cfb 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipesFragment.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipesFragment.kt @@ -27,7 +27,8 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) { override fun onCreate(savedInstanceState: Bundle?) { 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) { @@ -40,6 +41,7 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState") + authViewModel.showLoginButton = true setupRecipeAdapter() (requireActivity() as? AppCompatActivity)?.supportActionBar?.title = null } @@ -78,5 +80,6 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) { Timber.v("onDestroyView() called") // Prevent RV leaking through mObservers list in adapter binding.recipes.adapter = null + authViewModel.showLoginButton = false } } \ No newline at end of file