Merge pull request #92 from kirmanak/recipe-search
Implement searching recipe by name
This commit is contained in:
@@ -5,6 +5,7 @@ import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
|||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
|
|
||||||
interface RecipeRepo {
|
interface RecipeRepo {
|
||||||
|
|
||||||
fun createPager(): Pager<Int, RecipeSummaryEntity>
|
fun createPager(): Pager<Int, RecipeSummaryEntity>
|
||||||
|
|
||||||
suspend fun clearLocalData()
|
suspend fun clearLocalData()
|
||||||
@@ -12,4 +13,6 @@ interface RecipeRepo {
|
|||||||
suspend fun refreshRecipeInfo(recipeSlug: String): Result<Unit>
|
suspend fun refreshRecipeInfo(recipeSlug: String): Result<Unit>
|
||||||
|
|
||||||
suspend fun loadRecipeInfo(recipeId: String): FullRecipeEntity?
|
suspend fun loadRecipeInfo(recipeId: String): FullRecipeEntity?
|
||||||
|
|
||||||
|
fun updateNameQuery(name: String?)
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
|||||||
interface RecipeStorage {
|
interface RecipeStorage {
|
||||||
suspend fun saveRecipes(recipes: List<RecipeSummaryInfo>)
|
suspend fun saveRecipes(recipes: List<RecipeSummaryInfo>)
|
||||||
|
|
||||||
fun queryRecipes(): PagingSource<Int, RecipeSummaryEntity>
|
fun queryRecipes(query: String?): PagingSource<Int, RecipeSummaryEntity>
|
||||||
|
|
||||||
suspend fun refreshAll(recipes: List<RecipeSummaryInfo>)
|
suspend fun refreshAll(recipes: List<RecipeSummaryInfo>)
|
||||||
|
|
||||||
|
|||||||
@@ -23,21 +23,17 @@ class RecipeStorageImpl @Inject constructor(
|
|||||||
) : RecipeStorage {
|
) : RecipeStorage {
|
||||||
private val recipeDao: RecipeDao by lazy { db.recipeDao() }
|
private val recipeDao: RecipeDao by lazy { db.recipeDao() }
|
||||||
|
|
||||||
override suspend fun saveRecipes(
|
override suspend fun saveRecipes(recipes: List<RecipeSummaryInfo>) {
|
||||||
recipes: List<RecipeSummaryInfo>
|
|
||||||
) = db.withTransaction {
|
|
||||||
logger.v { "saveRecipes() called with $recipes" }
|
logger.v { "saveRecipes() called with $recipes" }
|
||||||
|
val entities = recipes.map { it.toRecipeSummaryEntity() }
|
||||||
for (recipe in recipes) {
|
logger.v { "saveRecipes: entities = $entities" }
|
||||||
val recipeSummaryEntity = recipe.toRecipeSummaryEntity()
|
db.withTransaction { recipeDao.insertRecipes(entities) }
|
||||||
recipeDao.insertRecipe(recipeSummaryEntity)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun queryRecipes(query: String?): PagingSource<Int, RecipeSummaryEntity> {
|
||||||
override fun queryRecipes(): PagingSource<Int, RecipeSummaryEntity> {
|
logger.v { "queryRecipes() called with: query = $query" }
|
||||||
logger.v { "queryRecipes() called" }
|
return if (query == null) recipeDao.queryRecipesByPages()
|
||||||
return recipeDao.queryRecipesByPages()
|
else recipeDao.queryRecipesByPages(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun refreshAll(recipes: List<RecipeSummaryInfo>) {
|
override suspend fun refreshAll(recipes: List<RecipeSummaryInfo>) {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package gq.kirmanak.mealient.data.recipes.impl
|
||||||
|
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
|
|
||||||
|
interface RecipePagingSourceFactory : () -> PagingSource<Int, RecipeSummaryEntity> {
|
||||||
|
fun setQuery(newQuery: String?)
|
||||||
|
fun invalidate()
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package gq.kirmanak.mealient.data.recipes.impl
|
||||||
|
|
||||||
|
import androidx.paging.InvalidatingPagingSourceFactory
|
||||||
|
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
||||||
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class RecipePagingSourceFactoryImpl @Inject constructor(
|
||||||
|
private val recipeStorage: RecipeStorage,
|
||||||
|
private val logger: Logger,
|
||||||
|
) : RecipePagingSourceFactory {
|
||||||
|
|
||||||
|
private val query = AtomicReference<String>(null)
|
||||||
|
|
||||||
|
private val delegate = InvalidatingPagingSourceFactory {
|
||||||
|
val currentQuery = query.get()
|
||||||
|
logger.d { "Creating paging source, query is $currentQuery" }
|
||||||
|
recipeStorage.queryRecipes(currentQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invoke() = delegate.invoke()
|
||||||
|
|
||||||
|
override fun setQuery(newQuery: String?) {
|
||||||
|
logger.v { "setQuery() called with: newQuery = $newQuery" }
|
||||||
|
query.set(newQuery)
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = delegate.invalidate()
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package gq.kirmanak.mealient.data.recipes.impl
|
package gq.kirmanak.mealient.data.recipes.impl
|
||||||
|
|
||||||
import androidx.paging.ExperimentalPagingApi
|
import androidx.paging.ExperimentalPagingApi
|
||||||
import androidx.paging.InvalidatingPagingSourceFactory
|
|
||||||
import androidx.paging.Pager
|
import androidx.paging.Pager
|
||||||
import androidx.paging.PagingConfig
|
import androidx.paging.PagingConfig
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
@@ -19,17 +18,18 @@ import javax.inject.Singleton
|
|||||||
class RecipeRepoImpl @Inject constructor(
|
class RecipeRepoImpl @Inject constructor(
|
||||||
private val mediator: RecipesRemoteMediator,
|
private val mediator: RecipesRemoteMediator,
|
||||||
private val storage: RecipeStorage,
|
private val storage: RecipeStorage,
|
||||||
private val pagingSourceFactory: InvalidatingPagingSourceFactory<Int, RecipeSummaryEntity>,
|
private val pagingSourceFactory: RecipePagingSourceFactory,
|
||||||
private val dataSource: RecipeDataSource,
|
private val dataSource: RecipeDataSource,
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
) : RecipeRepo {
|
) : RecipeRepo {
|
||||||
|
|
||||||
override fun createPager(): Pager<Int, RecipeSummaryEntity> {
|
override fun createPager(): Pager<Int, RecipeSummaryEntity> {
|
||||||
logger.v { "createPager() called" }
|
logger.v { "createPager() called" }
|
||||||
val pagingConfig = PagingConfig(pageSize = 5, enablePlaceholders = true)
|
val pagingConfig = PagingConfig(pageSize = 5, enablePlaceholders = true)
|
||||||
return Pager(
|
return Pager(
|
||||||
config = pagingConfig,
|
config = pagingConfig,
|
||||||
remoteMediator = mediator,
|
remoteMediator = mediator,
|
||||||
pagingSourceFactory = pagingSourceFactory
|
pagingSourceFactory = pagingSourceFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,4 +53,9 @@ class RecipeRepoImpl @Inject constructor(
|
|||||||
logger.v { "loadRecipeInfo() returned: $recipeInfo" }
|
logger.v { "loadRecipeInfo() returned: $recipeInfo" }
|
||||||
return recipeInfo
|
return recipeInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateNameQuery(name: String?) {
|
||||||
|
logger.v { "updateNameQuery() called with: name = $name" }
|
||||||
|
pagingSourceFactory.setQuery(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ import javax.inject.Singleton
|
|||||||
class RecipesRemoteMediator @Inject constructor(
|
class RecipesRemoteMediator @Inject constructor(
|
||||||
private val storage: RecipeStorage,
|
private val storage: RecipeStorage,
|
||||||
private val network: RecipeDataSource,
|
private val network: RecipeDataSource,
|
||||||
private val pagingSourceFactory: InvalidatingPagingSourceFactory<Int, RecipeSummaryEntity>,
|
private val pagingSourceFactory: RecipePagingSourceFactory,
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
) : RemoteMediator<Int, RecipeSummaryEntity>() {
|
) : RemoteMediator<Int, RecipeSummaryEntity>() {
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package gq.kirmanak.mealient.di
|
package gq.kirmanak.mealient.di
|
||||||
|
|
||||||
import androidx.paging.InvalidatingPagingSourceFactory
|
|
||||||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
@@ -13,9 +12,7 @@ import gq.kirmanak.mealient.data.network.MealieDataSourceWrapper
|
|||||||
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
|
||||||
import gq.kirmanak.mealient.data.recipes.impl.RecipeImageUrlProvider
|
import gq.kirmanak.mealient.data.recipes.impl.*
|
||||||
import gq.kirmanak.mealient.data.recipes.impl.RecipeImageUrlProviderImpl
|
|
||||||
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.database.recipe.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
import gq.kirmanak.mealient.ui.recipes.images.RecipeModelLoaderFactory
|
import gq.kirmanak.mealient.ui.recipes.images.RecipeModelLoaderFactory
|
||||||
@@ -46,13 +43,11 @@ interface RecipeModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
fun bindModelLoaderFactory(recipeModelLoaderFactory: RecipeModelLoaderFactory): ModelLoaderFactory<RecipeSummaryEntity, InputStream>
|
fun bindModelLoaderFactory(recipeModelLoaderFactory: RecipeModelLoaderFactory): ModelLoaderFactory<RecipeSummaryEntity, InputStream>
|
||||||
|
|
||||||
companion object {
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindRecipePagingSourceFactory(recipePagingSourceFactoryImpl: RecipePagingSourceFactoryImpl): RecipePagingSourceFactory
|
||||||
|
|
||||||
@Provides
|
companion object {
|
||||||
@Singleton
|
|
||||||
fun provideRecipePagingSourceFactory(
|
|
||||||
recipeStorage: RecipeStorage
|
|
||||||
) = InvalidatingPagingSourceFactory { recipeStorage.queryRecipes() }
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package gq.kirmanak.mealient.extensions
|
package gq.kirmanak.mealient.extensions
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
@@ -18,10 +17,7 @@ fun <T> Fragment.collectWhenViewResumed(flow: Flow<T>, collector: FlowCollector<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Fragment.showLongToast(@StringRes text: Int) = showLongToast(getString(text))
|
fun Fragment.showLongToast(@StringRes text: Int) = context?.showLongToast(text) != null
|
||||||
|
|
||||||
fun Fragment.showLongToast(text: String) = showToast(text, Toast.LENGTH_LONG)
|
fun Fragment.showLongToast(text: String) = context?.showLongToast(text) != null
|
||||||
|
|
||||||
private fun Fragment.showToast(text: String, length: Int): Boolean {
|
|
||||||
return context?.let { Toast.makeText(it, text, length).show() } != null
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package gq.kirmanak.mealient.extensions
|
package gq.kirmanak.mealient.extensions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
@@ -96,3 +98,12 @@ fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observ
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Context.showLongToast(text: String) = showToast(text, Toast.LENGTH_LONG)
|
||||||
|
|
||||||
|
fun Context.showLongToast(@StringRes text: Int) = showLongToast(getString(text))
|
||||||
|
|
||||||
|
private fun Context.showToast(text: String, length: Int) {
|
||||||
|
Toast.makeText(this, text, length).show()
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import android.view.Menu
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import androidx.appcompat.widget.SearchView.OnQueryTextListener
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
@@ -47,6 +49,7 @@ class MainActivity : AppCompatActivity(R.layout.main_activity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun configureNavGraph() {
|
private fun configureNavGraph() {
|
||||||
|
logger.v { "configureNavGraph() called" }
|
||||||
viewModel.startDestination.observeOnce(this) {
|
viewModel.startDestination.observeOnce(this) {
|
||||||
logger.d { "configureNavGraph: received destination" }
|
logger.d { "configureNavGraph: received destination" }
|
||||||
val graph = navController.navInflater.inflate(R.navigation.nav_graph)
|
val graph = navController.navInflater.inflate(R.navigation.nav_graph)
|
||||||
@@ -104,9 +107,37 @@ class MainActivity : AppCompatActivity(R.layout.main_activity) {
|
|||||||
menuInflater.inflate(R.menu.main_toolbar, menu)
|
menuInflater.inflate(R.menu.main_toolbar, menu)
|
||||||
menu.findItem(R.id.logout).isVisible = uiState.canShowLogout
|
menu.findItem(R.id.logout).isVisible = uiState.canShowLogout
|
||||||
menu.findItem(R.id.login).isVisible = uiState.canShowLogin
|
menu.findItem(R.id.login).isVisible = uiState.canShowLogin
|
||||||
|
val searchItem = menu.findItem(R.id.search_recipe_action)
|
||||||
|
searchItem.isVisible = uiState.searchVisible
|
||||||
|
setupSearchItem(searchItem)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupSearchItem(searchItem: MenuItem) {
|
||||||
|
logger.v { "setupSearchItem() called with: searchItem = $searchItem" }
|
||||||
|
val searchView = searchItem.actionView as? SearchView
|
||||||
|
if (searchView == null) {
|
||||||
|
logger.e { "setupSearchItem: search item's actionView is null or not SearchView" }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
searchView.queryHint = getString(R.string.search_recipes_hint)
|
||||||
|
searchView.setOnCloseListener {
|
||||||
|
logger.v { "onClose() called" }
|
||||||
|
viewModel.onSearchQuery(null)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
searchView.setOnQueryTextListener(object : OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String?): Boolean = true
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String?): Boolean {
|
||||||
|
logger.v { "onQueryTextChange() called with: newText = $newText" }
|
||||||
|
viewModel.onSearchQuery(newText?.trim()?.takeUnless { it.isEmpty() })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
logger.v { "onOptionsItemSelected() called with: item = $item" }
|
logger.v { "onOptionsItemSelected() called with: item = $item" }
|
||||||
val result = when (item.itemId) {
|
val result = when (item.itemId) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ data class MainActivityUiState(
|
|||||||
val titleVisible: Boolean = true,
|
val titleVisible: Boolean = true,
|
||||||
val isAuthorized: Boolean = false,
|
val isAuthorized: Boolean = false,
|
||||||
val navigationVisible: Boolean = false,
|
val navigationVisible: Boolean = false,
|
||||||
|
val searchVisible: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val canShowLogin: Boolean
|
val canShowLogin: Boolean
|
||||||
get() = !isAuthorized && loginButtonVisible
|
get() = !isAuthorized && loginButtonVisible
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import gq.kirmanak.mealient.R
|
|||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
||||||
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@@ -18,6 +19,7 @@ class MainActivityViewModel @Inject constructor(
|
|||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
private val disclaimerStorage: DisclaimerStorage,
|
private val disclaimerStorage: DisclaimerStorage,
|
||||||
private val serverInfoRepo: ServerInfoRepo,
|
private val serverInfoRepo: ServerInfoRepo,
|
||||||
|
private val recipeRepo: RecipeRepo,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableLiveData(MainActivityUiState())
|
private val _uiState = MutableLiveData(MainActivityUiState())
|
||||||
@@ -52,4 +54,9 @@ class MainActivityViewModel @Inject constructor(
|
|||||||
logger.v { "logout() called" }
|
logger.v { "logout() called" }
|
||||||
viewModelScope.launch { authRepo.logout() }
|
viewModelScope.launch { authRepo.logout() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onSearchQuery(query: String?) {
|
||||||
|
logger.v { "onSearchQuery() called with: query = $query" }
|
||||||
|
recipeRepo.updateNameQuery(query)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,12 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
|
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
|
||||||
activityViewModel.updateUiState {
|
activityViewModel.updateUiState {
|
||||||
it.copy(loginButtonVisible = true, titleVisible = false, navigationVisible = true)
|
it.copy(
|
||||||
|
loginButtonVisible = true,
|
||||||
|
titleVisible = false,
|
||||||
|
navigationVisible = true,
|
||||||
|
searchVisible = false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
viewModel.loadPreservedRequest()
|
viewModel.loadPreservedRequest()
|
||||||
setupViews()
|
setupViews()
|
||||||
|
|||||||
@@ -32,7 +32,12 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
|
|||||||
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
|
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
|
||||||
binding.button.setOnClickListener { onLoginClicked() }
|
binding.button.setOnClickListener { onLoginClicked() }
|
||||||
activityViewModel.updateUiState {
|
activityViewModel.updateUiState {
|
||||||
it.copy(loginButtonVisible = false, titleVisible = true, navigationVisible = false)
|
it.copy(
|
||||||
|
loginButtonVisible = false,
|
||||||
|
titleVisible = true,
|
||||||
|
navigationVisible = false,
|
||||||
|
searchVisible = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
viewModel.uiState.observe(viewLifecycleOwner, ::onUiStateChange)
|
viewModel.uiState.observe(viewLifecycleOwner, ::onUiStateChange)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,12 @@ class BaseURLFragment : Fragment(R.layout.fragment_base_url) {
|
|||||||
binding.button.setOnClickListener(::onProceedClick)
|
binding.button.setOnClickListener(::onProceedClick)
|
||||||
viewModel.uiState.observe(viewLifecycleOwner, ::onUiStateChange)
|
viewModel.uiState.observe(viewLifecycleOwner, ::onUiStateChange)
|
||||||
activityViewModel.updateUiState {
|
activityViewModel.updateUiState {
|
||||||
it.copy(loginButtonVisible = false, titleVisible = true, navigationVisible = false)
|
it.copy(
|
||||||
|
loginButtonVisible = false,
|
||||||
|
titleVisible = true,
|
||||||
|
navigationVisible = false,
|
||||||
|
searchVisible = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,12 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) {
|
|||||||
}
|
}
|
||||||
viewModel.startCountDown()
|
viewModel.startCountDown()
|
||||||
activityViewModel.updateUiState {
|
activityViewModel.updateUiState {
|
||||||
it.copy(loginButtonVisible = false, titleVisible = true, navigationVisible = false)
|
it.copy(
|
||||||
|
loginButtonVisible = false,
|
||||||
|
titleVisible = true,
|
||||||
|
navigationVisible = false,
|
||||||
|
searchVisible = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,7 +50,12 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
|
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
|
||||||
activityViewModel.updateUiState {
|
activityViewModel.updateUiState {
|
||||||
it.copy(loginButtonVisible = true, titleVisible = false, navigationVisible = true)
|
it.copy(
|
||||||
|
loginButtonVisible = true,
|
||||||
|
titleVisible = false,
|
||||||
|
navigationVisible = true,
|
||||||
|
searchVisible = true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
setupRecipeAdapter()
|
setupRecipeAdapter()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
style="@style/Widget.MaterialComponents.Toolbar.Primary"
|
style="@style/Widget.MaterialComponents.Toolbar.Primary"
|
||||||
|
android:theme="@style/ThemeOverlay.Toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?actionBarSize"
|
android:layout_height="?actionBarSize"
|
||||||
app:layout_scrollFlags="scroll|snap|enterAlways" />
|
app:layout_scrollFlags="scroll|snap|enterAlways" />
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item
|
<item
|
||||||
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="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/logout"
|
android:id="@+id/logout"
|
||||||
android:contentDescription="@string/menu_main_toolbar_content_description_logout"
|
android:contentDescription="@string/menu_main_toolbar_content_description_logout"
|
||||||
android:title="@string/menu_main_toolbar_logout"
|
android:title="@string/menu_main_toolbar_logout"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/search_recipe_action"
|
||||||
|
android:icon="@android:drawable/ic_menu_search"
|
||||||
|
android:title="@string/search_recipes_hint"
|
||||||
|
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
@@ -1,31 +1,32 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="AppTheme" parent="Theme.Material3.Dark.NoActionBar">
|
<style name="AppTheme" parent="Theme.Material3.Dark.NoActionBar">
|
||||||
<item name="colorPrimary">@color/md_theme_dark_primary</item>
|
<item name="colorPrimary">@color/md_theme_dark_primary</item>
|
||||||
<item name="colorOnPrimary">@color/md_theme_dark_onPrimary</item>
|
<item name="colorOnPrimary">@color/md_theme_dark_onPrimary</item>
|
||||||
<item name="colorPrimaryContainer">@color/md_theme_dark_primaryContainer</item>
|
<item name="colorPrimaryContainer">@color/md_theme_dark_primaryContainer</item>
|
||||||
<item name="colorOnPrimaryContainer">@color/md_theme_dark_onPrimaryContainer</item>
|
<item name="colorOnPrimaryContainer">@color/md_theme_dark_onPrimaryContainer</item>
|
||||||
<item name="colorSecondary">@color/md_theme_dark_secondary</item>
|
<item name="colorSecondary">@color/md_theme_dark_secondary</item>
|
||||||
<item name="colorOnSecondary">@color/md_theme_dark_onSecondary</item>
|
<item name="colorOnSecondary">@color/md_theme_dark_onSecondary</item>
|
||||||
<item name="colorSecondaryContainer">@color/md_theme_dark_secondaryContainer</item>
|
<item name="colorSecondaryContainer">@color/md_theme_dark_secondaryContainer</item>
|
||||||
<item name="colorOnSecondaryContainer">@color/md_theme_dark_onSecondaryContainer</item>
|
<item name="colorOnSecondaryContainer">@color/md_theme_dark_onSecondaryContainer</item>
|
||||||
<item name="colorTertiary">@color/md_theme_dark_tertiary</item>
|
<item name="colorTertiary">@color/md_theme_dark_tertiary</item>
|
||||||
<item name="colorOnTertiary">@color/md_theme_dark_onTertiary</item>
|
<item name="colorOnTertiary">@color/md_theme_dark_onTertiary</item>
|
||||||
<item name="colorTertiaryContainer">@color/md_theme_dark_tertiaryContainer</item>
|
<item name="colorTertiaryContainer">@color/md_theme_dark_tertiaryContainer</item>
|
||||||
<item name="colorOnTertiaryContainer">@color/md_theme_dark_onTertiaryContainer</item>
|
<item name="colorOnTertiaryContainer">@color/md_theme_dark_onTertiaryContainer</item>
|
||||||
<item name="colorError">@color/md_theme_dark_error</item>
|
<item name="colorError">@color/md_theme_dark_error</item>
|
||||||
<item name="colorErrorContainer">@color/md_theme_dark_errorContainer</item>
|
<item name="colorErrorContainer">@color/md_theme_dark_errorContainer</item>
|
||||||
<item name="colorOnError">@color/md_theme_dark_onError</item>
|
<item name="colorOnError">@color/md_theme_dark_onError</item>
|
||||||
<item name="colorOnErrorContainer">@color/md_theme_dark_onErrorContainer</item>
|
<item name="colorOnErrorContainer">@color/md_theme_dark_onErrorContainer</item>
|
||||||
<item name="android:colorBackground">@color/md_theme_dark_background</item>
|
<item name="android:colorBackground">@color/md_theme_dark_background</item>
|
||||||
<item name="colorOnBackground">@color/md_theme_dark_onBackground</item>
|
<item name="colorOnBackground">@color/md_theme_dark_onBackground</item>
|
||||||
<item name="colorSurface">@color/md_theme_dark_surface</item>
|
<item name="colorSurface">@color/md_theme_dark_surface</item>
|
||||||
<item name="colorOnSurface">@color/md_theme_dark_onSurface</item>
|
<item name="colorOnSurface">@color/md_theme_dark_onSurface</item>
|
||||||
<item name="colorSurfaceVariant">@color/md_theme_dark_surfaceVariant</item>
|
<item name="colorSurfaceVariant">@color/md_theme_dark_surfaceVariant</item>
|
||||||
<item name="colorOnSurfaceVariant">@color/md_theme_dark_onSurfaceVariant</item>
|
<item name="colorOnSurfaceVariant">@color/md_theme_dark_onSurfaceVariant</item>
|
||||||
<item name="colorOutline">@color/md_theme_dark_outline</item>
|
<item name="colorOutline">@color/md_theme_dark_outline</item>
|
||||||
<item name="colorOnSurfaceInverse">@color/md_theme_dark_inverseOnSurface</item>
|
<item name="colorOnSurfaceInverse">@color/md_theme_dark_inverseOnSurface</item>
|
||||||
<item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item>
|
<item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item>
|
||||||
<item name="colorPrimaryInverse">@color/md_theme_dark_primaryInverse</item>
|
<item name="colorPrimaryInverse">@color/md_theme_dark_primaryInverse</item>
|
||||||
</style>
|
<item name="android:overScrollMode">never</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -48,4 +48,5 @@
|
|||||||
<string name="fragment_recipes_load_failure_toast_no_connection">нет соединения</string>
|
<string name="fragment_recipes_load_failure_toast_no_connection">нет соединения</string>
|
||||||
<string name="fragment_recipes_load_failure_toast_no_reason">Ошибка загрузки.</string>
|
<string name="fragment_recipes_load_failure_toast_no_reason">Ошибка загрузки.</string>
|
||||||
<string name="menu_bottom_navigation_change_url">Сменить URL</string>
|
<string name="menu_bottom_navigation_change_url">Сменить URL</string>
|
||||||
|
<string name="search_recipes_hint">Найти рецепты</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -52,4 +52,5 @@
|
|||||||
<string name="fragment_recipes_load_failure_toast_unexpected_response">unexpected response</string>
|
<string name="fragment_recipes_load_failure_toast_unexpected_response">unexpected response</string>
|
||||||
<string name="fragment_recipes_load_failure_toast_no_connection">no connection</string>
|
<string name="fragment_recipes_load_failure_toast_no_connection">no connection</string>
|
||||||
<string name="menu_bottom_navigation_change_url">Change URL</string>
|
<string name="menu_bottom_navigation_change_url">Change URL</string>
|
||||||
|
<string name="search_recipes_hint">Search recipes</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -35,4 +35,9 @@
|
|||||||
<item name="colorPrimaryInverse">@color/md_theme_light_primaryInverse</item>
|
<item name="colorPrimaryInverse">@color/md_theme_light_primaryInverse</item>
|
||||||
<item name="android:overScrollMode">never</item>
|
<item name="android:overScrollMode">never</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="ThemeOverlay.Toolbar" parent="ThemeOverlay.MaterialComponents.Toolbar.Primary">
|
||||||
|
<item name="android:editTextColor">?colorOnPrimary</item>
|
||||||
|
<item name="android:textColorHint">?colorOnPrimary</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package gq.kirmanak.mealient.data.recipes.impl
|
||||||
|
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
|
import gq.kirmanak.mealient.test.HiltRobolectricTest
|
||||||
|
import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_RECIPE_SUMMARY_ENTITY
|
||||||
|
import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_SUMMARY_ENTITY
|
||||||
|
import gq.kirmanak.mealient.test.RecipeImplTestData.TEST_RECIPE_SUMMARIES
|
||||||
|
import gq.kirmanak.mealient.test.RecipeImplTestData.TEST_RECIPE_SUMMARY_ENTITIES
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class RecipePagingSourceFactoryImplTest : HiltRobolectricTest() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var subject: RecipePagingSourceFactory
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var storage: RecipeStorage
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when query is ca expect cake only is returned`() = runTest {
|
||||||
|
storage.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||||
|
subject.setQuery("ca")
|
||||||
|
assertThat(queryRecipes()).isEqualTo(listOf(CAKE_RECIPE_SUMMARY_ENTITY))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when query is po expect porridge only is returned`() = runTest {
|
||||||
|
storage.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||||
|
subject.setQuery("po")
|
||||||
|
assertThat(queryRecipes()).isEqualTo(listOf(PORRIDGE_RECIPE_SUMMARY_ENTITY))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when query is e expect cake and porridge are returned`() = runTest {
|
||||||
|
storage.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||||
|
subject.setQuery("e")
|
||||||
|
assertThat(queryRecipes()).isEqualTo(TEST_RECIPE_SUMMARY_ENTITIES)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when query is null expect cake and porridge are returned`() = runTest {
|
||||||
|
storage.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||||
|
subject.setQuery(null)
|
||||||
|
assertThat(queryRecipes()).isEqualTo(TEST_RECIPE_SUMMARY_ENTITIES)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun queryRecipes(): List<RecipeSummaryEntity> {
|
||||||
|
val loadParam = PagingSource.LoadParams.Refresh<Int>(null, Int.MAX_VALUE, false)
|
||||||
|
val loadResult = subject.invoke().load(loadParam)
|
||||||
|
return (loadResult as PagingSource.LoadResult.Page).data
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
package gq.kirmanak.mealient.data.recipes.impl
|
package gq.kirmanak.mealient.data.recipes.impl
|
||||||
|
|
||||||
import androidx.paging.InvalidatingPagingSourceFactory
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
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.network.RecipeDataSource
|
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
|
||||||
import gq.kirmanak.mealient.test.BaseUnitTest
|
import gq.kirmanak.mealient.test.BaseUnitTest
|
||||||
import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_FULL_RECIPE_INFO
|
import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_FULL_RECIPE_INFO
|
||||||
import gq.kirmanak.mealient.test.RecipeImplTestData.FULL_CAKE_INFO_ENTITY
|
import gq.kirmanak.mealient.test.RecipeImplTestData.FULL_CAKE_INFO_ENTITY
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
import io.mockk.impl.annotations.MockK
|
import io.mockk.impl.annotations.MockK
|
||||||
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -29,8 +28,8 @@ class RecipeRepoTest : BaseUnitTest() {
|
|||||||
@MockK
|
@MockK
|
||||||
lateinit var remoteMediator: RecipesRemoteMediator
|
lateinit var remoteMediator: RecipesRemoteMediator
|
||||||
|
|
||||||
@MockK
|
@MockK(relaxUnitFun = true)
|
||||||
lateinit var pagingSourceFactory: InvalidatingPagingSourceFactory<Int, RecipeSummaryEntity>
|
lateinit var pagingSourceFactory: RecipePagingSourceFactory
|
||||||
|
|
||||||
lateinit var subject: RecipeRepo
|
lateinit var subject: RecipeRepo
|
||||||
|
|
||||||
@@ -59,4 +58,10 @@ class RecipeRepoTest : BaseUnitTest() {
|
|||||||
subject.clearLocalData()
|
subject.clearLocalData()
|
||||||
coVerify { storage.clearAllLocalData() }
|
coVerify { storage.clearAllLocalData() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when updateNameQuery expect sets query in paging source factory`() {
|
||||||
|
subject.updateNameQuery("query")
|
||||||
|
verify { pagingSourceFactory.setQuery("query") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ class RecipesRemoteMediatorTest : BaseUnitTest() {
|
|||||||
lateinit var dataSource: RecipeDataSource
|
lateinit var dataSource: RecipeDataSource
|
||||||
|
|
||||||
@MockK(relaxUnitFun = true)
|
@MockK(relaxUnitFun = true)
|
||||||
lateinit var pagingSourceFactory: InvalidatingPagingSourceFactory<Int, RecipeSummaryEntity>
|
lateinit var pagingSourceFactory: RecipePagingSourceFactory
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ object RecipeImplTestData {
|
|||||||
imageId = "porridge",
|
imageId = "porridge",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val TEST_RECIPE_SUMMARY_ENTITIES =
|
||||||
|
listOf(CAKE_RECIPE_SUMMARY_ENTITY, PORRIDGE_RECIPE_SUMMARY_ENTITY)
|
||||||
|
|
||||||
val SUGAR_INGREDIENT = RecipeIngredientInfo(
|
val SUGAR_INGREDIENT = RecipeIngredientInfo(
|
||||||
note = "2 oz of white sugar",
|
note = "2 oz of white sugar",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package gq.kirmanak.mealient.ui.activity
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
|
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
||||||
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
|
import gq.kirmanak.mealient.test.BaseUnitTest
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.impl.annotations.MockK
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class MainActivityViewModelTest : BaseUnitTest() {
|
||||||
|
|
||||||
|
@MockK(relaxUnitFun = true)
|
||||||
|
lateinit var authRepo: AuthRepo
|
||||||
|
|
||||||
|
@MockK(relaxUnitFun = true)
|
||||||
|
lateinit var disclaimerStorage: DisclaimerStorage
|
||||||
|
|
||||||
|
@MockK(relaxUnitFun = true)
|
||||||
|
lateinit var serverInfoRepo: ServerInfoRepo
|
||||||
|
|
||||||
|
@MockK(relaxUnitFun = true)
|
||||||
|
lateinit var recipeRepo: RecipeRepo
|
||||||
|
|
||||||
|
private lateinit var subject: MainActivityViewModel
|
||||||
|
|
||||||
|
@Before
|
||||||
|
override fun setUp() {
|
||||||
|
super.setUp()
|
||||||
|
every { authRepo.isAuthorizedFlow } returns emptyFlow()
|
||||||
|
subject = MainActivityViewModel(
|
||||||
|
authRepo = authRepo,
|
||||||
|
logger = logger,
|
||||||
|
disclaimerStorage = disclaimerStorage,
|
||||||
|
serverInfoRepo = serverInfoRepo,
|
||||||
|
recipeRepo = recipeRepo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when onSearchQuery with query expect call to recipe repo`() {
|
||||||
|
subject.onSearchQuery("query")
|
||||||
|
verify { recipeRepo.updateNameQuery("query") }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when onSearchQuery with null expect call to recipe repo`() {
|
||||||
|
subject.onSearchQuery(null)
|
||||||
|
verify { recipeRepo.updateNameQuery(null) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,4 +33,5 @@ sonarqube {
|
|||||||
|
|
||||||
rootCoverage {
|
rootCoverage {
|
||||||
generateXml = true
|
generateXml = true
|
||||||
|
includeNoLocationClasses = true
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ import gq.kirmanak.mealient.database.recipe.entity.*
|
|||||||
RecipeSummaryEntity::class,
|
RecipeSummaryEntity::class,
|
||||||
RecipeEntity::class,
|
RecipeEntity::class,
|
||||||
RecipeIngredientEntity::class,
|
RecipeIngredientEntity::class,
|
||||||
RecipeInstructionEntity::class
|
RecipeInstructionEntity::class,
|
||||||
],
|
],
|
||||||
exportSchema = true,
|
exportSchema = true,
|
||||||
autoMigrations = [
|
autoMigrations = [
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ interface RecipeDao {
|
|||||||
@Query("SELECT * FROM recipe_summaries ORDER BY date_added DESC")
|
@Query("SELECT * FROM recipe_summaries ORDER BY date_added DESC")
|
||||||
fun queryRecipesByPages(): PagingSource<Int, RecipeSummaryEntity>
|
fun queryRecipesByPages(): PagingSource<Int, RecipeSummaryEntity>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM recipe_summaries WHERE recipe_summaries.name LIKE '%' || :query || '%' ORDER BY date_added DESC")
|
||||||
|
fun queryRecipesByPages(query: String): PagingSource<Int, RecipeSummaryEntity>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertRecipe(recipeSummaryEntity: RecipeSummaryEntity)
|
suspend fun insertRecipes(recipeSummaryEntity: Iterable<RecipeSummaryEntity>)
|
||||||
|
|
||||||
@Query("DELETE FROM recipe_summaries")
|
@Query("DELETE FROM recipe_summaries")
|
||||||
suspend fun removeAllRecipes()
|
suspend fun removeAllRecipes()
|
||||||
|
|||||||
Reference in New Issue
Block a user