Merge pull request #41 from kirmanak/material-3

Start migration to material 3
This commit is contained in:
Kirill Kamakin
2022-04-09 17:36:28 +05:00
committed by GitHub
34 changed files with 252 additions and 184 deletions

View File

@@ -22,6 +22,9 @@ android {
arguments += ["room.schemaLocation": "$projectDir/schemas".toString()] arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
} }
} }
buildConfigField "Boolean", "DEBUG_PICASSO", "false"
buildConfigField "Boolean", "LOG_NETWORK", "false"
} }
signingConfigs { signingConfigs {

View File

@@ -16,6 +16,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import dagger.multibindings.IntoSet import dagger.multibindings.IntoSet
import gq.kirmanak.mealient.BuildConfig
import leakcanary.LeakCanary import leakcanary.LeakCanary
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
@@ -30,7 +31,10 @@ object DebugModule {
@IntoSet @IntoSet
fun provideLoggingInterceptor(): Interceptor { fun provideLoggingInterceptor(): Interceptor {
val interceptor = HttpLoggingInterceptor { message -> Timber.tag("OkHttp").v(message) } 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 return interceptor
} }

View File

@@ -15,7 +15,7 @@
android:roundIcon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true" android:supportsRtl="true"
tools:ignore="UnusedAttribute" tools:ignore="UnusedAttribute"
android:theme="@style/Theme.Mealient"> android:theme="@style/AppTheme">
<activity <activity
android:name=".ui.activity.MainActivity" android:name=".ui.activity.MainActivity"
android:exported="true"> android:exported="true">

View File

@@ -4,8 +4,8 @@ import android.widget.ImageView
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import gq.kirmanak.mealient.R import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
import gq.kirmanak.mealient.data.recipes.RecipeImageLoader import gq.kirmanak.mealient.ui.images.ImageLoader
import gq.kirmanak.mealient.ui.ImageLoader import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject

View File

@@ -10,7 +10,6 @@ import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
import gq.kirmanak.mealient.data.network.RetrofitBuilder import gq.kirmanak.mealient.data.network.RetrofitBuilder
import gq.kirmanak.mealient.data.network.ServiceFactory import gq.kirmanak.mealient.data.network.ServiceFactory
import gq.kirmanak.mealient.data.network.createServiceFactory 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.RecipeRepo
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
import gq.kirmanak.mealient.data.recipes.db.RecipeStorageImpl 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.RecipeDataSource
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSourceImpl import gq.kirmanak.mealient.data.recipes.network.RecipeDataSourceImpl
import gq.kirmanak.mealient.data.recipes.network.RecipeService import gq.kirmanak.mealient.data.recipes.network.RecipeService
import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import javax.inject.Named import javax.inject.Named

View File

@@ -6,9 +6,9 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import gq.kirmanak.mealient.ui.ImageLoader import gq.kirmanak.mealient.ui.images.ImageLoader
import gq.kirmanak.mealient.ui.picasso.ImageLoaderPicasso import gq.kirmanak.mealient.ui.images.ImageLoaderPicasso
import gq.kirmanak.mealient.ui.picasso.PicassoBuilder import gq.kirmanak.mealient.ui.images.PicassoBuilder
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module

View File

@@ -12,16 +12,14 @@ import com.google.android.material.shape.MaterialShapeDrawable
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import gq.kirmanak.mealient.R import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.databinding.MainActivityBinding 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 import timber.log.Timber
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: MainActivityBinding private lateinit var binding: MainActivityBinding
private val viewModel by viewModels<MainActivityViewModel>() 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -31,7 +29,13 @@ class MainActivity : AppCompatActivity() {
setSupportActionBar(binding.toolbar) setSupportActionBar(binding.toolbar)
supportActionBar?.setIcon(R.drawable.ic_toolbar) supportActionBar?.setIcon(R.drawable.ic_toolbar)
setToolbarRoundCorner() 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() { 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 { override fun onCreateOptionsMenu(menu: Menu): Boolean {
Timber.v("onCreateOptionsMenu() called with: menu = $menu") Timber.v("onCreateOptionsMenu() called with: menu = $menu")
menuInflater.inflate(R.menu.main_toolbar, menu) menuInflater.inflate(R.menu.main_toolbar, menu)
menu.findItem(R.id.logout).isVisible = lastAuthenticationState == AUTHORIZED menu.findItem(R.id.logout).isVisible = uiState.canShowLogout
menu.findItem(R.id.login).isVisible = lastAuthenticationState == UNAUTHORIZED menu.findItem(R.id.login).isVisible = uiState.canShowLogin
return true return true
} }

View File

@@ -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
}

View File

@@ -1,14 +1,10 @@
package gq.kirmanak.mealient.ui.activity package gq.kirmanak.mealient.ui.activity
import androidx.lifecycle.LiveData import androidx.lifecycle.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
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 gq.kirmanak.mealient.ui.auth.AuthenticationState import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@@ -18,16 +14,22 @@ class MainActivityViewModel @Inject constructor(
private val authRepo: AuthRepo, private val authRepo: AuthRepo,
) : ViewModel() { ) : ViewModel() {
private val showLoginButtonFlow = MutableStateFlow(false) private val _uiState = MutableLiveData(MainActivityUiState())
var showLoginButton: Boolean by showLoginButtonFlow::value 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( init {
showLoginButtonFlow, authRepo.isAuthorizedFlow
authRepo.isAuthorizedFlow, .onEach { isAuthorized -> updateUiState { it.copy(isAuthorized = isAuthorized) } }
AuthenticationState::determineState .launchIn(viewModelScope)
) }
val authenticationStateLive: LiveData<AuthenticationState>
get() = authenticationStateFlow.asLiveData() fun updateUiState(updater: (MainActivityUiState) -> MainActivityUiState) {
uiState = updater(uiState)
}
fun logout() { fun logout() {
Timber.v("logout() called") Timber.v("logout() called")

View File

@@ -2,8 +2,8 @@ package gq.kirmanak.mealient.ui.auth
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import by.kirich1409.viewbindingdelegate.viewBinding 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.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.ui.activity.MainActivityViewModel
import timber.log.Timber import timber.log.Timber
@AndroidEntryPoint @AndroidEntryPoint
class AuthenticationFragment : Fragment(R.layout.fragment_authentication) { 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 viewModels<AuthenticationViewModel>() private val viewModel by viewModels<AuthenticationViewModel>()
private val activityViewModel by activityViewModels<MainActivityViewModel>()
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")
binding.button.setOnClickListener { onLoginClicked() } binding.button.setOnClickListener { onLoginClicked() }
(requireActivity() as? AppCompatActivity)?.supportActionBar?.title = activityViewModel.updateUiState { it.copy(loginButtonVisible = false, titleVisible = true) }
getString(R.string.app_name)
viewModel.authenticationResult.observe(viewLifecycleOwner, ::onAuthenticationResult) viewModel.authenticationResult.observe(viewLifecycleOwner, ::onAuthenticationResult)
} }

View File

@@ -3,6 +3,7 @@ package gq.kirmanak.mealient.ui.baseurl
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import by.kirich1409.viewbindingdelegate.viewBinding 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.data.network.NetworkError
import gq.kirmanak.mealient.databinding.FragmentBaseUrlBinding import gq.kirmanak.mealient.databinding.FragmentBaseUrlBinding
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
import timber.log.Timber import timber.log.Timber
@AndroidEntryPoint @AndroidEntryPoint
@@ -18,12 +20,14 @@ class BaseURLFragment : Fragment(R.layout.fragment_base_url) {
private val binding by viewBinding(FragmentBaseUrlBinding::bind) private val binding by viewBinding(FragmentBaseUrlBinding::bind)
private val viewModel by viewModels<BaseURLViewModel>() private val viewModel by viewModels<BaseURLViewModel>()
private val activityViewModel by activityViewModels<MainActivityViewModel>()
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")
binding.button.setOnClickListener(::onProceedClick) binding.button.setOnClickListener(::onProceedClick)
viewModel.checkURLResult.observe(viewLifecycleOwner, ::onCheckURLResult) viewModel.checkURLResult.observe(viewLifecycleOwner, ::onCheckURLResult)
activityViewModel.updateUiState { it.copy(loginButtonVisible = false, titleVisible = true) }
} }
private fun onProceedClick(view: View) { private fun onProceedClick(view: View) {

View File

@@ -2,20 +2,22 @@ package gq.kirmanak.mealient.ui.disclaimer
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
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.databinding.FragmentDisclaimerBinding import gq.kirmanak.mealient.databinding.FragmentDisclaimerBinding
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
import timber.log.Timber import timber.log.Timber
@AndroidEntryPoint @AndroidEntryPoint
class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) { class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) {
private val binding by viewBinding(FragmentDisclaimerBinding::bind) private val binding by viewBinding(FragmentDisclaimerBinding::bind)
private val viewModel by viewModels<DisclaimerViewModel>() private val viewModel by viewModels<DisclaimerViewModel>()
private val activityViewModel by activityViewModels<MainActivityViewModel>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -48,7 +50,6 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) {
binding.okay.isClickable = it == 0 binding.okay.isClickable = it == 0
} }
viewModel.startCountDown() viewModel.startCountDown()
(requireActivity() as? AppCompatActivity)?.supportActionBar?.title = activityViewModel.updateUiState { it.copy(loginButtonVisible = false, titleVisible = true) }
getString(R.string.app_name)
} }
} }

View File

@@ -1,4 +1,4 @@
package gq.kirmanak.mealient.ui package gq.kirmanak.mealient.ui.images
import android.widget.ImageView import android.widget.ImageView
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes

View File

@@ -1,8 +1,7 @@
package gq.kirmanak.mealient.ui.picasso package gq.kirmanak.mealient.ui.images
import android.widget.ImageView import android.widget.ImageView
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import gq.kirmanak.mealient.ui.ImageLoader
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton

View File

@@ -1,4 +1,4 @@
package gq.kirmanak.mealient.ui.picasso package gq.kirmanak.mealient.ui.images
import android.content.Context import android.content.Context
import com.squareup.picasso.OkHttp3Downloader import com.squareup.picasso.OkHttp3Downloader
@@ -22,7 +22,7 @@ class PicassoBuilder @Inject constructor(
Timber.v("buildPicasso() called") Timber.v("buildPicasso() called")
val builder = Picasso.Builder(context) val builder = Picasso.Builder(context)
builder.downloader(OkHttp3Downloader(okHttpClient)) builder.downloader(OkHttp3Downloader(okHttpClient))
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG_PICASSO) {
builder.loggingEnabled(true) builder.loggingEnabled(true)
builder.indicatorsEnabled(true) builder.indicatorsEnabled(true)
builder.listener { _, uri, exception -> builder.listener { _, uri, exception ->

View File

@@ -1,4 +1,4 @@
package gq.kirmanak.mealient.data.recipes package gq.kirmanak.mealient.ui.recipes
import android.widget.ImageView import android.widget.ImageView

View File

@@ -5,7 +5,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn import androidx.paging.cachedIn
import dagger.hilt.android.lifecycle.HiltViewModel 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.RecipeRepo
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@@ -2,7 +2,6 @@ package gq.kirmanak.mealient.ui.recipes
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
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.fragment.app.viewModels import androidx.fragment.app.viewModels
@@ -26,9 +25,8 @@ 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")
activityViewModel.showLoginButton = true activityViewModel.updateUiState { it.copy(loginButtonVisible = true, titleVisible = false) }
setupRecipeAdapter() setupRecipeAdapter()
(requireActivity() as? AppCompatActivity)?.supportActionBar?.title = null
} }
private fun navigateToRecipeInfo(recipeSummaryEntity: RecipeSummaryEntity) { private fun navigateToRecipeInfo(recipeSummaryEntity: RecipeSummaryEntity) {
@@ -64,6 +62,5 @@ 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
activityViewModel.showLoginButton = false
} }
} }

View File

@@ -15,19 +15,15 @@ import dagger.hilt.android.AndroidEntryPoint
import gq.kirmanak.mealient.R import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.databinding.FragmentRecipeInfoBinding import gq.kirmanak.mealient.databinding.FragmentRecipeInfoBinding
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class RecipeInfoFragment : BottomSheetDialogFragment() { class RecipeInfoFragment : BottomSheetDialogFragment() {
private val binding by viewBinding(FragmentRecipeInfoBinding::bind) private val binding by viewBinding(FragmentRecipeInfoBinding::bind)
private val arguments by navArgs<RecipeInfoFragmentArgs>() private val arguments by navArgs<RecipeInfoFragmentArgs>()
private val viewModel by viewModels<RecipeInfoViewModel>() private val viewModel by viewModels<RecipeInfoViewModel>()
private val ingredientsAdapter = RecipeIngredientsAdapter()
@Inject private val instructionsAdapter = RecipeInstructionsAdapter()
lateinit var ingredientsAdapter: RecipeIngredientsAdapter
@Inject
lateinit var instructionsAdapter: RecipeInstructionsAdapter
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@@ -42,22 +38,27 @@ class RecipeInfoFragment : BottomSheetDialogFragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
Timber.v("onViewCreated() called") Timber.v("onViewCreated() called")
binding.ingredientsList.adapter = ingredientsAdapter with(binding) {
binding.instructionsList.adapter = instructionsAdapter ingredientsList.adapter = ingredientsAdapter
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
} }
viewModel.listsVisibility.observe(viewLifecycleOwner) { with(viewModel) {
Timber.d("onViewCreated: lists visibility $it") loadRecipeImage(binding.image, arguments.recipeSlug)
binding.ingredientsHolder.isVisible = it.areIngredientsVisible loadRecipeInfo(arguments.recipeId, arguments.recipeSlug)
binding.instructionsGroup.isVisible = it.areInstructionsVisible 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)
} }
} }

View File

@@ -1,6 +1,9 @@
package gq.kirmanak.mealient.ui.recipes.info 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 areIngredientsVisible: Boolean = false,
val areInstructionsVisible: Boolean = false, val areInstructionsVisible: Boolean = false,
val recipeInfo: FullRecipeInfo? = null,
) )

View File

@@ -6,30 +6,21 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel 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.RecipeRepo
import gq.kirmanak.mealient.data.recipes.impl.FullRecipeInfo
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class RecipeInfoViewModel class RecipeInfoViewModel @Inject constructor(
@Inject
constructor(
private val recipeRepo: RecipeRepo, private val recipeRepo: RecipeRepo,
private val recipeImageLoader: RecipeImageLoader, private val recipeImageLoader: RecipeImageLoader,
private val recipeIngredientsAdapter: RecipeIngredientsAdapter,
private val recipeInstructionsAdapter: RecipeInstructionsAdapter,
) : ViewModel() { ) : ViewModel() {
private val _recipeInfo = MutableLiveData<FullRecipeInfo>() private val _uiState = MutableLiveData(RecipeInfoUiState())
val recipeInfo: LiveData<FullRecipeInfo> val uiState: LiveData<RecipeInfoUiState> get() = _uiState
get() = _recipeInfo
private val _listsVisibility = MutableLiveData(RecipeInfoListsVisibility())
val listsVisibility: LiveData<RecipeInfoListsVisibility>
get() = _listsVisibility
fun loadRecipeImage(view: ImageView, recipeSlug: String) { fun loadRecipeImage(view: ImageView, recipeSlug: String) {
Timber.v("loadRecipeImage() called with: view = $view, recipeSlug = $recipeSlug") Timber.v("loadRecipeImage() called with: view = $view, recipeSlug = $recipeSlug")
@@ -38,21 +29,16 @@ constructor(
fun loadRecipeInfo(recipeId: Long, recipeSlug: String) { fun loadRecipeInfo(recipeId: Long, recipeSlug: String) {
Timber.v("loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug") Timber.v("loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug")
_listsVisibility.value = RecipeInfoListsVisibility() _uiState.value = RecipeInfoUiState()
recipeIngredientsAdapter.submitList(null)
recipeInstructionsAdapter.submitList(null)
viewModelScope.launch { viewModelScope.launch {
runCatchingExceptCancel { recipeRepo.loadRecipeInfo(recipeId, recipeSlug) } runCatchingExceptCancel { recipeRepo.loadRecipeInfo(recipeId, recipeSlug) }
.onSuccess { .onSuccess {
Timber.d("loadRecipeInfo: received recipe info = $it") Timber.d("loadRecipeInfo: received recipe info = $it")
_recipeInfo.value = it _uiState.value = RecipeInfoUiState(
recipeIngredientsAdapter.submitList(it.recipeIngredients) areIngredientsVisible = it.recipeIngredients.isNotEmpty(),
recipeInstructionsAdapter.submitList(it.recipeInstructions) areInstructionsVisible = it.recipeInstructions.isNotEmpty(),
_listsVisibility.value = recipeInfo = it,
RecipeInfoListsVisibility( )
areIngredientsVisible = it.recipeIngredients.isNotEmpty(),
areInstructionsVisible = it.recipeInstructions.isNotEmpty()
)
} }
.onFailure { Timber.e(it, "loadRecipeInfo: can't load recipe info") } .onFailure { Timber.e(it, "loadRecipeInfo: can't load recipe info") }
} }

View File

@@ -9,11 +9,8 @@ import gq.kirmanak.mealient.data.recipes.db.entity.RecipeIngredientEntity
import gq.kirmanak.mealient.databinding.ViewHolderIngredientBinding import gq.kirmanak.mealient.databinding.ViewHolderIngredientBinding
import gq.kirmanak.mealient.ui.recipes.info.RecipeIngredientsAdapter.RecipeIngredientViewHolder import gq.kirmanak.mealient.ui.recipes.info.RecipeIngredientsAdapter.RecipeIngredientViewHolder
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton class RecipeIngredientsAdapter :
class RecipeIngredientsAdapter @Inject constructor() :
ListAdapter<RecipeIngredientEntity, RecipeIngredientViewHolder>(RecipeIngredientDiffCallback) { ListAdapter<RecipeIngredientEntity, RecipeIngredientViewHolder>(RecipeIngredientDiffCallback) {
class RecipeIngredientViewHolder( class RecipeIngredientViewHolder(

View File

@@ -10,11 +10,8 @@ import gq.kirmanak.mealient.data.recipes.db.entity.RecipeInstructionEntity
import gq.kirmanak.mealient.databinding.ViewHolderInstructionBinding import gq.kirmanak.mealient.databinding.ViewHolderInstructionBinding
import gq.kirmanak.mealient.ui.recipes.info.RecipeInstructionsAdapter.RecipeInstructionViewHolder import gq.kirmanak.mealient.ui.recipes.info.RecipeInstructionsAdapter.RecipeInstructionViewHolder
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton class RecipeInstructionsAdapter :
class RecipeInstructionsAdapter @Inject constructor() :
ListAdapter<RecipeInstructionEntity, RecipeInstructionViewHolder>(RecipeInstructionDiffCallback) { ListAdapter<RecipeInstructionEntity, RecipeInstructionViewHolder>(RecipeInstructionDiffCallback) {
private object RecipeInstructionDiffCallback : private object RecipeInstructionDiffCallback :

View File

@@ -23,12 +23,11 @@ class SplashViewModel @Inject constructor(
init { init {
viewModelScope.launch { viewModelScope.launch {
delay(1000) delay(1000)
_nextDestination.value = if (!disclaimerStorage.isDisclaimerAccepted()) _nextDestination.value = when {
SplashFragmentDirections.actionSplashFragmentToDisclaimerFragment() !disclaimerStorage.isDisclaimerAccepted() -> SplashFragmentDirections.actionSplashFragmentToDisclaimerFragment()
else if (baseURLStorage.getBaseURL() == null) baseURLStorage.getBaseURL() == null -> SplashFragmentDirections.actionSplashFragmentToBaseURLFragment()
SplashFragmentDirections.actionSplashFragmentToBaseURLFragment() else -> SplashFragmentDirections.actionSplashFragmentToRecipesFragment()
else }
SplashFragmentDirections.actionSplashFragmentToRecipesFragment()
} }
} }
} }

View File

@@ -42,11 +42,11 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<Button <Button
android:id="@+id/button" android:id="@+id/button"
android:text="@string/fragment_authentication_button_login" style="@style/SmallMarginButton"
app:layout_constraintBottom_toBottomOf="parent" android:text="@string/fragment_authentication_button_login"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
style="@style/SmallMarginButton" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_input_layout" /> app:layout_constraintTop_toBottomOf="@+id/password_input_layout" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -8,20 +8,16 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/main_text_holder" android:id="@+id/main_text_holder"
style="@style/Widget.MaterialComponents.CardView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" android:layout_marginHorizontal="20dp"
android:layout_marginTop="40dp" android:layout_marginTop="40dp"
android:elevation="0dp" app:cardElevation="8dp"
app:cardCornerRadius="@dimen/rounded_corner_size_default" app:layout_constraintTop_toTopOf="parent"
app:cardForegroundColor="#26C4C4C4"
app:layout_constraintBottom_toTopOf="@+id/okay" app:layout_constraintBottom_toTopOf="@+id/okay"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:shapeAppearance="@style/ShapeAppearance.AllCornersRounded">
app:strokeColor="#FFDCC8BF"
app:strokeWidth="1dp">
<TextView <TextView
android:id="@+id/main_text" android:id="@+id/main_text"
@@ -36,11 +32,9 @@
<Button <Button
android:id="@+id/okay" android:id="@+id/okay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_small"
android:clickable="false" android:clickable="false"
tools:text="Okay (3 seconds)" tools:text="Okay (3 seconds)"
style="@style/SmallMarginButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/main_text_holder" /> app:layout_constraintTop_toBottomOf="@+id/main_text_holder" />

View File

@@ -77,7 +77,7 @@
android:layout_marginHorizontal="8dp" android:layout_marginHorizontal="8dp"
android:layout_marginTop="11dp" android:layout_marginTop="11dp"
android:layout_marginBottom="20dp" android:layout_marginBottom="20dp"
app:cardCornerRadius="@dimen/rounded_corner_size_default" app:shapeAppearance="@style/ShapeAppearance.AllCornersRounded"
app:cardElevation="10dp" app:cardElevation="10dp"
app:layout_constraintBottom_toTopOf="@+id/instructions_header" app:layout_constraintBottom_toTopOf="@+id/instructions_header"
app:layout_constraintEnd_toStartOf="@+id/end_guide" app:layout_constraintEnd_toStartOf="@+id/end_guide"

View File

@@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_margin="8dp" android:layout_margin="8dp"
app:cardCornerRadius="@dimen/rounded_corner_size_default" app:shapeAppearance="@style/ShapeAppearance.AllCornersRounded"
app:cardElevation="10dp" app:cardElevation="10dp"
android:layout_height="wrap_content"> android:layout_height="wrap_content">

View File

@@ -6,7 +6,7 @@
android:id="@+id/login" android:id="@+id/login"
android:contentDescription="@string/menu_main_toolbar_content_description_login" android:contentDescription="@string/menu_main_toolbar_content_description_login"
android:title="@string/menu_main_toolbar_login" android:title="@string/menu_main_toolbar_login"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/logout" android:id="@+id/logout"

View File

@@ -1,17 +1,31 @@
<resources> <resources>
<!-- Base application theme. -->
<style name="Theme.Mealient" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <style name="AppTheme" parent="Theme.Material3.Dark.NoActionBar">
<!-- Primary brand color. --> <item name="colorPrimary">@color/md_theme_dark_primary</item>
<item name="colorPrimary">@color/purple_200</item> <item name="colorOnPrimary">@color/md_theme_dark_onPrimary</item>
<item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorPrimaryContainer">@color/md_theme_dark_primaryContainer</item>
<item name="colorOnPrimary">@color/black</item> <item name="colorOnPrimaryContainer">@color/md_theme_dark_onPrimaryContainer</item>
<!-- Secondary brand color. --> <item name="colorSecondary">@color/md_theme_dark_secondary</item>
<item name="colorSecondary">@color/teal_200</item> <item name="colorOnSecondary">@color/md_theme_dark_onSecondary</item>
<item name="colorSecondaryVariant">@color/teal_200</item> <item name="colorSecondaryContainer">@color/md_theme_dark_secondaryContainer</item>
<item name="colorOnSecondary">@color/black</item> <item name="colorOnSecondaryContainer">@color/md_theme_dark_onSecondaryContainer</item>
<!-- Status bar color. --> <item name="colorTertiary">@color/md_theme_dark_tertiary</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item> <item name="colorOnTertiary">@color/md_theme_dark_onTertiary</item>
<!-- Customize your theme here. --> <item name="colorTertiaryContainer">@color/md_theme_dark_tertiaryContainer</item>
<item name="android:overScrollMode">never</item> <item name="colorOnTertiaryContainer">@color/md_theme_dark_onTertiaryContainer</item>
</style> <item name="colorError">@color/md_theme_dark_error</item>
<item name="colorErrorContainer">@color/md_theme_dark_errorContainer</item>
<item name="colorOnError">@color/md_theme_dark_onError</item>
<item name="colorOnErrorContainer">@color/md_theme_dark_onErrorContainer</item>
<item name="android:colorBackground">@color/md_theme_dark_background</item>
<item name="colorOnBackground">@color/md_theme_dark_onBackground</item>
<item name="colorSurface">@color/md_theme_dark_surface</item>
<item name="colorOnSurface">@color/md_theme_dark_onSurface</item>
<item name="colorSurfaceVariant">@color/md_theme_dark_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/md_theme_dark_onSurfaceVariant</item>
<item name="colorOutline">@color/md_theme_dark_outline</item>
<item name="colorOnSurfaceInverse">@color/md_theme_dark_inverseOnSurface</item>
<item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_dark_primaryInverse</item>
</style>
</resources> </resources>

View File

@@ -1,12 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="purple_200">#FFBB86FC</color> <color name="md_theme_light_primary">#7743B5</color>
<color name="purple_500">#FF6200EE</color> <color name="md_theme_light_onPrimary">#FFFFFF</color>
<color name="purple_700">#FF3700B3</color> <color name="md_theme_light_primaryContainer">#EFDBFF</color>
<color name="teal_200">#FF03DAC5</color> <color name="md_theme_light_onPrimaryContainer">#290054</color>
<color name="teal_700">#FF018786</color> <color name="md_theme_light_secondary">#655A70</color>
<color name="black">#FF000000</color> <color name="md_theme_light_onSecondary">#FFFFFF</color>
<color name="white">#FFFFFFFF</color> <color name="md_theme_light_secondaryContainer">#ECDDF7</color>
<color name="md_theme_light_onSecondaryContainer">#201829</color>
<color name="primary">#FF9D76DC</color> <color name="md_theme_light_tertiary">#805159</color>
<color name="md_theme_light_onTertiary">#FFFFFF</color>
<color name="md_theme_light_tertiaryContainer">#FFD9DF</color>
<color name="md_theme_light_onTertiaryContainer">#321118</color>
<color name="md_theme_light_error">#BA1B1B</color>
<color name="md_theme_light_errorContainer">#FFDAD4</color>
<color name="md_theme_light_onError">#FFFFFF</color>
<color name="md_theme_light_onErrorContainer">#410001</color>
<color name="md_theme_light_background">#FFFBFC</color>
<color name="md_theme_light_onBackground">#1D1B1E</color>
<color name="md_theme_light_surface">#FFFBFC</color>
<color name="md_theme_light_onSurface">#1D1B1E</color>
<color name="md_theme_light_surfaceVariant">#E8DFEB</color>
<color name="md_theme_light_onSurfaceVariant">#4A454E</color>
<color name="md_theme_light_outline">#7B757E</color>
<color name="md_theme_light_inverseOnSurface">#F5EFF3</color>
<color name="md_theme_light_inverseSurface">#322F33</color>
<color name="md_theme_light_inversePrimary">#DBB8FF</color>
<color name="md_theme_light_shadow">#000000</color>
<color name="md_theme_light_primaryInverse">#DBB8FF</color>
<color name="md_theme_dark_primary">#DBB8FF</color>
<color name="md_theme_dark_onPrimary">#460283</color>
<color name="md_theme_dark_primaryContainer">#5E289B</color>
<color name="md_theme_dark_onPrimaryContainer">#EFDBFF</color>
<color name="md_theme_dark_secondary">#CFC1DA</color>
<color name="md_theme_dark_onSecondary">#362D40</color>
<color name="md_theme_dark_secondaryContainer">#4D4357</color>
<color name="md_theme_dark_onSecondaryContainer">#ECDDF7</color>
<color name="md_theme_dark_tertiary">#F2B7C0</color>
<color name="md_theme_dark_onTertiary">#4B252C</color>
<color name="md_theme_dark_tertiaryContainer">#653A42</color>
<color name="md_theme_dark_onTertiaryContainer">#FFD9DF</color>
<color name="md_theme_dark_error">#FFB4A9</color>
<color name="md_theme_dark_errorContainer">#930006</color>
<color name="md_theme_dark_onError">#680003</color>
<color name="md_theme_dark_onErrorContainer">#FFDAD4</color>
<color name="md_theme_dark_background">#1D1B1E</color>
<color name="md_theme_dark_onBackground">#E7E1E5</color>
<color name="md_theme_dark_surface">#1D1B1E</color>
<color name="md_theme_dark_onSurface">#E7E1E5</color>
<color name="md_theme_dark_surfaceVariant">#4A454E</color>
<color name="md_theme_dark_onSurfaceVariant">#CCC4CF</color>
<color name="md_theme_dark_outline">#958E98</color>
<color name="md_theme_dark_inverseOnSurface">#1D1B1E</color>
<color name="md_theme_dark_inverseSurface">#E7E1E5</color>
<color name="md_theme_dark_inversePrimary">#7743B5</color>
<color name="md_theme_dark_shadow">#000000</color>
<color name="md_theme_dark_primaryInverse">#7743B5</color>
</resources> </resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="SmallMarginTextInputLayoutStyle" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox"> <style name="SmallMarginTextInputLayoutStyle">
<item name="android:layout_width">0dp</item> <item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:layout_margin">@dimen/margin_small</item> <item name="android:layout_margin">@dimen/margin_small</item>
@@ -13,17 +13,17 @@
<item name="android:layout_margin">@dimen/margin_small</item> <item name="android:layout_margin">@dimen/margin_small</item>
</style> </style>
<style name="ShapeAppearance.AllCornersRounded" parent="ShapeAppearance.MaterialComponents"> <style name="ShapeAppearance.AllCornersRounded" parent="ShapeAppearance.Material3.LargeComponent">
<item name="cornerSize">@dimen/rounded_corner_size_default</item> <item name="cornerSize">@dimen/rounded_corner_size_default</item>
</style> </style>
<!-- This is a workaround to support always round corners of the bottom sheet <!-- This is a workaround to support always round corners of the bottom sheet
See more at https://github.com/material-components/material-components-android/pull/437#issuecomment-852461685 --> See more at https://github.com/material-components/material-components-android/pull/437#issuecomment-852461685 -->
<style name="NoShapeBottomSheetDialog" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog"> <style name="NoShapeBottomSheetDialog" parent="ThemeOverlay.Material3.BottomSheetDialog">
<item name="bottomSheetStyle">@style/NoShapeBottomSheet</item> <item name="bottomSheetStyle">@style/NoShapeBottomSheet</item>
</style> </style>
<style name="NoShapeBottomSheet" parent="Widget.MaterialComponents.BottomSheet.Modal"> <style name="NoShapeBottomSheet" parent="Widget.Material3.BottomSheet.Modal">
<item name="shapeAppearance">@null</item> <item name="shapeAppearance">@null</item>
<item name="shapeAppearanceOverlay">@null</item> <item name="shapeAppearanceOverlay">@null</item>
<item name="android:background">@drawable/recipe_info_background</item> <item name="android:background">@drawable/recipe_info_background</item>

View File

@@ -1,17 +1,32 @@
<resources> <resources>
<!-- Base application theme. -->
<style name="Theme.Mealient" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <style name="AppTheme" parent="Theme.Material3.Light.NoActionBar">
<!-- Primary brand color. --> <item name="colorPrimary">@color/md_theme_light_primary</item>
<item name="colorPrimary">@color/primary</item> <item name="colorOnPrimary">@color/md_theme_light_onPrimary</item>
<item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorPrimaryContainer">@color/md_theme_light_primaryContainer</item>
<item name="colorOnPrimary">@color/white</item> <item name="colorOnPrimaryContainer">@color/md_theme_light_onPrimaryContainer</item>
<!-- Secondary brand color. --> <item name="colorSecondary">@color/md_theme_light_secondary</item>
<item name="colorSecondary">@color/teal_200</item> <item name="colorOnSecondary">@color/md_theme_light_onSecondary</item>
<item name="colorSecondaryVariant">@color/teal_700</item> <item name="colorSecondaryContainer">@color/md_theme_light_secondaryContainer</item>
<item name="colorOnSecondary">@color/black</item> <item name="colorOnSecondaryContainer">@color/md_theme_light_onSecondaryContainer</item>
<!-- Status bar color. --> <item name="colorTertiary">@color/md_theme_light_tertiary</item>
<item name="android:statusBarColor">?attr/colorPrimary</item> <item name="colorOnTertiary">@color/md_theme_light_onTertiary</item>
<!-- Customize your theme here. --> <item name="colorTertiaryContainer">@color/md_theme_light_tertiaryContainer</item>
<item name="android:overScrollMode">never</item> <item name="colorOnTertiaryContainer">@color/md_theme_light_onTertiaryContainer</item>
</style> <item name="colorError">@color/md_theme_light_error</item>
<item name="colorErrorContainer">@color/md_theme_light_errorContainer</item>
<item name="colorOnError">@color/md_theme_light_onError</item>
<item name="colorOnErrorContainer">@color/md_theme_light_onErrorContainer</item>
<item name="android:colorBackground">@color/md_theme_light_background</item>
<item name="colorOnBackground">@color/md_theme_light_onBackground</item>
<item name="colorSurface">@color/md_theme_light_surface</item>
<item name="colorOnSurface">@color/md_theme_light_onSurface</item>
<item name="colorSurfaceVariant">@color/md_theme_light_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/md_theme_light_onSurfaceVariant</item>
<item name="colorOutline">@color/md_theme_light_outline</item>
<item name="colorOnSurfaceInverse">@color/md_theme_light_inverseOnSurface</item>
<item name="colorSurfaceInverse">@color/md_theme_light_inverseSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_light_primaryInverse</item>
<item name="android:overScrollMode">never</item>
</style>
</resources> </resources>

View File

@@ -2,7 +2,7 @@ package gq.kirmanak.mealient.data.recipes.impl
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage 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.MockKAnnotations
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK