Skip to content

Commit

Permalink
Add keywords list
Browse files Browse the repository at this point in the history
  • Loading branch information
lneugebauer committed Jun 29, 2023
1 parent e10af47 commit c3fa1aa
Show file tree
Hide file tree
Showing 23 changed files with 426 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,21 @@ fun CategoryListScreen(
val uiState by viewModel.uiState.collectAsState()

Scaffold(
topBar = { CategoryListTopBar() },
topBar = { TopAppBar() },
) { innerPadding ->
when (uiState) {
is CategoryListScreenState.Initial -> Loader()
is CategoryListScreenState.Loaded -> {
val categories = (uiState as CategoryListScreenState.Loaded).data
CategoryListScreen(
data = categories,
modifier = Modifier.padding(innerPadding),
) { categoryName ->
navigator.navigate(RecipeListScreenDestination(categoryName))
if (categories.isEmpty()) {
AbstractErrorScreen(uiText = UiText.StringResource(R.string.error_no_categories_found))
} else {
CategoryListScreen(
data = categories,
modifier = Modifier.padding(innerPadding),
) { categoryName ->
navigator.navigate(RecipeListScreenDestination(filterBy = categoryName))
}
}
}

Expand All @@ -72,47 +76,43 @@ private fun CategoryListScreen(
modifier: Modifier,
onClick: (String) -> Unit,
) {
if (data.isEmpty()) {
AbstractErrorScreen(uiText = UiText.StringResource(R.string.error_no_categories_found))
} else {
val listState = rememberLazyListState()
LazyColumn(
modifier = modifier.fillMaxSize(),
state = listState,
) {
itemsIndexed(data) { index, category ->
ListItem(
modifier = Modifier.clickable(
onClick = {
onClick.invoke(category.name)
},
),
trailing = {
Badge(backgroundColor = MaterialTheme.colors.primary) {
Text(text = category.recipeCount.toString())
}
},
text = {
val categoryName = if (category.name == "*") {
stringResource(R.string.recipe_uncategorised)
} else {
category.name
}
Text(text = categoryName)
val listState = rememberLazyListState()
LazyColumn(
modifier = modifier.fillMaxSize(),
state = listState,
) {
itemsIndexed(data) { index, category ->
ListItem(
modifier = Modifier.clickable(
onClick = {
onClick.invoke(category.name)
},
),
trailing = {
Badge(backgroundColor = MaterialTheme.colors.primary) {
Text(text = category.recipeCount.toString())
}
},
text = {
val categoryName = if (category.name == "*") {
stringResource(R.string.recipe_uncategorised)
} else {
category.name
}
Text(text = categoryName)
},
)
if (index != data.size - 1) {
Divider(
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.padding_m)),
)
if (index != data.size - 1) {
Divider(
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.padding_m)),
)
}
}
}
}
}

@Composable
fun CategoryListTopBar() {
private fun TopAppBar() {
TopAppBar(
title = { Text(text = stringResource(R.string.common_categories)) },
backgroundColor = NcBlue700,
Expand All @@ -122,7 +122,7 @@ fun CategoryListTopBar() {

@Preview
@Composable
private fun CategoryListScreen() {
private fun CategoryListScreenPreview() {
val categories = MutableList(10) {
Category(name = "Category $it", nextInt(0, 20))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import de.lukasneugebauer.nextcloudcookbook.core.data.remote.response.ErrorRespo
import de.lukasneugebauer.nextcloudcookbook.core.util.Constants.FULL_PATH
import de.lukasneugebauer.nextcloudcookbook.recipe.data.dto.RecipeDto
import de.lukasneugebauer.nextcloudcookbook.recipe.data.dto.RecipePreviewDto
import de.lukasneugebauer.nextcloudcookbook.tag.data.dto.TagDto
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
Expand All @@ -31,6 +32,9 @@ interface NcCookbookApi {
@GET("$FULL_PATH/category/{categoryName}")
suspend fun getRecipesByCategory(@Path("categoryName") categoryName: String): List<RecipePreviewDto>

@GET("$FULL_PATH/tags/{tagName}")
suspend fun getRecipesByTag(@Path("tagName") tagName: String): List<RecipePreviewDto>

@GET("$FULL_PATH/recipes")
suspend fun getRecipes(): List<RecipePreviewDto>

Expand All @@ -45,4 +49,7 @@ interface NcCookbookApi {

@DELETE("$FULL_PATH/recipes/{id}")
suspend fun deleteRecipe(@Path("id") id: Int): NetworkResponse<String, ErrorResponse>

@GET("$FULL_PATH/keywords")
suspend fun getTags(): List<TagDto>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ package de.lukasneugebauer.nextcloudcookbook.core.domain.usecase
import com.dropbox.android.external.store4.ExperimentalStoreApi
import de.lukasneugebauer.nextcloudcookbook.di.CategoriesStore
import de.lukasneugebauer.nextcloudcookbook.di.RecipePreviewsByCategoryStore
import de.lukasneugebauer.nextcloudcookbook.di.RecipePreviewsByTagStore
import de.lukasneugebauer.nextcloudcookbook.di.RecipePreviewsStore
import de.lukasneugebauer.nextcloudcookbook.di.RecipeStore
import javax.inject.Inject
import javax.inject.Singleton

@OptIn(ExperimentalStoreApi::class)
@Singleton
class ClearAllStoresUseCase @Inject constructor(
private val categoriesStore: CategoriesStore,
private val recipePreviewsByCategoryStore: RecipePreviewsByCategoryStore,
private val recipePreviewsByTagStore: RecipePreviewsByTagStore,
private val recipePreviewsStore: RecipePreviewsStore,
private val recipeStore: RecipeStore,
) {
Expand All @@ -19,6 +23,7 @@ class ClearAllStoresUseCase @Inject constructor(
listOf(
categoriesStore,
recipePreviewsByCategoryStore,
recipePreviewsByTagStore,
recipePreviewsStore,
recipeStore,
).forEach { store ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material.icons.filled.Fastfood
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Sell
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand All @@ -24,6 +25,7 @@ import de.lukasneugebauer.nextcloudcookbook.destinations.CategoryListScreenDesti
import de.lukasneugebauer.nextcloudcookbook.destinations.Destination
import de.lukasneugebauer.nextcloudcookbook.destinations.HomeScreenDestination
import de.lukasneugebauer.nextcloudcookbook.destinations.RecipeListScreenDestination
import de.lukasneugebauer.nextcloudcookbook.destinations.TagListScreenDestination

enum class BottomBarDestination(
val direction: Destination,
Expand All @@ -32,6 +34,7 @@ enum class BottomBarDestination(
) {
Home(HomeScreenDestination, Icons.Default.Home, R.string.common_home),
Categories(CategoryListScreenDestination, Icons.Default.Bookmark, R.string.common_categories),
Tags(TagListScreenDestination, Icons.Default.Sell, R.string.common_tags),
Recipes(RecipeListScreenDestination, Icons.Default.Fastfood, R.string.common_recipes),
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import javax.inject.Singleton

typealias RecipePreviewsByCategoryStore = Store<String, List<RecipePreviewDto>>
data class RecipePreviewsByCategoryKey(val categoryName: String)
data class RecipePreviewsByTagKey(val tagName: String)

typealias RecipePreviewsByCategoryStore = Store<RecipePreviewsByCategoryKey, List<RecipePreviewDto>>
typealias RecipePreviewsByTagStore = Store<RecipePreviewsByTagKey, List<RecipePreviewDto>>
typealias RecipePreviewsStore = Store<Any, List<RecipePreviewDto>>
typealias RecipeStore = Store<Int, RecipeDto>

Expand All @@ -32,8 +36,23 @@ object RecipeModule {
fun provideRecipePreviewsByCategoryStore(apiProvider: ApiProvider): RecipePreviewsByCategoryStore {
return StoreBuilder
.from(
Fetcher.of { categoryName: String ->
apiProvider.getNcCookbookApi()?.getRecipesByCategory(categoryName)
Fetcher.of { key: RecipePreviewsByCategoryKey ->
apiProvider.getNcCookbookApi()?.getRecipesByCategory(key.categoryName)
?: throw NullPointerException("Nextcloud Cookbook API is null.")
},
)
.build()
}

@ExperimentalCoroutinesApi
@FlowPreview
@Provides
@Singleton
fun provideRecipePreviewsByTagStore(apiProvider: ApiProvider): RecipePreviewsByTagStore {
return StoreBuilder
.from(
Fetcher.of { key: RecipePreviewsByTagKey ->
apiProvider.getNcCookbookApi()?.getRecipesByTag(key.tagName)
?: throw NullPointerException("Nextcloud Cookbook API is null.")
},
)
Expand Down Expand Up @@ -75,14 +94,16 @@ object RecipeModule {
fun provideRecipeRepository(
apiProvider: ApiProvider,
@IoDispatcher ioDispatcher: CoroutineDispatcher,
recipesByCategoryStore: RecipePreviewsByCategoryStore,
recipePreviewsByCategoryStore: RecipePreviewsByCategoryStore,
recipePreviewsByTagStore: RecipePreviewsByTagStore,
recipePreviewsStore: RecipePreviewsStore,
recipeStore: RecipeStore,
): RecipeRepository =
RecipeRepositoryImpl(
apiProvider,
ioDispatcher,
recipesByCategoryStore,
recipePreviewsByCategoryStore,
recipePreviewsByTagStore,
recipePreviewsStore,
recipeStore,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package de.lukasneugebauer.nextcloudcookbook.di

import com.dropbox.android.external.store4.Fetcher
import com.dropbox.android.external.store4.Store
import com.dropbox.android.external.store4.StoreBuilder
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import de.lukasneugebauer.nextcloudcookbook.tag.data.dto.TagDto
import de.lukasneugebauer.nextcloudcookbook.tag.data.repository.TagRepositoryImpl
import de.lukasneugebauer.nextcloudcookbook.tag.domain.repository.TagRepository
import javax.inject.Singleton

typealias TagsStore = Store<Any, List<TagDto>>

@Module
@InstallIn(SingletonComponent::class)
object TagModule {

@Provides
@Singleton
fun provideTagsStore(apiProvider: ApiProvider): TagsStore {
return StoreBuilder
.from(
fetcher = Fetcher.of {
apiProvider.getNcCookbookApi()?.getTags()
?: throw NullPointerException("Nextcloud Cookbook API is null.")
},
)
.build()
}

@Provides
@Singleton
fun provideTagRepository(
tagsStore: TagsStore,
): TagRepository = TagRepositoryImpl(tagsStore)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import de.lukasneugebauer.nextcloudcookbook.core.util.Resource
import de.lukasneugebauer.nextcloudcookbook.core.util.SimpleResource
import de.lukasneugebauer.nextcloudcookbook.core.util.UiText
import de.lukasneugebauer.nextcloudcookbook.di.ApiProvider
import de.lukasneugebauer.nextcloudcookbook.di.RecipePreviewsByCategoryKey
import de.lukasneugebauer.nextcloudcookbook.di.RecipePreviewsByCategoryStore
import de.lukasneugebauer.nextcloudcookbook.di.RecipePreviewsByTagKey
import de.lukasneugebauer.nextcloudcookbook.di.RecipePreviewsByTagStore
import de.lukasneugebauer.nextcloudcookbook.di.RecipePreviewsStore
import de.lukasneugebauer.nextcloudcookbook.di.RecipeStore
import de.lukasneugebauer.nextcloudcookbook.recipe.data.dto.RecipeDto
Expand All @@ -28,6 +31,7 @@ class RecipeRepositoryImpl @Inject constructor(
private val apiProvider: ApiProvider,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val recipePreviewsByCategoryStore: RecipePreviewsByCategoryStore,
private val recipePreviewsByTagStore: RecipePreviewsByTagStore,
private val recipePreviewsStore: RecipePreviewsStore,
private val recipeStore: RecipeStore,
) : RecipeRepository, BaseRepository() {
Expand All @@ -38,7 +42,15 @@ class RecipeRepositoryImpl @Inject constructor(
override fun getRecipePreviewsByCategory(categoryName: String): Flow<StoreResponse<List<RecipePreviewDto>>> =
recipePreviewsByCategoryStore.stream(
StoreRequest.cached(
key = categoryName,
key = RecipePreviewsByCategoryKey(categoryName),
refresh = false,
),
)

override fun getRecipePreviewsByTag(tagName: String): Flow<StoreResponse<List<RecipePreviewDto>>> =
recipePreviewsByTagStore.stream(
StoreRequest.cached(
key = RecipePreviewsByTagKey(tagName),
refresh = false,
),
)
Expand Down Expand Up @@ -88,14 +100,15 @@ class RecipeRepositoryImpl @Inject constructor(
refreshCaches(id = id, categoryName = categoryName, deleted = true)
Resource.Success(Unit)
}

is NetworkResponse.Error -> handleResponseError(response.error)
}
}
}

private suspend fun refreshCaches(id: Int, categoryName: String, deleted: Boolean = false) {
if (categoryName.isNotBlank()) {
recipePreviewsByCategoryStore.fresh(categoryName)
recipePreviewsByCategoryStore.fresh(RecipePreviewsByCategoryKey(categoryName))
}
recipePreviewsStore.fresh(Unit)
if (deleted) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package de.lukasneugebauer.nextcloudcookbook.recipe.domain.model

enum class RecipeListFilter {
CATEGORY,
TAG,
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ interface RecipeRepository {

fun getRecipePreviewsByCategory(categoryName: String): Flow<StoreResponse<List<RecipePreviewDto>>>

fun getRecipePreviewsByTag(tagName: String): Flow<StoreResponse<List<RecipePreviewDto>>>

fun getRecipeFlow(id: Int): Flow<StoreResponse<RecipeDto>>

suspend fun getRecipe(id: Int): RecipeDto
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import de.lukasneugebauer.nextcloudcookbook.core.data.PreferencesManager
import de.lukasneugebauer.nextcloudcookbook.core.domain.model.RecipeOfTheDay
import de.lukasneugebauer.nextcloudcookbook.core.util.IoDispatcher
import de.lukasneugebauer.nextcloudcookbook.di.CategoriesStore
import de.lukasneugebauer.nextcloudcookbook.di.RecipePreviewsByCategoryKey
import de.lukasneugebauer.nextcloudcookbook.di.RecipePreviewsByCategoryStore
import de.lukasneugebauer.nextcloudcookbook.di.RecipePreviewsStore
import de.lukasneugebauer.nextcloudcookbook.di.RecipeStore
Expand Down Expand Up @@ -74,7 +75,7 @@ class GetHomeScreenDataUseCase @Inject constructor(
.take(RecipeConstants.HOME_SCREEN_CATEGORIES)
.forEach { categoryDto ->
val recipePreviews = recipePreviewsByCategoryStore
.get(categoryDto.name)
.get(RecipePreviewsByCategoryKey(categoryDto.name))
.map { it.toRecipePreview() }
if (recipePreviews.isNotEmpty()) {
val result = HomeScreenDataResult.Row(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ fun HomeScreen(
text = item.headline,
clickable = item.recipes.size > MORE_BUTTON_THRESHOLD,
) {
navigator.navigate(RecipeListScreenDestination(categoryName = item.headline))
navigator.navigate(RecipeListScreenDestination(filterBy = item.headline))
}
RowContainer(
data = item.recipes.map {
Expand Down
Loading

0 comments on commit c3fa1aa

Please sign in to comment.