Use single UI state for activity
This commit is contained in:
@@ -12,16 +12,14 @@ import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import gq.kirmanak.mealient.R
|
||||
import gq.kirmanak.mealient.databinding.MainActivityBinding
|
||||
import gq.kirmanak.mealient.ui.auth.AuthenticationState
|
||||
import gq.kirmanak.mealient.ui.auth.AuthenticationState.AUTHORIZED
|
||||
import gq.kirmanak.mealient.ui.auth.AuthenticationState.UNAUTHORIZED
|
||||
import timber.log.Timber
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var binding: MainActivityBinding
|
||||
private val viewModel by viewModels<MainActivityViewModel>()
|
||||
private var lastAuthenticationState: AuthenticationState? = null
|
||||
private val title: String by lazy { getString(R.string.app_name) }
|
||||
private val uiState: MainActivityUiState get() = viewModel.uiState
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -31,7 +29,13 @@ class MainActivity : AppCompatActivity() {
|
||||
setSupportActionBar(binding.toolbar)
|
||||
supportActionBar?.setIcon(R.drawable.ic_toolbar)
|
||||
setToolbarRoundCorner()
|
||||
listenToAuthStatuses()
|
||||
viewModel.uiStateLive.observe(this, ::onUiStateChange)
|
||||
}
|
||||
|
||||
private fun onUiStateChange(uiState: MainActivityUiState) {
|
||||
Timber.v("onUiStateChange() called with: uiState = $uiState")
|
||||
supportActionBar?.title = if (uiState.titleVisible) title else null
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
private fun setToolbarRoundCorner() {
|
||||
@@ -51,22 +55,11 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun listenToAuthStatuses() {
|
||||
Timber.v("listenToAuthStatuses() called")
|
||||
viewModel.authenticationStateLive.observe(this, ::onAuthStateUpdate)
|
||||
}
|
||||
|
||||
private fun onAuthStateUpdate(authState: AuthenticationState) {
|
||||
Timber.v("onAuthStateUpdate() called with: it = $authState")
|
||||
lastAuthenticationState = authState
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
Timber.v("onCreateOptionsMenu() called with: menu = $menu")
|
||||
menuInflater.inflate(R.menu.main_toolbar, menu)
|
||||
menu.findItem(R.id.logout).isVisible = lastAuthenticationState == AUTHORIZED
|
||||
menu.findItem(R.id.login).isVisible = lastAuthenticationState == UNAUTHORIZED
|
||||
menu.findItem(R.id.logout).isVisible = uiState.canShowLogout
|
||||
menu.findItem(R.id.login).isVisible = uiState.canShowLogin
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package gq.kirmanak.mealient.ui.activity
|
||||
|
||||
data class MainActivityUiState(
|
||||
val loginButtonVisible: Boolean = false,
|
||||
val titleVisible: Boolean = true,
|
||||
val isAuthorized: Boolean = false,
|
||||
) {
|
||||
val canShowLogin: Boolean
|
||||
get() = !isAuthorized && loginButtonVisible
|
||||
|
||||
val canShowLogout: Boolean
|
||||
get() = isAuthorized && loginButtonVisible
|
||||
}
|
||||
@@ -1,14 +1,10 @@
|
||||
package gq.kirmanak.mealient.ui.activity
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.*
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||
import gq.kirmanak.mealient.ui.auth.AuthenticationState
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@@ -18,16 +14,22 @@ class MainActivityViewModel @Inject constructor(
|
||||
private val authRepo: AuthRepo,
|
||||
) : ViewModel() {
|
||||
|
||||
private val showLoginButtonFlow = MutableStateFlow(false)
|
||||
var showLoginButton: Boolean by showLoginButtonFlow::value
|
||||
private val _uiState = MutableLiveData(MainActivityUiState())
|
||||
val uiStateLive: LiveData<MainActivityUiState>
|
||||
get() = _uiState.distinctUntilChanged()
|
||||
var uiState: MainActivityUiState
|
||||
get() = checkNotNull(_uiState.value) { "UiState must not be null" }
|
||||
private set(value) = _uiState.postValue(value)
|
||||
|
||||
private val authenticationStateFlow = combine(
|
||||
showLoginButtonFlow,
|
||||
authRepo.isAuthorizedFlow,
|
||||
AuthenticationState::determineState
|
||||
)
|
||||
val authenticationStateLive: LiveData<AuthenticationState>
|
||||
get() = authenticationStateFlow.asLiveData()
|
||||
init {
|
||||
authRepo.isAuthorizedFlow
|
||||
.onEach { isAuthorized -> updateUiState { it.copy(isAuthorized = isAuthorized) } }
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
fun updateUiState(updater: (MainActivityUiState) -> MainActivityUiState) {
|
||||
uiState = updater(uiState)
|
||||
}
|
||||
|
||||
fun logout() {
|
||||
Timber.v("logout() called")
|
||||
|
||||
@@ -2,8 +2,8 @@ package gq.kirmanak.mealient.ui.auth
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
@@ -12,19 +12,20 @@ import gq.kirmanak.mealient.R
|
||||
import gq.kirmanak.mealient.data.network.NetworkError
|
||||
import gq.kirmanak.mealient.databinding.FragmentAuthenticationBinding
|
||||
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
||||
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
|
||||
import timber.log.Timber
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
|
||||
private val binding by viewBinding(FragmentAuthenticationBinding::bind)
|
||||
private val viewModel by viewModels<AuthenticationViewModel>()
|
||||
private val activityViewModel by activityViewModels<MainActivityViewModel>()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
||||
binding.button.setOnClickListener { onLoginClicked() }
|
||||
(requireActivity() as? AppCompatActivity)?.supportActionBar?.title =
|
||||
getString(R.string.app_name)
|
||||
activityViewModel.updateUiState { it.copy(loginButtonVisible = false, titleVisible = true) }
|
||||
viewModel.authenticationResult.observe(viewLifecycleOwner, ::onAuthenticationResult)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package gq.kirmanak.mealient.ui.baseurl
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
@@ -11,6 +12,7 @@ import gq.kirmanak.mealient.R
|
||||
import gq.kirmanak.mealient.data.network.NetworkError
|
||||
import gq.kirmanak.mealient.databinding.FragmentBaseUrlBinding
|
||||
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
||||
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
|
||||
import timber.log.Timber
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -18,12 +20,14 @@ class BaseURLFragment : Fragment(R.layout.fragment_base_url) {
|
||||
|
||||
private val binding by viewBinding(FragmentBaseUrlBinding::bind)
|
||||
private val viewModel by viewModels<BaseURLViewModel>()
|
||||
private val activityViewModel by activityViewModels<MainActivityViewModel>()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
||||
binding.button.setOnClickListener(::onProceedClick)
|
||||
viewModel.checkURLResult.observe(viewLifecycleOwner, ::onCheckURLResult)
|
||||
activityViewModel.updateUiState { it.copy(loginButtonVisible = false, titleVisible = true) }
|
||||
}
|
||||
|
||||
private fun onProceedClick(view: View) {
|
||||
|
||||
@@ -2,20 +2,22 @@ package gq.kirmanak.mealient.ui.disclaimer
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import gq.kirmanak.mealient.R
|
||||
import gq.kirmanak.mealient.databinding.FragmentDisclaimerBinding
|
||||
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
|
||||
import timber.log.Timber
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) {
|
||||
private val binding by viewBinding(FragmentDisclaimerBinding::bind)
|
||||
private val viewModel by viewModels<DisclaimerViewModel>()
|
||||
private val activityViewModel by activityViewModels<MainActivityViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -48,7 +50,6 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) {
|
||||
binding.okay.isClickable = it == 0
|
||||
}
|
||||
viewModel.startCountDown()
|
||||
(requireActivity() as? AppCompatActivity)?.supportActionBar?.title =
|
||||
getString(R.string.app_name)
|
||||
activityViewModel.updateUiState { it.copy(loginButtonVisible = false, titleVisible = true) }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package gq.kirmanak.mealient.ui.recipes
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
@@ -26,9 +25,8 @@ 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")
|
||||
activityViewModel.showLoginButton = true
|
||||
activityViewModel.updateUiState { it.copy(loginButtonVisible = true, titleVisible = false) }
|
||||
setupRecipeAdapter()
|
||||
(requireActivity() as? AppCompatActivity)?.supportActionBar?.title = null
|
||||
}
|
||||
|
||||
private fun navigateToRecipeInfo(recipeSummaryEntity: RecipeSummaryEntity) {
|
||||
@@ -64,6 +62,5 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
||||
Timber.v("onDestroyView() called")
|
||||
// Prevent RV leaking through mObservers list in adapter
|
||||
binding.recipes.adapter = null
|
||||
activityViewModel.showLoginButton = false
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
android:id="@+id/login"
|
||||
android:contentDescription="@string/menu_main_toolbar_content_description_login"
|
||||
android:title="@string/menu_main_toolbar_login"
|
||||
app:showAsAction="never" />
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/logout"
|
||||
|
||||
Reference in New Issue
Block a user