From e7620400b8d5fc05e6ca54b19a1b973383e78634 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 9 Apr 2022 00:47:50 +0500 Subject: [PATCH 1/6] Use single UI state for activity --- .../mealient/ui/activity/MainActivity.kt | 29 ++++++---------- .../ui/activity/MainActivityUiState.kt | 13 +++++++ .../ui/activity/MainActivityViewModel.kt | 34 ++++++++++--------- .../ui/auth/AuthenticationFragment.kt | 7 ++-- .../mealient/ui/baseurl/BaseURLFragment.kt | 4 +++ .../ui/disclaimer/DisclaimerFragment.kt | 7 ++-- .../mealient/ui/recipes/RecipesFragment.kt | 5 +-- app/src/main/res/menu/main_toolbar.xml | 2 +- 8 files changed, 56 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityUiState.kt diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivity.kt b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivity.kt index 478002d..08aac96 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivity.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivity.kt @@ -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() - 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 } diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityUiState.kt b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityUiState.kt new file mode 100644 index 0000000..c9b5e82 --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityUiState.kt @@ -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 +} diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModel.kt index 6815084..7290723 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModel.kt @@ -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 + 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 - 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") 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 1aa024c..e0d5743 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 @@ -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() + private val activityViewModel by activityViewModels() 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) } diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLFragment.kt index d30441e..a3f5513 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLFragment.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLFragment.kt @@ -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() + private val activityViewModel by activityViewModels() 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) { diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerFragment.kt index b9ac72e..f306505 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerFragment.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerFragment.kt @@ -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() + private val activityViewModel by activityViewModels() 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) } } } \ 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 14ba126..a961ea4 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 @@ -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 } } \ No newline at end of file diff --git a/app/src/main/res/menu/main_toolbar.xml b/app/src/main/res/menu/main_toolbar.xml index bad8b2b..6d9ba04 100644 --- a/app/src/main/res/menu/main_toolbar.xml +++ b/app/src/main/res/menu/main_toolbar.xml @@ -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" /> Date: Sat, 9 Apr 2022 03:38:30 +0500 Subject: [PATCH 2/6] Simplify RecipeInfoFragment code --- .../ui/recipes/info/RecipeInfoFragment.kt | 43 ++++++++++--------- ...istsVisibility.kt => RecipeInfoUiState.kt} | 5 ++- .../ui/recipes/info/RecipeInfoViewModel.kt | 32 ++++---------- .../recipes/info/RecipeIngredientsAdapter.kt | 5 +-- .../recipes/info/RecipeInstructionsAdapter.kt | 5 +-- 5 files changed, 37 insertions(+), 53 deletions(-) rename app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/{RecipeInfoListsVisibility.kt => RecipeInfoUiState.kt} (51%) diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoFragment.kt index 2613863..fd74d8a 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoFragment.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoFragment.kt @@ -15,19 +15,15 @@ import dagger.hilt.android.AndroidEntryPoint import gq.kirmanak.mealient.R import gq.kirmanak.mealient.databinding.FragmentRecipeInfoBinding import timber.log.Timber -import javax.inject.Inject @AndroidEntryPoint class RecipeInfoFragment : BottomSheetDialogFragment() { + private val binding by viewBinding(FragmentRecipeInfoBinding::bind) private val arguments by navArgs() private val viewModel by viewModels() - - @Inject - lateinit var ingredientsAdapter: RecipeIngredientsAdapter - - @Inject - lateinit var instructionsAdapter: RecipeInstructionsAdapter + private val ingredientsAdapter = RecipeIngredientsAdapter() + private val instructionsAdapter = RecipeInstructionsAdapter() override fun onCreateView( inflater: LayoutInflater, @@ -42,22 +38,27 @@ class RecipeInfoFragment : BottomSheetDialogFragment() { super.onViewCreated(view, savedInstanceState) Timber.v("onViewCreated() called") - binding.ingredientsList.adapter = ingredientsAdapter - binding.instructionsList.adapter = instructionsAdapter - - viewModel.loadRecipeImage(binding.image, arguments.recipeSlug) - viewModel.loadRecipeInfo(arguments.recipeId, arguments.recipeSlug) - - viewModel.recipeInfo.observe(viewLifecycleOwner) { - Timber.d("onViewCreated: full info $it") - binding.title.text = it.recipeSummaryEntity.name - binding.description.text = it.recipeSummaryEntity.description + with(binding) { + ingredientsList.adapter = ingredientsAdapter + instructionsList.adapter = instructionsAdapter } - viewModel.listsVisibility.observe(viewLifecycleOwner) { - Timber.d("onViewCreated: lists visibility $it") - binding.ingredientsHolder.isVisible = it.areIngredientsVisible - binding.instructionsGroup.isVisible = it.areInstructionsVisible + with(viewModel) { + loadRecipeImage(binding.image, arguments.recipeSlug) + loadRecipeInfo(arguments.recipeId, arguments.recipeSlug) + uiState.observe(viewLifecycleOwner, ::onUiStateChange) + } + } + + private fun onUiStateChange(uiState: RecipeInfoUiState) = with(binding) { + Timber.v("onUiStateChange() called") + ingredientsHolder.isVisible = uiState.areIngredientsVisible + instructionsGroup.isVisible = uiState.areInstructionsVisible + uiState.recipeInfo?.let { + title.text = it.recipeSummaryEntity.name + description.text = it.recipeSummaryEntity.description + ingredientsAdapter.submitList(it.recipeIngredients) + instructionsAdapter.submitList(it.recipeInstructions) } } diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoListsVisibility.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoUiState.kt similarity index 51% rename from app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoListsVisibility.kt rename to app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoUiState.kt index 84bee8d..e6358df 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoListsVisibility.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoUiState.kt @@ -1,6 +1,9 @@ package gq.kirmanak.mealient.ui.recipes.info -data class RecipeInfoListsVisibility( +import gq.kirmanak.mealient.data.recipes.impl.FullRecipeInfo + +data class RecipeInfoUiState( val areIngredientsVisible: Boolean = false, val areInstructionsVisible: Boolean = false, + val recipeInfo: FullRecipeInfo? = null, ) diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt index 77a0a83..34bf78e 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt @@ -8,28 +8,19 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import gq.kirmanak.mealient.data.recipes.RecipeImageLoader import gq.kirmanak.mealient.data.recipes.RecipeRepo -import gq.kirmanak.mealient.data.recipes.impl.FullRecipeInfo import gq.kirmanak.mealient.extensions.runCatchingExceptCancel import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @HiltViewModel -class RecipeInfoViewModel -@Inject -constructor( +class RecipeInfoViewModel @Inject constructor( private val recipeRepo: RecipeRepo, private val recipeImageLoader: RecipeImageLoader, - private val recipeIngredientsAdapter: RecipeIngredientsAdapter, - private val recipeInstructionsAdapter: RecipeInstructionsAdapter, ) : ViewModel() { - private val _recipeInfo = MutableLiveData() - val recipeInfo: LiveData - get() = _recipeInfo - private val _listsVisibility = MutableLiveData(RecipeInfoListsVisibility()) - val listsVisibility: LiveData - get() = _listsVisibility + private val _uiState = MutableLiveData(RecipeInfoUiState()) + val uiState: LiveData get() = _uiState fun loadRecipeImage(view: ImageView, recipeSlug: String) { Timber.v("loadRecipeImage() called with: view = $view, recipeSlug = $recipeSlug") @@ -38,21 +29,16 @@ constructor( fun loadRecipeInfo(recipeId: Long, recipeSlug: String) { Timber.v("loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug") - _listsVisibility.value = RecipeInfoListsVisibility() - recipeIngredientsAdapter.submitList(null) - recipeInstructionsAdapter.submitList(null) + _uiState.value = RecipeInfoUiState() viewModelScope.launch { runCatchingExceptCancel { recipeRepo.loadRecipeInfo(recipeId, recipeSlug) } .onSuccess { Timber.d("loadRecipeInfo: received recipe info = $it") - _recipeInfo.value = it - recipeIngredientsAdapter.submitList(it.recipeIngredients) - recipeInstructionsAdapter.submitList(it.recipeInstructions) - _listsVisibility.value = - RecipeInfoListsVisibility( - areIngredientsVisible = it.recipeIngredients.isNotEmpty(), - areInstructionsVisible = it.recipeInstructions.isNotEmpty() - ) + _uiState.value = RecipeInfoUiState( + areIngredientsVisible = it.recipeIngredients.isNotEmpty(), + areInstructionsVisible = it.recipeInstructions.isNotEmpty(), + recipeInfo = it, + ) } .onFailure { Timber.e(it, "loadRecipeInfo: can't load recipe info") } } diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeIngredientsAdapter.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeIngredientsAdapter.kt index 2ca4939..1d6215f 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeIngredientsAdapter.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeIngredientsAdapter.kt @@ -9,11 +9,8 @@ import gq.kirmanak.mealient.data.recipes.db.entity.RecipeIngredientEntity import gq.kirmanak.mealient.databinding.ViewHolderIngredientBinding import gq.kirmanak.mealient.ui.recipes.info.RecipeIngredientsAdapter.RecipeIngredientViewHolder import timber.log.Timber -import javax.inject.Inject -import javax.inject.Singleton -@Singleton -class RecipeIngredientsAdapter @Inject constructor() : +class RecipeIngredientsAdapter : ListAdapter(RecipeIngredientDiffCallback) { class RecipeIngredientViewHolder( diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInstructionsAdapter.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInstructionsAdapter.kt index 6f32f87..b84cd1b 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInstructionsAdapter.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInstructionsAdapter.kt @@ -10,11 +10,8 @@ import gq.kirmanak.mealient.data.recipes.db.entity.RecipeInstructionEntity import gq.kirmanak.mealient.databinding.ViewHolderInstructionBinding import gq.kirmanak.mealient.ui.recipes.info.RecipeInstructionsAdapter.RecipeInstructionViewHolder import timber.log.Timber -import javax.inject.Inject -import javax.inject.Singleton -@Singleton -class RecipeInstructionsAdapter @Inject constructor() : +class RecipeInstructionsAdapter : ListAdapter(RecipeInstructionDiffCallback) { private object RecipeInstructionDiffCallback : From 15b4c2b91a2d5395d3c413f0676a1258494434b3 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 9 Apr 2022 03:58:08 +0500 Subject: [PATCH 3/6] Replace if with when in SplashViewModel --- .../gq/kirmanak/mealient/ui/splash/SplashViewModel.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/splash/SplashViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/splash/SplashViewModel.kt index c845740..8fe4eb4 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/splash/SplashViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/splash/SplashViewModel.kt @@ -23,12 +23,11 @@ class SplashViewModel @Inject constructor( init { viewModelScope.launch { delay(1000) - _nextDestination.value = if (!disclaimerStorage.isDisclaimerAccepted()) - SplashFragmentDirections.actionSplashFragmentToDisclaimerFragment() - else if (baseURLStorage.getBaseURL() == null) - SplashFragmentDirections.actionSplashFragmentToBaseURLFragment() - else - SplashFragmentDirections.actionSplashFragmentToRecipesFragment() + _nextDestination.value = when { + !disclaimerStorage.isDisclaimerAccepted() -> SplashFragmentDirections.actionSplashFragmentToDisclaimerFragment() + baseURLStorage.getBaseURL() == null -> SplashFragmentDirections.actionSplashFragmentToBaseURLFragment() + else -> SplashFragmentDirections.actionSplashFragmentToRecipesFragment() + } } } } From a5ae5e91cdb38d72b7e0ef7e418d17f6bb2f72cc Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 9 Apr 2022 14:59:56 +0500 Subject: [PATCH 4/6] Disable Picasso logging --- app/build.gradle | 2 ++ .../mealient/data/recipes/impl/RecipeImageLoaderImpl.kt | 4 ++-- app/src/main/java/gq/kirmanak/mealient/di/RecipeModule.kt | 2 +- app/src/main/java/gq/kirmanak/mealient/di/UiModule.kt | 6 +++--- .../gq/kirmanak/mealient/ui/{ => images}/ImageLoader.kt | 2 +- .../mealient/ui/{picasso => images}/ImageLoaderPicasso.kt | 3 +-- .../mealient/ui/{picasso => images}/PicassoBuilder.kt | 4 ++-- .../mealient/{data => ui}/recipes/RecipeImageLoader.kt | 2 +- .../java/gq/kirmanak/mealient/ui/recipes/RecipeViewModel.kt | 1 - .../mealient/ui/recipes/info/RecipeInfoViewModel.kt | 2 +- .../mealient/data/recipes/impl/RecipeImageLoaderImplTest.kt | 2 +- 11 files changed, 15 insertions(+), 15 deletions(-) rename app/src/main/java/gq/kirmanak/mealient/ui/{ => images}/ImageLoader.kt (82%) rename app/src/main/java/gq/kirmanak/mealient/ui/{picasso => images}/ImageLoaderPicasso.kt (90%) rename app/src/main/java/gq/kirmanak/mealient/ui/{picasso => images}/PicassoBuilder.kt (92%) rename app/src/main/java/gq/kirmanak/mealient/{data => ui}/recipes/RecipeImageLoader.kt (75%) diff --git a/app/build.gradle b/app/build.gradle index e2b0af1..a3e6c49 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,6 +22,8 @@ android { arguments += ["room.schemaLocation": "$projectDir/schemas".toString()] } } + + buildConfigField "Boolean", "DEBUG_PICASSO", "false" } signingConfigs { diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageLoaderImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageLoaderImpl.kt index 97f31a8..85f1bb2 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageLoaderImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageLoaderImpl.kt @@ -4,8 +4,8 @@ import android.widget.ImageView import androidx.annotation.VisibleForTesting import gq.kirmanak.mealient.R import gq.kirmanak.mealient.data.baseurl.BaseURLStorage -import gq.kirmanak.mealient.data.recipes.RecipeImageLoader -import gq.kirmanak.mealient.ui.ImageLoader +import gq.kirmanak.mealient.ui.images.ImageLoader +import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import timber.log.Timber import javax.inject.Inject diff --git a/app/src/main/java/gq/kirmanak/mealient/di/RecipeModule.kt b/app/src/main/java/gq/kirmanak/mealient/di/RecipeModule.kt index 8b3ad9e..ad4dd94 100644 --- a/app/src/main/java/gq/kirmanak/mealient/di/RecipeModule.kt +++ b/app/src/main/java/gq/kirmanak/mealient/di/RecipeModule.kt @@ -10,7 +10,6 @@ import gq.kirmanak.mealient.data.baseurl.BaseURLStorage import gq.kirmanak.mealient.data.network.RetrofitBuilder import gq.kirmanak.mealient.data.network.ServiceFactory import gq.kirmanak.mealient.data.network.createServiceFactory -import gq.kirmanak.mealient.data.recipes.RecipeImageLoader import gq.kirmanak.mealient.data.recipes.RecipeRepo import gq.kirmanak.mealient.data.recipes.db.RecipeStorage import gq.kirmanak.mealient.data.recipes.db.RecipeStorageImpl @@ -19,6 +18,7 @@ import gq.kirmanak.mealient.data.recipes.impl.RecipeRepoImpl import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import gq.kirmanak.mealient.data.recipes.network.RecipeDataSourceImpl import gq.kirmanak.mealient.data.recipes.network.RecipeService +import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader import kotlinx.serialization.json.Json import okhttp3.OkHttpClient import javax.inject.Named diff --git a/app/src/main/java/gq/kirmanak/mealient/di/UiModule.kt b/app/src/main/java/gq/kirmanak/mealient/di/UiModule.kt index 6c4cd61..1d31cf5 100644 --- a/app/src/main/java/gq/kirmanak/mealient/di/UiModule.kt +++ b/app/src/main/java/gq/kirmanak/mealient/di/UiModule.kt @@ -6,9 +6,9 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import gq.kirmanak.mealient.ui.ImageLoader -import gq.kirmanak.mealient.ui.picasso.ImageLoaderPicasso -import gq.kirmanak.mealient.ui.picasso.PicassoBuilder +import gq.kirmanak.mealient.ui.images.ImageLoader +import gq.kirmanak.mealient.ui.images.ImageLoaderPicasso +import gq.kirmanak.mealient.ui.images.PicassoBuilder import javax.inject.Singleton @Module diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/ImageLoader.kt b/app/src/main/java/gq/kirmanak/mealient/ui/images/ImageLoader.kt similarity index 82% rename from app/src/main/java/gq/kirmanak/mealient/ui/ImageLoader.kt rename to app/src/main/java/gq/kirmanak/mealient/ui/images/ImageLoader.kt index 2658f1b..cc33ed3 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/ImageLoader.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/images/ImageLoader.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.ui +package gq.kirmanak.mealient.ui.images import android.widget.ImageView import androidx.annotation.DrawableRes diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/picasso/ImageLoaderPicasso.kt b/app/src/main/java/gq/kirmanak/mealient/ui/images/ImageLoaderPicasso.kt similarity index 90% rename from app/src/main/java/gq/kirmanak/mealient/ui/picasso/ImageLoaderPicasso.kt rename to app/src/main/java/gq/kirmanak/mealient/ui/images/ImageLoaderPicasso.kt index 8779d4d..5dc35ec 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/picasso/ImageLoaderPicasso.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/images/ImageLoaderPicasso.kt @@ -1,8 +1,7 @@ -package gq.kirmanak.mealient.ui.picasso +package gq.kirmanak.mealient.ui.images import android.widget.ImageView import com.squareup.picasso.Picasso -import gq.kirmanak.mealient.ui.ImageLoader import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/picasso/PicassoBuilder.kt b/app/src/main/java/gq/kirmanak/mealient/ui/images/PicassoBuilder.kt similarity index 92% rename from app/src/main/java/gq/kirmanak/mealient/ui/picasso/PicassoBuilder.kt rename to app/src/main/java/gq/kirmanak/mealient/ui/images/PicassoBuilder.kt index a2cea53..9933996 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/picasso/PicassoBuilder.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/images/PicassoBuilder.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.ui.picasso +package gq.kirmanak.mealient.ui.images import android.content.Context import com.squareup.picasso.OkHttp3Downloader @@ -22,7 +22,7 @@ class PicassoBuilder @Inject constructor( Timber.v("buildPicasso() called") val builder = Picasso.Builder(context) builder.downloader(OkHttp3Downloader(okHttpClient)) - if (BuildConfig.DEBUG) { + if (BuildConfig.DEBUG_PICASSO) { builder.loggingEnabled(true) builder.indicatorsEnabled(true) builder.listener { _, uri, exception -> diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/RecipeImageLoader.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipeImageLoader.kt similarity index 75% rename from app/src/main/java/gq/kirmanak/mealient/data/recipes/RecipeImageLoader.kt rename to app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipeImageLoader.kt index 0604e72..2afa981 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/RecipeImageLoader.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipeImageLoader.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.data.recipes +package gq.kirmanak.mealient.ui.recipes import android.widget.ImageView diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipeViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipeViewModel.kt index 6ff9334..6b71714 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipeViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipeViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.cachedIn import dagger.hilt.android.lifecycle.HiltViewModel -import gq.kirmanak.mealient.data.recipes.RecipeImageLoader import gq.kirmanak.mealient.data.recipes.RecipeRepo import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity import kotlinx.coroutines.launch diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt index 34bf78e..5bc7242 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt @@ -6,9 +6,9 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import gq.kirmanak.mealient.data.recipes.RecipeImageLoader import gq.kirmanak.mealient.data.recipes.RecipeRepo import gq.kirmanak.mealient.extensions.runCatchingExceptCancel +import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject diff --git a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageLoaderImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageLoaderImplTest.kt index fc528b8..6cedcc9 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageLoaderImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageLoaderImplTest.kt @@ -2,7 +2,7 @@ package gq.kirmanak.mealient.data.recipes.impl import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.baseurl.BaseURLStorage -import gq.kirmanak.mealient.ui.ImageLoader +import gq.kirmanak.mealient.ui.images.ImageLoader import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.impl.annotations.MockK From d52c428ea59c0a31ec64e68295943de121596711 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 9 Apr 2022 15:00:11 +0500 Subject: [PATCH 5/6] Disable extra OkHttp logging --- app/build.gradle | 1 + app/src/debug/java/gq/kirmanak/mealient/di/DebugModule.kt | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a3e6c49..20b53d5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,6 +24,7 @@ android { } buildConfigField "Boolean", "DEBUG_PICASSO", "false" + buildConfigField "Boolean", "LOG_NETWORK", "false" } signingConfigs { diff --git a/app/src/debug/java/gq/kirmanak/mealient/di/DebugModule.kt b/app/src/debug/java/gq/kirmanak/mealient/di/DebugModule.kt index d35f6e6..813f53c 100644 --- a/app/src/debug/java/gq/kirmanak/mealient/di/DebugModule.kt +++ b/app/src/debug/java/gq/kirmanak/mealient/di/DebugModule.kt @@ -16,6 +16,7 @@ import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet +import gq.kirmanak.mealient.BuildConfig import leakcanary.LeakCanary import okhttp3.Interceptor import okhttp3.logging.HttpLoggingInterceptor @@ -30,7 +31,10 @@ object DebugModule { @IntoSet fun provideLoggingInterceptor(): Interceptor { val interceptor = HttpLoggingInterceptor { message -> Timber.tag("OkHttp").v(message) } - interceptor.level = HttpLoggingInterceptor.Level.BODY + interceptor.level = when { + BuildConfig.LOG_NETWORK -> HttpLoggingInterceptor.Level.BODY + else -> HttpLoggingInterceptor.Level.BASIC + } return interceptor } From 9db89965c0cf3a80e63307a62ece027392081b92 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 9 Apr 2022 16:48:30 +0500 Subject: [PATCH 6/6] Start migration to Material 3 Theme was generated here https://material-foundation.github.io/material-theme-builder/#/custom Primary: BB86FC Secondary: 655A70 Tertiary: 805159 Neutral: 605D62 --- app/src/main/AndroidManifest.xml | 2 +- .../res/layout/fragment_authentication.xml | 14 ++-- .../main/res/layout/fragment_disclaimer.xml | 14 ++-- .../main/res/layout/fragment_recipe_info.xml | 2 +- .../res/layout/view_holder_instruction.xml | 2 +- app/src/main/res/values-night/themes.xml | 44 ++++++++----- app/src/main/res/values/colors.xml | 66 ++++++++++++++++--- app/src/main/res/values/styles.xml | 8 +-- app/src/main/res/values/themes.xml | 45 ++++++++----- 9 files changed, 133 insertions(+), 64 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a72e4e7..3c9d105 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,7 @@ android:roundIcon="@mipmap/ic_launcher" android:supportsRtl="true" tools:ignore="UnusedAttribute" - android:theme="@style/Theme.Mealient"> + android:theme="@style/AppTheme"> diff --git a/app/src/main/res/layout/fragment_authentication.xml b/app/src/main/res/layout/fragment_authentication.xml index 8355ea2..098f119 100644 --- a/app/src/main/res/layout/fragment_authentication.xml +++ b/app/src/main/res/layout/fragment_authentication.xml @@ -42,11 +42,11 @@