diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85a3a2a..3d3464e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,7 +19,6 @@ diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/App.kt b/app/src/main/java/org/mixdrinks/mixdrinks/App.kt index 1d5446f..ebc7421 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/App.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/App.kt @@ -9,44 +9,59 @@ import org.koin.core.context.GlobalContext.startKoin import org.koin.dsl.module import org.mixdrinks.mixdrinks.app.RetrofitClient import org.mixdrinks.mixdrinks.database.AppDatabase -import org.mixdrinks.mixdrinks.features.data.CocktailProvider +import org.mixdrinks.mixdrinks.features.data.GoodType +import org.mixdrinks.mixdrinks.features.data.MixDrinkService import org.mixdrinks.mixdrinks.features.detail.ui.cocktail.DetailScreenCocktailViewModel -import org.mixdrinks.mixdrinks.features.detail.ui.good.DetailScreenGoodViewModel -import org.mixdrinks.mixdrinks.features.detail.ui.tool.DetailScreenToolViewModel +import org.mixdrinks.mixdrinks.features.detail.ui.goods.DetailScreenGoodsViewModel import org.mixdrinks.mixdrinks.features.fetcher.Fetcher -import org.mixdrinks.mixdrinks.features.filter.ui.FilterScreenViewModel -import org.mixdrinks.mixdrinks.features.start.ui.StartScreenViewModel +import org.mixdrinks.mixdrinks.features.start.StartRepository +import org.mixdrinks.mixdrinks.features.start.filter.ui.main.FilterScreenViewModel +import org.mixdrinks.mixdrinks.features.start.filter.SelectedFilterStorage +import org.mixdrinks.mixdrinks.features.start.main.ui.StartScreenViewModel +import org.mixdrinks.mixdrinks.features.start.filter.FilterRepository +import org.mixdrinks.mixdrinks.features.start.filter.ui.search.FilterSearchScreenViewModel class App : Application() { - override fun onCreate() { - super.onCreate() + override fun onCreate() { + super.onCreate() - val appModule = module { + val appModule = module { + single { + Room.databaseBuilder( + androidContext(), + AppDatabase::class.java, + "mix-drinks-database" + ).build() + } + single { RetrofitClient.retrofit.create(MixDrinkService::class.java) } - single { - Room.databaseBuilder( - androidContext(), - AppDatabase::class.java, - "mix-drinks-database" - ).build() - } + single { Fetcher(get(), get()) } - single { RetrofitClient.retrofit.create(CocktailProvider::class.java) } - viewModel { StartScreenViewModel(get()) } - viewModel { (id: Int) -> DetailScreenCocktailViewModel(cocktailId = id, get()) } - viewModel { (id: Int) -> DetailScreenGoodViewModel(goodId = id, get())} - viewModel { (id: Int) -> DetailScreenToolViewModel(toolId = id, get()) } + // Repository + single { StartRepository(get(), get()) } + single { FilterRepository(get(), get()) } - viewModel { FilterScreenViewModel(get()) } + single { SelectedFilterStorage() } - single { Fetcher(get(), get()) } - } + viewModel { StartScreenViewModel(get(), get()) } + viewModel { (id: Int) -> DetailScreenCocktailViewModel(cocktailId = id, get(), get()) } + viewModel { (goodType: GoodType) -> DetailScreenGoodsViewModel(goodType = goodType, get()) } + + viewModel { FilterScreenViewModel(get(), get()) } + viewModel { (groupId: Int) -> + FilterSearchScreenViewModel( + groupId = groupId, + get(), + get() + ) + } + } - startKoin { - androidLogger() - androidContext(applicationContext) - modules(appModule) + startKoin { + androidLogger() + androidContext(applicationContext) + modules(appModule) + } } - } } diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/app/MainActivity.kt b/app/src/main/java/org/mixdrinks/mixdrinks/app/MainActivity.kt index 51fdc3d..dcf9311 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/app/MainActivity.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/app/MainActivity.kt @@ -14,3 +14,4 @@ class MainActivity : ComponentActivity() { } + diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/app/MixDrinksApp.kt b/app/src/main/java/org/mixdrinks/mixdrinks/app/MixDrinksApp.kt index b6ef8b3..9b2320a 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/app/MixDrinksApp.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/app/MixDrinksApp.kt @@ -5,78 +5,70 @@ import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import org.koin.androidx.compose.get -import org.mixdrinks.mixdrinks.features.fetcher.Fetcher import org.mixdrinks.mixdrinks.app.ui.theme.MixDrinksTheme import org.mixdrinks.mixdrinks.features.common.ui.NotFoundScreen +import org.mixdrinks.mixdrinks.features.data.GoodType import org.mixdrinks.mixdrinks.features.detail.ui.cocktail.DetailScreen -import org.mixdrinks.mixdrinks.features.detail.ui.good.DetailScreenGood -import org.mixdrinks.mixdrinks.features.detail.ui.tool.DetailScreenTool -import org.mixdrinks.mixdrinks.features.filter.ui.FilterScreen -import org.mixdrinks.mixdrinks.features.start.ui.StartScreen +import org.mixdrinks.mixdrinks.features.detail.ui.goods.DetailScreenGoods +import org.mixdrinks.mixdrinks.features.start.filter.ui.main.FilterScreen +import org.mixdrinks.mixdrinks.features.start.filter.ui.search.FilterSearchScreen +import org.mixdrinks.mixdrinks.features.start.main.ui.StartScreen + @Suppress("LongMethod") @Composable fun MixDrinksApp(modifier: Modifier = Modifier) { - Fetcher(get(), get()) - MixDrinksTheme { val navController = rememberNavController() NavHost( - navController = navController, - startDestination = Routes.start + navController = navController, startDestination = Routes.start ) { composable(Routes.start) { - StartScreen( - modifier = modifier, + StartScreen(modifier = modifier, onNavigateToDetail = { navController.navigate("${Routes.cocktail}/$it") }, onNavigateToFilter = { navController.navigate(Routes.filter) }, - ) + onNavigateToStart = { navController.navigate(Routes.start) }) } - composable("${Routes.cocktail}/{${Routes.cocktailId}}") { - backStackEntry -> + composable("${Routes.cocktail}/{${Routes.cocktailId}}") { backStackEntry -> val cocktailId = backStackEntry.arguments?.getString(Routes.cocktailId) - cocktailId?.toInt()?.let { - DetailScreen( - modifier = modifier, - cocktailId = it, - onNavigateToDetailGood = { navController.navigate("${Routes.good}/$it")}, - onNavigateToDetailTool = { navController.navigate("${Routes.tool}/$it")}, - onBack = { navController.popBackStack() } - ) + cocktailId?.toInt()?.let { id -> + DetailScreen(modifier = modifier, + cocktailId = id, + onNavigateToStart = { navController.navigate(Routes.start) }, + onNavigateToDetailGood = { goodType -> + navController.navigate("${Routes.good}/${goodType.id}/${goodType.type}") + }, + onBack = { navController.popBackStack() }) } } - composable("${Routes.good}/{${Routes.goodId}}") { - backStackEntry -> + composable("${Routes.good}/{${Routes.goodId}}/{${Routes.goodType}}") { backStackEntry -> val goodId = backStackEntry.arguments?.getString(Routes.goodId) - goodId?.toInt()?.let { - DetailScreenGood( + val goodType = backStackEntry.arguments?.getString(Routes.goodType) + + if (goodId != null && goodType != null) + DetailScreenGoods( modifier = modifier, - goodId = it, - onBack = { navController.popBackStack() } - ) - } + goodType = GoodType(id = goodId.toInt(), GoodType.Type.fromString(goodType)), + onBack = { navController.popBackStack() }) } - composable("${Routes.tool}/{${Routes.toolId}}") { - backStackEntry -> - val toolId = backStackEntry.arguments?.getString(Routes.toolId) - toolId?.toInt()?.let { - DetailScreenTool( + composable(Routes.filter) { + FilterScreen(modifier = modifier, + onNavigateToStart = { navController.navigate(Routes.start) }, + onNavigateToFilterSearch = { navController.navigate("${Routes.filterSearch}/${it}") } + ) + } + composable("${Routes.filterSearch}/{${Routes.groupFilterId}}") { backStackEntry -> + val groupId = backStackEntry.arguments?.getString(Routes.groupFilterId) + groupId?.toInt()?.let { + FilterSearchScreen( modifier = modifier, - toolId = it, - onBack = { navController.popBackStack() } + groupId = it, + onNavigateToFilter = { navController.navigate(Routes.filter) }, ) } } - composable(Routes.filter) { - FilterScreen( - modifier = modifier, - ) - } composable(Routes.notFound) { - NotFoundScreen( - modifier = modifier, - onNavigateToStart = { navController.navigate(Routes.start) } - ) + NotFoundScreen(modifier = modifier, + onNavigateToStart = { navController.navigate(Routes.start) }) } } } diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/app/RetrofitClient.kt b/app/src/main/java/org/mixdrinks/mixdrinks/app/RetrofitClient.kt index 7ebb743..cbfedff 100755 --- a/app/src/main/java/org/mixdrinks/mixdrinks/app/RetrofitClient.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/app/RetrofitClient.kt @@ -10,7 +10,7 @@ import retrofit2.Retrofit import java.util.concurrent.TimeUnit object RetrofitClient { - private const val BASE_URL = "https://api.mixdrinks.org/v2/" + private const val BASE_URL = "https://api.mixdrinks.org/" private const val TIME_OUT: Long = 60 private val logging = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/app/Routes.kt b/app/src/main/java/org/mixdrinks/mixdrinks/app/Routes.kt index c4ac714..1dbbf4c 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/app/Routes.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/app/Routes.kt @@ -6,12 +6,14 @@ object Routes { const val notFound = "not_found" const val cocktail = "cocktail" - const val cocktailId = "cocktailId" - - const val tool = "tool" - const val toolId = "toolId" + const val cocktailId = "cocktail_id" const val good = "good" - const val goodId = "goodId" + const val goodId = "good_id" + const val goodType = "good_type" + + const val filterSearch = "filter_search" + const val groupFilterId = "group_filter_id" + } diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/app/ui/theme/Color.kt b/app/src/main/java/org/mixdrinks/mixdrinks/app/ui/theme/Color.kt index d74ef1c..005d8b4 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/app/ui/theme/Color.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/app/ui/theme/Color.kt @@ -7,4 +7,5 @@ import androidx.compose.ui.graphics.Color val Green700 = Color(0XFF2B4718) val Green = Color(0XFF4E6640) val Black = Color(0XFF181818) +val GreenAlfa = Color(0x322B4718) diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/app/ui/theme/Theme.kt b/app/src/main/java/org/mixdrinks/mixdrinks/app/ui/theme/Theme.kt index bc6eb25..72a25a1 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/app/ui/theme/Theme.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/app/ui/theme/Theme.kt @@ -15,7 +15,7 @@ private val DarkColorPalette = darkColors( private val LightColorPalette = lightColors( primary = Green700, primaryVariant = Green, - secondary = Black + secondary = Black, /* Other default colors to override background = Color.White, diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/app/ui/theme/Type.kt b/app/src/main/java/org/mixdrinks/mixdrinks/app/ui/theme/Type.kt index 5e87fa2..7e575d5 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/app/ui/theme/Type.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/app/ui/theme/Type.kt @@ -46,6 +46,12 @@ val Typography = Typography( fontSize = 14.sp, color = Green700 ), + h5 = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.W400, + lineHeight = 18.sp, + // letterSpacing = + ), button = TextStyle( fontSize = 14.sp, color = Color.White, diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/database/dao/CocktailDao.kt b/app/src/main/java/org/mixdrinks/mixdrinks/database/dao/CocktailDao.kt index 7efcb1a..3b87312 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/database/dao/CocktailDao.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/database/dao/CocktailDao.kt @@ -1,7 +1,6 @@ package org.mixdrinks.mixdrinks.database.dao -import android.util.Log import androidx.room.ColumnInfo import androidx.room.Dao import androidx.room.Embedded @@ -22,12 +21,14 @@ import org.mixdrinks.mixdrinks.database.entities.CocktailToGoodRelation import org.mixdrinks.mixdrinks.database.entities.CocktailToTag import org.mixdrinks.mixdrinks.database.entities.CocktailToTaste import org.mixdrinks.mixdrinks.database.entities.CocktailToTool +import org.mixdrinks.mixdrinks.database.entities.FilterWithCocktailIds import org.mixdrinks.mixdrinks.database.entities.Glassware import org.mixdrinks.mixdrinks.database.entities.Good import org.mixdrinks.mixdrinks.database.entities.Tag import org.mixdrinks.mixdrinks.database.entities.Taste import org.mixdrinks.mixdrinks.database.entities.Tool import org.mixdrinks.mixdrinks.features.data.CocktailFull +import org.mixdrinks.mixdrinks.features.data.SelectedFilter @Dao interface CocktailDao { @@ -59,7 +60,8 @@ interface CocktailDao { suspend fun getById(id: Int): CocktailSnapshotDatabase @Query("SELECT cocktail_id, name FROM cocktails") - suspend fun getAllShortCocktail(): List + suspend fun getAllCocktailShort(): List + } data class CocktailSnapshotDatabase( @@ -111,7 +113,6 @@ data class CocktailSnapshotDatabase( } fun toCocktailFull(): CocktailFull { - Log.d("good", goods.size.toString() + goods.toString()) return CocktailFull( id = CocktailId(cocktail.cocktailId), name = cocktail.name, @@ -122,9 +123,11 @@ data class CocktailSnapshotDatabase( id = GoodId(good.goodId), name = good.name, amount = goodsUnit.filter { - it.goodId == good.goodId }.first().amount, + it.goodId == good.goodId + }.first().amount, unit = goodsUnit.filter { - it.goodId == good.goodId }.first().unit, + it.goodId == good.goodId + }.first().unit, ) }, tools = tools.map { tool -> @@ -145,13 +148,35 @@ data class CocktailSnapshotDatabase( name = taste.name ) }, - glassware = CocktailFull.Glassware(id = GlasswareId(glassware.glasswareId), name = glassware.name) + glassware = CocktailFull.Glassware( + id = GlasswareId(glassware.glasswareId), + name = glassware.name + ) ) } } -data class CocktailShort( +data class Cocktails( @ColumnInfo(name = "cocktail_id") val cocktailId: Int, val name: String, + @Relation( + parentColumn = "cocktail_id", + entityColumn = "cocktail_id", + ) + val filters: List +) { + fun toCocktailsWithFilters(): CocktailsWithFilters { + return CocktailsWithFilters( + cocktailId = cocktailId, + name = name, + filters = filters.map { SelectedFilter(it.filterId, it.filterGroupId) } + ) + } +} + +data class CocktailsWithFilters( + val cocktailId: Int, + val name: String, + val filters: List ) diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/database/dao/FilterGroupDao.kt b/app/src/main/java/org/mixdrinks/mixdrinks/database/dao/FilterGroupDao.kt index b076a28..9163d24 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/database/dao/FilterGroupDao.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/database/dao/FilterGroupDao.kt @@ -11,16 +11,15 @@ import org.mixdrinks.mixdrinks.database.entities.FilterGroup import org.mixdrinks.mixdrinks.database.entities.FilterWithCocktailIds import org.mixdrinks.mixdrinks.database.entities.Filters - @Dao interface FilterGroupDao { @Insert(onConflict = OnConflictStrategy.REPLACE) @Transaction - suspend fun addAllFilterGroups(filterGroup: List) + suspend fun addFilterGroup(filterGroup: FilterGroup) @Insert(onConflict = OnConflictStrategy.REPLACE) @Transaction - suspend fun addAllFilters(filterGroup: List) + suspend fun addFilter(filterGroup: Filters) @Insert(onConflict = OnConflictStrategy.REPLACE) @Transaction @@ -28,15 +27,23 @@ interface FilterGroupDao { @Query("SELECT * FROM filter_groups") suspend fun getAllFilterGroups() : List + } data class FilterGroups( @Embedded val filterGroup: FilterGroup, @Relation( - parentColumn = "id", + parentColumn = "filter_group_id", entityColumn = "filter_group_id", ) - val filters: List + val filters: List, + @Relation( + parentColumn = "filter_group_id", + entityColumn = "filter_group_id", + ) + val cocktailIds: List, + + ) diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/database/entities/FilterGroup.kt b/app/src/main/java/org/mixdrinks/mixdrinks/database/entities/FilterGroup.kt index 9c0b961..8a9b2ac 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/database/entities/FilterGroup.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/database/entities/FilterGroup.kt @@ -9,14 +9,17 @@ import org.mixdrinks.dto.SelectionType @Entity(tableName = "filter_groups") data class FilterGroup( @PrimaryKey + @ColumnInfo(name = "filter_group_id") val id: Int, val name: String, val selectionType: SelectionType, ) -@Entity(tableName = "filters") +@Entity( + tableName = "filters", + primaryKeys = ["filter_id", "filter_group_id"], + ) data class Filters( - @PrimaryKey @ColumnInfo(name = "filter_id") val filterId : Int, @ColumnInfo(name = "filter_group_id") @@ -30,8 +33,8 @@ data class Filters( foreignKeys = [ ForeignKey( entity = Filters::class, - parentColumns = arrayOf("filter_id"), - childColumns = arrayOf("filter_id"), + parentColumns = arrayOf("filter_id", "filter_group_id"), + childColumns = arrayOf("filter_id", "filter_group_id"), onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE ), @@ -47,6 +50,8 @@ data class Filters( data class FilterWithCocktailIds( @ColumnInfo(name = "filter_id") val filterId : Int, + @ColumnInfo(name = "filter_group_id") + val filterGroupId: Int, @ColumnInfo(name = "cocktail_id") val cocktailId: Int, ) diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/common/ui/NotFoundScreen.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/common/ui/NotFoundScreen.kt index 1b10985..8aeae48 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/common/ui/NotFoundScreen.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/common/ui/NotFoundScreen.kt @@ -1,7 +1,6 @@ package org.mixdrinks.mixdrinks.features.common.ui import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -24,8 +23,8 @@ import org.mixdrinks.mixdrinks.R fun NotFoundScreen(modifier: Modifier, onNavigateToStart: () -> Unit) { Column( modifier = modifier + .padding(top = 50.dp) .fillMaxSize(), - verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Image( diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/common/ui/widgets/IconTextFieldIcon.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/common/ui/widgets/IconTextFieldIcon.kt new file mode 100644 index 0000000..890a655 --- /dev/null +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/common/ui/widgets/IconTextFieldIcon.kt @@ -0,0 +1,23 @@ +package org.mixdrinks.mixdrinks.features.common.ui.widgets + +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Close +import androidx.compose.runtime.Composable + + +@Composable +fun IconTextFieldIcon(text: String, onClick: () -> Unit) { + if (text.isNotEmpty()) { + IconButton(onClick = { onClick() }) { + Icon( + imageVector = Icons.Outlined.Close, + tint = MaterialTheme.colors.secondary, + contentDescription = null + ) + } + } +} + diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/data/CocktailFull.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/data/CocktailFull.kt index b3e4e3d..aaa587d 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/data/CocktailFull.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/data/CocktailFull.kt @@ -1,5 +1,6 @@ package org.mixdrinks.mixdrinks.features.data +import androidx.room.ColumnInfo import org.mixdrinks.dto.CocktailId import org.mixdrinks.dto.GlasswareId import org.mixdrinks.dto.GoodId @@ -43,3 +44,9 @@ data class CocktailFull( ) } +data class CocktailShort( + @ColumnInfo(name = "cocktail_id") + val cocktailId: Int, + val name: String, +) + diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/data/DetailGood.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/data/DetailGood.kt index ebad3e7..26c8e6a 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/data/DetailGood.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/data/DetailGood.kt @@ -1,17 +1,20 @@ package org.mixdrinks.mixdrinks.features.data -import org.mixdrinks.dto.GoodId -import org.mixdrinks.dto.ToolId - -data class DetailGood ( - val id: GoodId, +data class DetailGood( + val id: Int, val name: String, - val about: String -) + val about: String, -data class DetailTool ( - val id: ToolId, - val name: String, - val about: String ) +data class GoodType( + val id: Int, + val type: Type +) { + enum class Type { + GOOD, GLASSWARE, TOOL; + companion object { + fun fromString(value: String) = Type.values().first() { it.toString() == value } + } + } +} diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/data/FilterGroupFull.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/data/FilterGroupFull.kt index 474b2b9..c265d87 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/data/FilterGroupFull.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/data/FilterGroupFull.kt @@ -6,12 +6,20 @@ data class FilterGroupFull( val id: Int, val name: String, val selectionType: SelectionType, - val filters: List - ) { - data class Filters( + val filters: List +) { + data class Filter( val id: Int, val name: String, - val cocktailIds: Set + val enabled: Boolean, + val checked: Boolean, + val cocktailIds: List ) } +data class SelectedFilter( + val filterId: Int, + val filterGroupId: Int, +) + + diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/data/CocktailProvider.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/data/MixDrinkService.kt similarity index 86% rename from app/src/main/java/org/mixdrinks/mixdrinks/features/data/CocktailProvider.kt rename to app/src/main/java/org/mixdrinks/mixdrinks/features/data/MixDrinkService.kt index 37d4a2a..6e54003 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/data/CocktailProvider.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/data/MixDrinkService.kt @@ -3,7 +3,7 @@ package org.mixdrinks.mixdrinks.features.data import org.mixdrinks.dto.SnapshotDto import retrofit2.http.GET -interface CocktailProvider { +interface MixDrinkService { @GET("snapshot") suspend fun getAllCocktails(): SnapshotDto } diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/UiComponent.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/UiComponent.kt index a92fb92..4323865 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/UiComponent.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/UiComponent.kt @@ -1,16 +1,19 @@ package org.mixdrinks.mixdrinks.features.detail.ui -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.material.Icon -import androidx.compose.material.IconButton +import androidx.compose.foundation.layout.size import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource @@ -19,26 +22,40 @@ import org.mixdrinks.mixdrinks.R @Composable fun Header(modifier: Modifier, text: String, onClick: () -> Unit) { - Row( - modifier = modifier - .fillMaxWidth(1f), - horizontalArrangement = Arrangement.Start - ) { - IconButton( - modifier = modifier.height(24.dp), - onClick = { onClick() } + Column { + Row( + modifier = modifier + .background(MaterialTheme.colors.primary) + .fillMaxWidth() + .height(52.dp), ) { - Icon( - painter = painterResource(id = R.drawable.baseline_arrow_back_ios_24), - tint = Color.Black, - contentDescription = null + Box( + modifier = modifier.size(52.dp) + .clickable { + onClick() + } + ) { + Image( + modifier = modifier + .align(Alignment.Center) + .size(32.dp) + .padding(start = 12.dp), + painter = painterResource(R.drawable.baseline_arrow_back_ios_24), + contentDescription = "Test" + ) + } + Text( + modifier = modifier.padding(start = 4.dp) + .align(Alignment.CenterVertically), + color = Color.White, + text = text, + style = MaterialTheme.typography.h2, + softWrap = false, + maxLines = 1, ) } - Spacer(modifier = modifier.padding(start = 15.dp)) - Text( - text = text, - style = MaterialTheme.typography.h1 - ) } } + + diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/DetailScreenCocktail.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/DetailScreenCocktail.kt index 5e2fc6e..e0d5049 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/DetailScreenCocktail.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/DetailScreenCocktail.kt @@ -29,6 +29,7 @@ import org.mixdrinks.mixdrinks.R import org.mixdrinks.mixdrinks.features.common.ui.ErrorLoadingScreen import org.mixdrinks.mixdrinks.features.common.ui.LoaderIndicatorScreen import org.mixdrinks.mixdrinks.features.data.CocktailFull +import org.mixdrinks.mixdrinks.features.data.GoodType import org.mixdrinks.mixdrinks.features.detail.ui.Header @Suppress("LongParameterList") @@ -36,28 +37,30 @@ import org.mixdrinks.mixdrinks.features.detail.ui.Header fun DetailScreen( modifier: Modifier, cocktailId: Int, - onNavigateToDetailGood: (id: Int) -> Unit, - onNavigateToDetailTool: (id: Int) -> Unit, + onNavigateToDetailGood: (goodType: GoodType) -> Unit, onBack: () -> Unit, + onNavigateToStart: () -> Unit, viewModel: DetailScreenCocktailViewModel = koinViewModel { parametersOf(cocktailId) }, ) { val cocktail by viewModel.uiState.collectAsState() - when(cocktail) { + when (cocktail) { is DetailScreenCocktailViewModel.DetailUiState.Loaded -> { val data = (cocktail as DetailScreenCocktailViewModel.DetailUiState.Loaded).itemState DetailsScreenData( modifier = modifier, data = data, - onNavigateToDetailTool = onNavigateToDetailTool, onNavigateToDetailGood = onNavigateToDetailGood, onBack = onBack, - viewModel = viewModel + viewModel = viewModel, + onClickTagAction = onNavigateToStart ) } + is DetailScreenCocktailViewModel.DetailUiState.Loading -> { LoaderIndicatorScreen(modifier = modifier) } + else -> { val error = cocktail as DetailScreenCocktailViewModel.DetailUiState.Error Log.d("Exception", error.message) @@ -71,91 +74,92 @@ fun DetailScreen( fun DetailsScreenData( modifier: Modifier, data: DetailItemUiState, - onNavigateToDetailGood: (id: Int) -> Unit, - onNavigateToDetailTool: (id: Int) -> Unit, + onNavigateToDetailGood: (goodType: GoodType) -> Unit, onBack: () -> Unit, - viewModel: DetailScreenCocktailViewModel - ) { - Column( - modifier = modifier - .verticalScroll(rememberScrollState()) - .fillMaxWidth(1f) - .fillMaxHeight(1f) - .padding(10.dp) - ) { + viewModel: DetailScreenCocktailViewModel, + onClickTagAction: () -> Unit, +) { + Column { Header( modifier = modifier, text = data.cocktail.name, onClick = onBack ) - Spacer(modifier = modifier.padding(5.dp)) - - TagListItem( - modifier = modifier, - listTags = data.cocktail.tags, - onClickAction = { Log.d("MyLog", it.toString()) } - ) - Spacer(modifier = modifier.padding(5.dp)) - - AsyncImage( - model = ImageUrlCreators.createUrl( - data.cocktail.id, - ImageUrlCreators.Size.SIZE_320 - ), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier + Column( + modifier = modifier + .verticalScroll(rememberScrollState()) .fillMaxWidth(1f) - .size(width = 300.dp, height = 200.dp), - ) - Spacer(modifier = modifier.padding(top = 20.dp)) + .fillMaxHeight(1f) + ) { + TagListItem( + modifier = modifier.padding(5.dp), + listTags = data.cocktail.tags, + onClickAction = { tagId -> + viewModel.onClickTag(tagId) + onClickTagAction() + } + ) - CocktailRecipeContent( - modifier = modifier, - cocktailName = data.cocktail.name, - cocktailReceipt = data.cocktail.receipt - ) - Spacer(modifier = modifier.padding(top = 10.dp)) + AsyncImage( + model = ImageUrlCreators.createUrl( + data.cocktail.id, ImageUrlCreators.Size.SIZE_320 + ), + contentDescription = data.cocktail.name, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth(1f) + .size(width = 300.dp, height = 200.dp), + ) + Column(modifier = modifier.padding(5.dp)) { + CocktailRecipeContent( + modifier = modifier, + cocktailReceipt = data.cocktail.receipt + ) + Spacer(modifier = modifier.padding(top = 10.dp)) - CocktailIngredientsContent( - modifier = modifier, - onClick = onNavigateToDetailGood, - viewModel = viewModel, - data = data - ) - Spacer(modifier = modifier.padding(top = 15.dp)) + CocktailIngredientsContent( + modifier = modifier, + onClick = onNavigateToDetailGood, + viewModel = viewModel, + data = data + ) + Spacer(modifier = modifier.padding(top = 15.dp)) - CocktailNeedToolsContent( - modifier = modifier, - cocktailName = data.cocktail.name, - cocktailTools = data.cocktail.tools, - onClick = onNavigateToDetailTool - ) + CocktailToolsContent( + modifier = modifier, + cocktailTools = data.cocktail.tools, + glassware = data.cocktail.glassware, + onClick = onNavigateToDetailGood + ) + } + } } + + } @Composable private fun CocktailIngredientsContent( modifier: Modifier, - onClick: (id: Int) -> Unit, + onClick: (goodType: GoodType) -> Unit, viewModel: DetailScreenCocktailViewModel, data: DetailItemUiState ) { Row( modifier = modifier - .fillMaxWidth(1f), + .fillMaxWidth(1f) + .padding(bottom = 15.dp, top = 10.dp), horizontalArrangement = Arrangement.Start ) { Text( style = MaterialTheme.typography.h2, - text = "${stringResource(R.string.cocktail_ingredients)} ${data.cocktail.name}" + text = stringResource(R.string.cocktail_ingredients) ) - } + Spacer(modifier.weight(1f)) + CocktailPortions(modifier = modifier, viewModel = viewModel, data = data) - Spacer(modifier = modifier.padding(top = 15.dp)) - CocktailPortions(modifier = modifier, viewModel = viewModel, data = data ) - Spacer(modifier = modifier.padding(bottom = 15.dp)) - GoodsListItem( + } + GoodsListItems( modifier = modifier, onClick = onClick, data = data @@ -170,10 +174,10 @@ private fun CocktailPortions( ) { Row { SquareMarker( - modifier = modifier - .clickable { - viewModel.decPortion() - }, + modifier = modifier + .clickable { + viewModel.decPortion() + }, text = "-", ) Spacer(modifier = modifier.padding(5.dp)) @@ -195,11 +199,11 @@ private fun CocktailPortions( } @Composable -fun CocktailNeedToolsContent( +fun CocktailToolsContent( modifier: Modifier, - cocktailName: String, cocktailTools: List, - onClick: (id: Int) -> Unit + glassware: CocktailFull.Glassware, + onClick: (goodType: GoodType) -> Unit ) { Row( modifier = modifier @@ -208,14 +212,15 @@ fun CocktailNeedToolsContent( ) { Text( style = MaterialTheme.typography.h2, - text = "${stringResource(R.string.need_tools)} $cocktailName" + text = stringResource(R.string.need_tools) ) } Spacer(modifier = modifier.padding(bottom = 15.dp)) - ToolsListItem( + ToolsListItems( modifier = modifier, tools = cocktailTools, - onCLick = onClick + glassware = glassware, + onClick = onClick ) } diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/DetailScreenCocktailViewModel.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/DetailScreenCocktailViewModel.kt index 35e66ab..0d30b42 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/DetailScreenCocktailViewModel.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/DetailScreenCocktailViewModel.kt @@ -7,12 +7,15 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import org.mixdrinks.mixdrinks.database.AppDatabase import org.mixdrinks.mixdrinks.features.data.CocktailFull +import org.mixdrinks.mixdrinks.features.data.SelectedFilter +import org.mixdrinks.mixdrinks.features.start.filter.SelectedFilterStorage import java.io.IOException @Suppress("TooGenericExceptionCaught") class DetailScreenCocktailViewModel( private val cocktailId: Int, private val roomDatabase: AppDatabase, + private val filterStorage: SelectedFilterStorage ) : ViewModel() { private val _uiState = MutableStateFlow(DetailUiState.Loading) @@ -21,7 +24,6 @@ class DetailScreenCocktailViewModel( private lateinit var cocktail: CocktailFull private var portions: Int = 1 - init { _uiState.value = DetailUiState.Loading viewModelScope.launch { @@ -46,6 +48,12 @@ class DetailScreenCocktailViewModel( _uiState.value = DetailUiState.Loaded(DetailItemUiState(cocktail, portions)) } + fun onClickTag(id: Int) { + // Filter group for tags is 0 in Database + val filterGroupId = 0 + filterStorage.onClickTag(SelectedFilter(filterId = id, filterGroupId = filterGroupId)) + } + sealed class DetailUiState { object Loading : DetailUiState() class Loaded(val itemState: DetailItemUiState) : DetailUiState() diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/GoodsListItem.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/GoodsListItem.kt index 0b3fed7..53e2198 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/GoodsListItem.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/GoodsListItem.kt @@ -24,32 +24,36 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import org.mixdrinks.domain.ImageUrlCreators +import org.mixdrinks.dto.GoodId import org.mixdrinks.mixdrinks.features.data.CocktailFull +import org.mixdrinks.mixdrinks.features.data.GoodType @Suppress("MagicNumber") @Composable -fun GoodsListItem( +fun GoodsListItems( modifier: Modifier, data: DetailItemUiState, - onClick: (id :Int) -> Unit + onClick: (goodType: GoodType) -> Unit ) { LazyHorizontalGrid( rows = GridCells.Fixed(1), horizontalArrangement = Arrangement.spacedBy(8.dp), - modifier = modifier.height(240.dp) + modifier = modifier.height(200.dp) ) { items(data.cocktail.goods) { item -> Card( modifier = modifier - .clickable { onClick(item.id.id) }, + .clickable { onClick(GoodType(id = item.id.id, type = GoodType.Type.GOOD)) }, ) { Column( modifier = modifier .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { - ImageItem(modifier = modifier, - ImageUrlCreators.createUrl(item.id, ImageUrlCreators.Size.SIZE_320) + ImageItem( + modifier = modifier, + ImageUrlCreators.createUrl(item.id, ImageUrlCreators.Size.SIZE_320), + description = item.name ) Row( modifier = modifier @@ -78,57 +82,86 @@ fun GoodsListItem( } @Composable -fun ToolsListItem(modifier: Modifier, tools: List, onCLick: (id: Int) -> Unit) { +fun ToolsListItems( + modifier: Modifier, + tools: List, + glassware: CocktailFull.Glassware, + onClick: (goodType: GoodType) -> Unit +) { LazyHorizontalGrid( rows = GridCells.Fixed(1), horizontalArrangement = Arrangement.spacedBy(8.dp), - modifier = modifier.height(220.dp) + modifier = modifier.height(180.dp) ) { + item { + ToolsListItem( + modifier = modifier, + id = glassware.id.value, + name = glassware.name, + type = GoodType.Type.GLASSWARE, + onClick = onClick + ) + } items(tools) { item -> - Card( + ToolsListItem( + modifier = modifier, id = item.id.id, + name = item.name, type = GoodType.Type.TOOL, onClick = onClick + ) + } + } +} + +@Composable +fun ToolsListItem( + modifier: Modifier, + id: Int, + name: String, + type: GoodType.Type, + onClick: (goodType: GoodType) -> Unit +) { + Card( + modifier = modifier + .clickable { + onClick(GoodType(id = id, type)) + }, + ) { + Column( + modifier = modifier + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + ImageItem( + modifier = modifier, + ImageUrlCreators.createUrl(GoodId(id), ImageUrlCreators.Size.SIZE_320), + description = name + ) + Row( modifier = modifier - .clickable { onCLick(item.id.id) }, + .fillMaxWidth(1f), + horizontalArrangement = Arrangement.Start ) { - Column( - modifier = modifier - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - ImageItem( - modifier = modifier, - ImageUrlCreators.createUrl( - item.id, - ImageUrlCreators.Size.SIZE_320 - ) - ) - Row( - modifier = modifier - .fillMaxWidth(1f), - horizontalArrangement = Arrangement.Start - ) { - Text( - style = MaterialTheme.typography.body1, - text = item.name, - ) - } - } + Text( + style = MaterialTheme.typography.body1, + text = name, + ) } } } } + @Composable -fun ImageItem(modifier:Modifier, url: String) { +private fun ImageItem(modifier: Modifier, url: String, description: String) { AsyncImage( modifier = modifier .border( BorderStroke(2.dp, MaterialTheme.colors.primary), shape = RoundedCornerShape(16.dp), ) - .size(190.dp) + .size(150.dp) .padding(5.dp), model = url, - contentDescription = null, + contentDescription = description, contentScale = ContentScale.Inside, ) } diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/RecipeContent.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/RecipeContent.kt index 077f211..6a0102f 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/RecipeContent.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/cocktail/RecipeContent.kt @@ -26,7 +26,7 @@ import org.mixdrinks.mixdrinks.R import org.mixdrinks.mixdrinks.app.ui.theme.Green700 @Composable -fun CocktailRecipeContent(modifier: Modifier, cocktailName: String, cocktailReceipt: List) { +fun CocktailRecipeContent(modifier: Modifier, cocktailReceipt: List) { Row( modifier = modifier .fillMaxWidth(1f), @@ -34,7 +34,7 @@ fun CocktailRecipeContent(modifier: Modifier, cocktailName: String, cocktailRece ) { Text( style = MaterialTheme.typography.h2, - text = "${stringResource(R.string.cocktail_recipe)} $cocktailName", + text = stringResource(R.string.cocktail_recipe), ) } Spacer(modifier = modifier.padding(top = 5.dp)) diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/good/DetailScreenGood.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/good/DetailScreenGood.kt deleted file mode 100644 index 83f0664..0000000 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/good/DetailScreenGood.kt +++ /dev/null @@ -1,111 +0,0 @@ -package org.mixdrinks.mixdrinks.features.detail.ui.good - -import android.util.Log -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage -import org.koin.androidx.compose.koinViewModel -import org.koin.core.parameter.parametersOf -import org.mixdrinks.domain.ImageUrlCreators -import org.mixdrinks.mixdrinks.R -import org.mixdrinks.mixdrinks.features.common.ui.ErrorLoadingScreen -import org.mixdrinks.mixdrinks.features.common.ui.LoaderIndicatorScreen -import org.mixdrinks.mixdrinks.features.data.DetailGood -import org.mixdrinks.mixdrinks.features.detail.ui.Header - -@Composable -fun DetailScreenGood( - modifier: Modifier, - goodId: Int, - onBack: () -> Unit, - viewModel: DetailScreenGoodViewModel = koinViewModel { parametersOf(goodId) }, -) { - val cocktail by viewModel.uiState.collectAsState() - - when(cocktail) { - is DetailScreenGoodViewModel.DetailGoodUiState.Loaded -> { - val data = (cocktail as DetailScreenGoodViewModel.DetailGoodUiState.Loaded).itemState - DetailScreenGoodData(modifier = modifier, data.good, onBack = onBack) - } - is DetailScreenGoodViewModel.DetailGoodUiState.Loading -> { - LoaderIndicatorScreen(modifier = modifier) - } - else -> { - val error = cocktail as DetailScreenGoodViewModel.DetailGoodUiState.Error - Log.d("Exception", error.message) - ErrorLoadingScreen(modifier = modifier) - } - } -} - -@Suppress("MagicNumber") -@Composable -fun DetailScreenGoodData(modifier: Modifier, good: DetailGood, onBack: () -> Unit) { - Column( - modifier = modifier - .verticalScroll(rememberScrollState()) - .fillMaxWidth(1f) - .fillMaxHeight(1f) - .padding(10.dp) - ) { - Header( - modifier = modifier, - text = good.name, - onClick = onBack - ) - Spacer(modifier = modifier.padding(5.dp)) - - AsyncImage( - model = ImageUrlCreators.createUrl( - good.id, - ImageUrlCreators.Size.SIZE_320 - ), - contentDescription = null, - contentScale = ContentScale.FillHeight, - modifier = Modifier - .fillMaxWidth(1f) - .height(300.dp), - ) - Spacer(modifier = modifier.padding(top = 20.dp)) - - Row( - modifier = modifier - .fillMaxWidth(1f), - horizontalArrangement = Arrangement.Start - ) { - Text( - style = MaterialTheme.typography.h2, - text = "${stringResource(R.string.description)} ${good.name}" - ) - } - Spacer(modifier = modifier.padding(15.dp)) - - Row( - modifier = modifier - .fillMaxWidth(1f), - horizontalArrangement = Arrangement.Start - ) { - Text( - style = MaterialTheme.typography.h3, - text = good.about, - ) - } - } -} diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/goods/DetailScreenGoods.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/goods/DetailScreenGoods.kt new file mode 100644 index 0000000..4181811 --- /dev/null +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/goods/DetailScreenGoods.kt @@ -0,0 +1,117 @@ +package org.mixdrinks.mixdrinks.features.detail.ui.goods + +import android.util.Log +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf +import org.mixdrinks.domain.ImageUrlCreators +import org.mixdrinks.dto.GoodId +import org.mixdrinks.mixdrinks.R +import org.mixdrinks.mixdrinks.features.common.ui.ErrorLoadingScreen +import org.mixdrinks.mixdrinks.features.common.ui.LoaderIndicatorScreen +import org.mixdrinks.mixdrinks.features.data.DetailGood +import org.mixdrinks.mixdrinks.features.data.GoodType +import org.mixdrinks.mixdrinks.features.detail.ui.Header + +@Composable +fun DetailScreenGoods( + modifier: Modifier, + goodType: GoodType, + onBack: () -> Unit, + viewModel: DetailScreenGoodsViewModel = koinViewModel { parametersOf(goodType) }, +) { + val good by viewModel.uiState.collectAsState() + + when (good) { + is DetailScreenGoodsViewModel.DetailGoodUiState.Loaded -> { + val data = (good as DetailScreenGoodsViewModel.DetailGoodUiState.Loaded).itemState + DetailScreenGoodsData(modifier = modifier, data.good, onBack = onBack) + } + + is DetailScreenGoodsViewModel.DetailGoodUiState.Loading -> { + LoaderIndicatorScreen(modifier = modifier) + } + + else -> { + val error = good as DetailScreenGoodsViewModel.DetailGoodUiState.Error + Log.d("Exception", error.message) + ErrorLoadingScreen(modifier = modifier) + } + } +} + +@Suppress("MagicNumber") +@Composable +fun DetailScreenGoodsData(modifier: Modifier, good: DetailGood, onBack: () -> Unit) { + Column { + Header( + modifier = modifier, + text = good.name, + onClick = onBack + ) + Column( + modifier = modifier + .verticalScroll(rememberScrollState()) + .fillMaxWidth(1f) + .fillMaxHeight(1f) + .padding(10.dp) + ) { + Spacer(modifier = modifier.padding(5.dp)) + + AsyncImage( + model = ImageUrlCreators.createUrl( + GoodId(good.id), + ImageUrlCreators.Size.SIZE_320 + ), + contentDescription = null, + contentScale = ContentScale.FillHeight, + modifier = Modifier + .fillMaxWidth(1f) + .height(300.dp), + ) + Spacer(modifier = modifier.padding(top = 20.dp)) + + Row( + modifier = modifier + .fillMaxWidth(1f), + horizontalArrangement = Arrangement.Start + ) { + Text( + style = MaterialTheme.typography.h2, + text = "${stringResource(R.string.description)} ${good.name}" + ) + } + Spacer(modifier = modifier.padding(15.dp)) + + Row( + modifier = modifier + .fillMaxWidth(1f), + horizontalArrangement = Arrangement.Start + ) { + Text( + style = MaterialTheme.typography.h3, + text = good.about, + ) + } + } + } +} diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/good/DetailScreenGoodViewModel.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/goods/DetailScreenGoodsViewModel.kt similarity index 54% rename from app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/good/DetailScreenGoodViewModel.kt rename to app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/goods/DetailScreenGoodsViewModel.kt index 95dfda8..4e19a02 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/good/DetailScreenGoodViewModel.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/goods/DetailScreenGoodsViewModel.kt @@ -1,18 +1,18 @@ -package org.mixdrinks.mixdrinks.features.detail.ui.good +package org.mixdrinks.mixdrinks.features.detail.ui.goods import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import org.mixdrinks.dto.GoodId import org.mixdrinks.mixdrinks.database.AppDatabase import org.mixdrinks.mixdrinks.features.data.DetailGood +import org.mixdrinks.mixdrinks.features.data.GoodType import java.io.IOException @Suppress("TooGenericExceptionCaught") -class DetailScreenGoodViewModel( - private val goodId: Int, +class DetailScreenGoodsViewModel( + private val goodType: GoodType, private val roomDatabase: AppDatabase, ) : ViewModel() { @@ -24,17 +24,26 @@ class DetailScreenGoodViewModel( viewModelScope.launch { try { - val result = roomDatabase.goodDao().getGoodById(goodId) + val result = when (goodType.type) { + GoodType.Type.GOOD -> { + val good = roomDatabase.goodDao().getGoodById(goodType.id) + DetailGood(id = good.goodId, name = good.name, about = good.about) + } - _uiState.value = DetailGoodUiState.Loaded( - DetailGoodItemUiState( + GoodType.Type.TOOL -> { + val tool = roomDatabase.toolDao().getToolById(goodType.id) + DetailGood(id = tool.toolId, name = tool.name, about = tool.about) + } + + GoodType.Type.GLASSWARE -> { + val glassware = roomDatabase.glasswareDao().getById(goodType.id) DetailGood( - id = GoodId(result.goodId), - name = result.name, - about = result.about + id = glassware.glasswareId, name = glassware.name, + about = glassware.about ) - ) - ) + } + } + _uiState.value = DetailGoodUiState.Loaded(DetailGoodItemUiState(result)) } catch (error: IOException) { _uiState.value = DetailGoodUiState.Error(error.toString()) } catch (error: Exception) { diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/tool/DetailScreenTool.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/tool/DetailScreenTool.kt deleted file mode 100644 index 2c08a3c..0000000 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/tool/DetailScreenTool.kt +++ /dev/null @@ -1,110 +0,0 @@ -package org.mixdrinks.mixdrinks.features.detail.ui.tool - -import android.util.Log -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage -import org.koin.androidx.compose.koinViewModel -import org.koin.core.parameter.parametersOf -import org.mixdrinks.domain.ImageUrlCreators -import org.mixdrinks.mixdrinks.R -import org.mixdrinks.mixdrinks.features.common.ui.ErrorLoadingScreen -import org.mixdrinks.mixdrinks.features.common.ui.LoaderIndicatorScreen -import org.mixdrinks.mixdrinks.features.data.DetailTool -import org.mixdrinks.mixdrinks.features.detail.ui.Header - -@Composable -fun DetailScreenTool( - modifier: Modifier, - toolId: Int, - onBack: () -> Unit, - viewModel: DetailScreenToolViewModel = koinViewModel { parametersOf(toolId) }, -) { - val cocktail by viewModel.uiState.collectAsState() - - when(cocktail) { - is DetailScreenToolViewModel.DetailToolUiState.Loaded -> { - val data = (cocktail as DetailScreenToolViewModel.DetailToolUiState.Loaded).itemState - DetailScreenToolData(modifier = modifier, data.tool, onBack = onBack) - } - is DetailScreenToolViewModel.DetailToolUiState.Loading -> { - LoaderIndicatorScreen(modifier = modifier) - } - else -> { - val error = cocktail as DetailScreenToolViewModel.DetailToolUiState.Error - Log.d("Exception", error.message) - ErrorLoadingScreen(modifier = modifier) - } - } -} -@Suppress("MagicNumber") -@Composable -fun DetailScreenToolData(modifier: Modifier, tool: DetailTool, onBack: () -> Unit) { - Column( - modifier = modifier - .verticalScroll(rememberScrollState()) - .fillMaxWidth(1f) - .fillMaxHeight(1f) - .padding(10.dp) - ) { - Header( - modifier = modifier, - text = tool.name, - onClick = onBack - ) - Spacer(modifier = modifier.padding(5.dp)) - - AsyncImage( - model = ImageUrlCreators.createUrl( - toolId = tool.id, - ImageUrlCreators.Size.SIZE_320 - ), - contentDescription = null, - contentScale = ContentScale.FillHeight, - modifier = Modifier - .fillMaxWidth(1f) - .height(300.dp), - ) - Spacer(modifier = modifier.padding(top = 20.dp)) - - Row( - modifier = modifier - .fillMaxWidth(1f), - horizontalArrangement = Arrangement.Start - ) { - Text( - style = MaterialTheme.typography.h2, - text = "${stringResource(R.string.description)} ${tool.name}", - ) - } - Spacer(modifier = modifier.padding(15.dp)) - - Row( - modifier = modifier - .fillMaxWidth(1f), - horizontalArrangement = Arrangement.Start - ) { - Text( - style = MaterialTheme.typography.h3, - text = tool.about, - ) - } - } -} diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/tool/DetailScreenToolViewModel.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/tool/DetailScreenToolViewModel.kt deleted file mode 100644 index 6468b09..0000000 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/detail/ui/tool/DetailScreenToolViewModel.kt +++ /dev/null @@ -1,55 +0,0 @@ -package org.mixdrinks.mixdrinks.features.detail.ui.tool - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import org.mixdrinks.dto.ToolId -import org.mixdrinks.mixdrinks.database.AppDatabase -import org.mixdrinks.mixdrinks.features.data.DetailTool -import java.io.IOException - -@Suppress("TooGenericExceptionCaught") -class DetailScreenToolViewModel( - private val toolId: Int, - private val roomDatabase: AppDatabase, -) : ViewModel() { - - private val _uiState = MutableStateFlow(DetailToolUiState.Loading) - val uiState: StateFlow = _uiState - - init { - _uiState.value = DetailToolUiState.Loading - - viewModelScope.launch { - try { - val result = roomDatabase.toolDao().getToolById(toolId) - - _uiState.value = DetailToolUiState.Loaded( - DetailToolItemUiState( - DetailTool( - id = ToolId(result.toolId), - name = result.name, - about = result.about - ) - ) - ) - } catch (error: IOException) { - _uiState.value = DetailToolUiState.Error(error.toString()) - } catch (error: Exception) { - _uiState.value = DetailToolUiState.Error(error.toString()) - } - } - } - - sealed class DetailToolUiState { - object Loading : DetailToolUiState() - class Loaded(val itemState: DetailToolItemUiState) : DetailToolUiState() - class Error(val message: String) : DetailToolUiState() - } -} - -data class DetailToolItemUiState( - val tool: DetailTool -) diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/fetcher/Fetcher.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/fetcher/Fetcher.kt index cac5ec9..8b44669 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/fetcher/Fetcher.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/fetcher/Fetcher.kt @@ -1,8 +1,6 @@ package org.mixdrinks.mixdrinks.features.fetcher import androidx.room.Transaction -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.launch import org.mixdrinks.dto.CocktailDto import org.mixdrinks.dto.FilterGroupDto import org.mixdrinks.dto.GlasswareDto @@ -25,19 +23,15 @@ import org.mixdrinks.mixdrinks.database.entities.Good import org.mixdrinks.mixdrinks.database.entities.Tag import org.mixdrinks.mixdrinks.database.entities.Taste import org.mixdrinks.mixdrinks.database.entities.Tool -import org.mixdrinks.mixdrinks.features.data.CocktailProvider +import org.mixdrinks.mixdrinks.features.data.MixDrinkService class Fetcher( - private val cocktailProvider: CocktailProvider, - private val roomDatabase: AppDatabase + private val cocktailProvider: MixDrinkService, + private val roomDatabase: AppDatabase, ) { - private val scope = MainScope() - - init { - scope.launch { - if (roomDatabase.cocktailDao().getAll().isEmpty()) { - insertToDataBase(cocktailProvider.getAllCocktails()) - } + suspend fun startFetch() { + if (roomDatabase.cocktailDao().getAll().isEmpty()) { + insertToDataBase(cocktailProvider.getAllCocktails()) } } private suspend fun insertToDataBase(snapshot: SnapshotDto) { @@ -49,6 +43,7 @@ class Fetcher( insertAllCocktails(snapshot.cocktails) insertAllFilters(snapshot.filterGroups) } + @Transaction private suspend fun insertAllGlasswares(glasswares: List) { roomDatabase.glasswareDao().addAll(glasswares.map { glassware -> @@ -59,6 +54,7 @@ class Fetcher( ) }) } + @Transaction private suspend fun insertAllGoods(goods: List) { roomDatabase.goodDao().addAll(goods.map { good -> @@ -69,6 +65,7 @@ class Fetcher( ) }) } + @Transaction private suspend fun insertAllTags(tags: List) { roomDatabase.tagDao().addAll(tags.map { tag -> @@ -78,6 +75,7 @@ class Fetcher( ) }) } + @Transaction private suspend fun insertAllTastes(tastes: List) { roomDatabase.tasteDao().addAll(tastes.map { taste -> @@ -87,6 +85,7 @@ class Fetcher( ) }) } + @Transaction private suspend fun insertAllTools(tools: List) { roomDatabase.toolDao().addAll(tools.map { tool -> @@ -97,6 +96,7 @@ class Fetcher( ) }) } + @Transaction private suspend fun insertAllCocktails(cocktails: List) { roomDatabase.cocktailDao().addAllCocktails( @@ -146,35 +146,31 @@ class Fetcher( ) } } + @Transaction private suspend fun insertAllFilters(filterGroup: List) { - roomDatabase.filterGroupDao().addAllFilterGroups( - filterGroup.map { + filterGroup.map { item -> + roomDatabase.filterGroupDao().addFilterGroup( FilterGroup( - id = it.id.value, - name = it.name, - selectionType = it.selectionType + id = item.id.value, + name = item.name, + selectionType = item.selectionType ) - } - ) - filterGroup.map { - roomDatabase.filterGroupDao().addAllFilters( - it.filters.map { filter -> + ) + item.filters.map { + roomDatabase.filterGroupDao().addFilter( Filters( - filterId = filter.id.value, - filterGroupId = it.id.value, - name = filter.name + filterId = it.id.value, + filterGroupId = item.id.value, + name = it.name ) - } - ) - } - filterGroup.map { - it.filters.map { filter -> + ) roomDatabase.filterGroupDao().addAllFilterWithCocktailId( - filter.cocktailIds.map { cocktailId -> + it.cocktailIds.map { cocktailId -> FilterWithCocktailIds( - filterId = filter.id.value, - cocktailId = cocktailId.id + filterId = it.id.value, + cocktailId = cocktailId.id, + filterGroupId = item.id.value ) } ) diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/filter/ui/FilterScreenViewModel.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/filter/ui/FilterScreenViewModel.kt deleted file mode 100644 index fcba097..0000000 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/filter/ui/FilterScreenViewModel.kt +++ /dev/null @@ -1,81 +0,0 @@ -package org.mixdrinks.mixdrinks.features.filter.ui - -import android.util.Log -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import org.mixdrinks.mixdrinks.database.AppDatabase -import org.mixdrinks.mixdrinks.features.data.FilterGroupFull -import java.io.IOException - -@Suppress("TooGenericExceptionCaught") -class FilterScreenViewModel( - private val roomDatabase: AppDatabase -) : ViewModel() { - private val _listCheckedFilter: MutableState> = mutableStateOf(mutableListOf()) - val listCheckedFilter: State> = _listCheckedFilter - - private val _uiState = MutableStateFlow( - FilterUiState.Loading) - val uiState: StateFlow = _uiState - - init { - _uiState.value = FilterUiState.Loading - - viewModelScope.launch { - try { - val result = roomDatabase.filterGroupDao().getAllFilterGroups() - _uiState.value = FilterUiState.Loaded(FilterItemUiState( - result.map { - FilterGroupFull( - id = it.filterGroup.id, - name = it.filterGroup.name, - selectionType = it.filterGroup.selectionType, - filters = it.filters.map {filter -> - FilterGroupFull.Filters( - id = filter.filterId, - name = filter.name, - cocktailIds = setOf() - ) - } - ) - } - )) - } catch (error: IOException) { - _uiState.value = FilterUiState.Error(error.toString()) - } catch (error: Exception) { - _uiState.value = FilterUiState.Error(error.toString()) - } - } - } - - sealed class FilterUiState { - object Loading : FilterUiState() - class Loaded(val itemState: FilterItemUiState) : FilterUiState() - class Error(val message: String) : FilterUiState() - } - - fun checkedAction(id: Int) { - if(_listCheckedFilter.value.find { it == id } == null) { - _listCheckedFilter.value.add(id) - } else { - _listCheckedFilter.value.remove(id) - } - Log.d("CheckedAction", _listCheckedFilter.value.toString()) - } - - fun clearFilters() { - _listCheckedFilter.value.clear() - } -} - -data class FilterItemUiState( - val filters: List -) - - diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/header/ui/SearchHintContent.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/header/ui/SearchHintContent.kt deleted file mode 100644 index 39602ff..0000000 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/header/ui/SearchHintContent.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.mixdrinks.mixdrinks.features.header.ui - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - - -@Composable -fun SearchHintContent(modifier: Modifier, listHints: List = listItems, onClickAction: (item: String) -> Unit) { - LazyColumn( - contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - items(listHints) { item -> - Row( - modifier = modifier - .fillMaxWidth() - .clickable { - onClickAction(item) - } - ) { - Text( - color = MaterialTheme.colors.primary, - style = MaterialTheme.typography.h3, - text = item - ) - } - - } - } -} - -private val listItems = listOf( - "Джин тонік", - "Джин тонік 2", - "Джин тонік 3", -) - diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/StartRepository.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/StartRepository.kt new file mode 100644 index 0000000..4b5d83b --- /dev/null +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/StartRepository.kt @@ -0,0 +1,50 @@ +package org.mixdrinks.mixdrinks.features.start + +import org.mixdrinks.mixdrinks.database.AppDatabase +import org.mixdrinks.mixdrinks.database.dao.CocktailsWithFilters +import org.mixdrinks.mixdrinks.features.data.CocktailShort +import org.mixdrinks.mixdrinks.features.start.filter.SelectedFilterStorage + + +class StartRepository( + private val roomDatabase: AppDatabase, + private val filterStorage: SelectedFilterStorage, +) { + private var cocktailsFromDatabase: List = listOf() + private var cocktails: List = listOf() + suspend fun getCocktails(): List { + if (cocktailsFromDatabase.isEmpty()) { + cocktailsFromDatabase = getCocktailFromDatabase() + } + cocktails = if (filterStorage.selectedFilters.value.isNotEmpty()) { + filterCocktail(cocktailsFromDatabase).map { + CocktailShort(it.cocktailId, it.name) + } + } else { + cocktailsFromDatabase.map { + CocktailShort(it.cocktailId, it.name) + } + } + + return cocktails + } + + fun searchCocktail(search: String): List { + return cocktails.filter { it.name.lowercase().contains(search.lowercase()) } + } + + private fun filterCocktail(cocktails: List): List { + return cocktails.filter { cocktail -> + cocktail.filters.find { filters -> + filterStorage.selectedFilters.value.find { + it.filterId == filters.filterId && it.filterGroupId == filters.filterGroupId + } != null + } != null + } + } + + private suspend fun getCocktailFromDatabase(): List { + return roomDatabase.cocktailDao().getAllCocktailShort().map { it.toCocktailsWithFilters() } + } +} + diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/FilterRepository.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/FilterRepository.kt new file mode 100644 index 0000000..23933c5 --- /dev/null +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/FilterRepository.kt @@ -0,0 +1,51 @@ +package org.mixdrinks.mixdrinks.features.start.filter + +import org.mixdrinks.mixdrinks.database.AppDatabase +import org.mixdrinks.mixdrinks.database.dao.FilterGroups +import org.mixdrinks.mixdrinks.features.data.FilterGroupFull + +class FilterRepository( + private val roomDatabase: AppDatabase, + private val filterStorage: SelectedFilterStorage + +) { + private var filterGroups: List = listOf() + + suspend fun getFiltersByGroupId(groupId: Int, search: String): List { + return getFilters().first { it.id == groupId }.filters.filter { + it.name.lowercase().contains(search.lowercase()) + } + } + + suspend fun getFilters(): List { + if (filterGroups.isEmpty()) { + filterGroups = getFiltersFromDatabase() + } + return filterGroups.map { item -> + FilterGroupFull( + id = item.filterGroup.id, + name = item.filterGroup.name, + selectionType = item.filterGroup.selectionType, + filters = item.filters.map { filter -> + FilterGroupFull.Filter( + id = filter.filterId, + name = filter.name, + cocktailIds = item.cocktailIds.filter { + it.filterId == filter.filterId && it.filterGroupId == filter.filterGroupId + }.map { it.cocktailId }, + checked = (filterStorage.selectedFilters.value.find { item -> + item.filterId == filter.filterId && item.filterGroupId == filter.filterGroupId + } != null), + enabled = true + ) + } + ) + } + } + + private suspend fun getFiltersFromDatabase(): List { + return roomDatabase.filterGroupDao().getAllFilterGroups() + } + +} + diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/SelectedFilterStorage.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/SelectedFilterStorage.kt new file mode 100644 index 0000000..b04207e --- /dev/null +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/SelectedFilterStorage.kt @@ -0,0 +1,42 @@ +package org.mixdrinks.mixdrinks.features.start.filter + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import org.mixdrinks.mixdrinks.features.data.SelectedFilter + +class SelectedFilterStorage { + private val _selectedFilters = MutableStateFlow(mutableListOf()) + val selectedFilters: StateFlow> = _selectedFilters + + fun add(selectedFilter: SelectedFilter) { + val list = mutableListOf() + list.addAll((_selectedFilters.value)) + if (list.find { + it.filterId == selectedFilter.filterId && it.filterGroupId == selectedFilter.filterGroupId + } == null + ) { + list.add(selectedFilter) + } else { + list.remove(selectedFilter) + } + + _selectedFilters.update { + list.toMutableList() + } + } + + fun clear() { + _selectedFilters.update { + mutableListOf() + } + } + + fun onClickTag(selectedFilter: SelectedFilter) { + _selectedFilters.update { + mutableListOf(selectedFilter) + } + } + +} + diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/filter/ui/FilterScreen.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/ui/main/FilterScreen.kt similarity index 52% rename from app/src/main/java/org/mixdrinks/mixdrinks/features/filter/ui/FilterScreen.kt rename to app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/ui/main/FilterScreen.kt index 3d7ce4c..c1b4136 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/filter/ui/FilterScreen.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/ui/main/FilterScreen.kt @@ -1,4 +1,4 @@ -package org.mixdrinks.mixdrinks.features.filter.ui +package org.mixdrinks.mixdrinks.features.start.filter.ui.main import android.util.Log import androidx.compose.foundation.BorderStroke @@ -6,6 +6,8 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -13,9 +15,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button @@ -26,9 +25,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -40,26 +36,33 @@ import org.mixdrinks.mixdrinks.R import org.mixdrinks.mixdrinks.features.common.ui.ErrorLoadingScreen import org.mixdrinks.mixdrinks.features.common.ui.LoaderIndicatorScreen import org.mixdrinks.mixdrinks.features.data.FilterGroupFull - +import org.mixdrinks.mixdrinks.features.data.SelectedFilter +import java.util.Locale @Composable fun FilterScreen( modifier: Modifier, viewModel: FilterScreenViewModel = koinViewModel(), + onNavigateToStart: () -> Unit, + onNavigateToFilterSearch: (groupId: Int) -> Unit ) { val filters by viewModel.uiState.collectAsState() - when(filters) { + when (filters) { is FilterScreenViewModel.FilterUiState.Loaded -> { val data = (filters as FilterScreenViewModel.FilterUiState.Loaded).itemState FilterScreenData( modifier = modifier, filters = data.filters, - viewModel = viewModel + viewModel = viewModel, + onClickButtonApplyAction = onNavigateToStart, + onClickButtonAddAction = onNavigateToFilterSearch ) } + is FilterScreenViewModel.FilterUiState.Loading -> { LoaderIndicatorScreen(modifier = modifier) } + else -> { val error = filters as FilterScreenViewModel.FilterUiState.Error Log.d("Exception", error.message) @@ -67,12 +70,13 @@ fun FilterScreen( } } } - @Composable fun FilterScreenData( modifier: Modifier, filters: List, viewModel: FilterScreenViewModel, + onClickButtonApplyAction: () -> Unit, + onClickButtonAddAction: (groupId: Int) -> Unit ) { Column( modifier = modifier @@ -89,7 +93,7 @@ fun FilterScreenData( Text( modifier = modifier, text = stringResource(id = R.string.filter), - style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.W700,), + style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.W700), ) OutlinedButton( onClick = { viewModel.clearFilters() } @@ -102,39 +106,113 @@ fun FilterScreenData( } } Spacer(modifier = modifier.padding(top = 10.dp)) - Box( + + Row( modifier = modifier.weight(1f) ) { LazyColumn { items( - items = filters, - itemContent = { + items = filters.sortedBy { it.filters.size }, + itemContent = { filterGroup -> Text( - text = it.name, + text = filterGroup.name, style = MaterialTheme.typography.h2 ) - FilterCategoryItems( + FiltersItem( modifier = modifier, - items = it.filters, - viewModel = viewModel + filtersGroup = filterGroup, + viewModel = viewModel, + onClickButtonAddAction = onClickButtonAddAction ) } ) } } - ApplyButton(modifier = modifier) + ApplyButton(modifier = modifier, onClick = onClickButtonApplyAction) } } +@OptIn(ExperimentalLayoutApi::class) +@Suppress("MagicNumber") @Composable -fun ApplyButton(modifier: Modifier) { +private fun FiltersItem( + modifier: Modifier, + filtersGroup: FilterGroupFull, + viewModel: FilterScreenViewModel, + onClickButtonAddAction: (groupId: Int) -> Unit +) { + FlowRow(modifier = modifier) { + when { + (filtersGroup.filters.size < 20) -> { + filtersGroup.filters.forEach { item -> + ItemFlowRow( + item, + onClick = { filterId -> + viewModel.checkedAction( + SelectedFilter( + filterId = filterId, + filterGroupId = filtersGroup.id, + ) + ) + } + ) + } + } + + else -> { + filtersGroup.filters.filter { it.checked }.forEach { item -> + ItemFlowRow( + item, + onClick = { filterId -> + viewModel.checkedAction( + SelectedFilter( + filterId = filterId, + filterGroupId = filtersGroup.id, + ) + ) + } + ) + } + AddButton( + modifier = modifier, + text = filtersGroup.name, + onClick = { onClickButtonAddAction(filtersGroup.id) } + ) + } + } + } +} + + +@Composable +private fun ItemFlowRow(item: FilterGroupFull.Filter, onClick: (id: Int) -> Unit) { + OutlinedButton( + border = BorderStroke(1.dp, MaterialTheme.colors.primary), + shape = RoundedCornerShape(18.dp), + onClick = { + onClick(item.id) + }, + colors = ButtonDefaults.buttonColors( + backgroundColor = if (item.checked) MaterialTheme.colors.primary else Color.Transparent, + ) + ) { + Text( + text = item.name, + style = MaterialTheme.typography.h4, + color = if (item.checked) Color.White else MaterialTheme.colors.primary + ) + } +} + +@Composable +fun ApplyButton(modifier: Modifier, onClick: () -> Unit) { Box( modifier = modifier .background(Color.White) .fillMaxWidth() ) { Button( - onClick = { }, + onClick = { onClick() }, shape = RoundedCornerShape(16.dp), colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.primary), modifier = Modifier @@ -152,48 +230,33 @@ fun ApplyButton(modifier: Modifier) { } } -@Suppress("MagicNumber") @Composable -fun FilterCategoryItems( - modifier: Modifier, - items: List, - viewModel: FilterScreenViewModel -) { - //val listCheckedFilter by viewModel.listCheckedFilter - - val countRow = if(items.size < 5) 1 else 2 - val heightRow = if(countRow == 1) 40 else 80 // dp - LazyVerticalGrid ( - columns = GridCells.Fixed(4), - verticalArrangement = Arrangement.spacedBy(2.dp), +fun AddButton(modifier: Modifier, text: String, onClick: () -> Unit) { + Box( modifier = modifier - .height(heightRow.dp) - .fillMaxWidth(1f), - + .background(Color.White) + .fillMaxWidth() ) { - items( - items = items, - itemContent = { item -> - var isSelected by remember { mutableStateOf(false) } - OutlinedButton( - border = BorderStroke(1.dp, MaterialTheme.colors.primary), - shape = RoundedCornerShape(18.dp), - onClick = { - isSelected = !isSelected - viewModel.checkedAction(item.id) - }, - colors = ButtonDefaults.buttonColors( - backgroundColor = if(isSelected) MaterialTheme.colors.primary else Color.Transparent, - ) - ) { - Text( - text = item.name, - style = MaterialTheme.typography.h4, - color = if(isSelected) Color.White else MaterialTheme.colors.primary - ) - } - } - ) + OutlinedButton( + onClick = { onClick() }, + border = BorderStroke(1.dp, MaterialTheme.colors.primary), + shape = RoundedCornerShape(18.dp), + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + .height(40.dp) + .align(Alignment.Center) + ) { + Text( + text = "${stringResource(id = R.string.add)} ${text.lowercase(Locale.ROOT)} ${ + stringResource( + id = R.string.to_filter + ).lowercase() + }", + style = MaterialTheme.typography.h5, + color = Color.Black + ) + } } } diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/ui/main/FilterScreenViewModel.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/ui/main/FilterScreenViewModel.kt new file mode 100644 index 0000000..b4e1bcb --- /dev/null +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/ui/main/FilterScreenViewModel.kt @@ -0,0 +1,67 @@ +package org.mixdrinks.mixdrinks.features.start.filter.ui.main + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.mixdrinks.mixdrinks.features.data.FilterGroupFull +import org.mixdrinks.mixdrinks.features.data.SelectedFilter +import org.mixdrinks.mixdrinks.features.start.filter.FilterRepository +import org.mixdrinks.mixdrinks.features.start.filter.SelectedFilterStorage +import java.io.IOException + +@Suppress("TooGenericExceptionCaught") +class FilterScreenViewModel( + private val filterStorage: SelectedFilterStorage, + private val filterRepository: FilterRepository +) : ViewModel() { + private val _uiState = MutableStateFlow(FilterUiState.Loading) + val uiState: StateFlow = _uiState + + init { + _uiState.value = FilterUiState.Loading + + viewModelScope.launch { + filterStorage.selectedFilters.collect { + updateFilters() + } + } + viewModelScope.launch { + try { + updateFilters() + } catch (error: IOException) { + _uiState.value = FilterUiState.Error(error.toString()) + } catch (error: Exception) { + _uiState.value = FilterUiState.Error(error.toString()) + } + } + } + + private suspend fun updateFilters() { + _uiState.update { + FilterUiState.Loaded(FilterItemUiState(filterRepository.getFilters())) + } + } + + sealed class FilterUiState { + object Loading : FilterUiState() + class Loaded(val itemState: FilterItemUiState) : FilterUiState() + class Error(val message: String) : FilterUiState() + } + + fun checkedAction(selectedFilter: SelectedFilter) { + filterStorage.add(selectedFilter) + } + + fun clearFilters() { + filterStorage.clear() + } +} + +data class FilterItemUiState( + val filters: List +) + + diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/ui/search/FilterSearchScreen.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/ui/search/FilterSearchScreen.kt new file mode 100644 index 0000000..26fb9f1 --- /dev/null +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/ui/search/FilterSearchScreen.kt @@ -0,0 +1,235 @@ +package org.mixdrinks.mixdrinks.features.start.filter.ui.search + +import android.util.Log +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedButton +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf +import org.mixdrinks.domain.ImageUrlCreators +import org.mixdrinks.dto.CocktailId +import org.mixdrinks.mixdrinks.app.ui.theme.GreenAlfa +import org.mixdrinks.mixdrinks.features.common.ui.ErrorLoadingScreen +import org.mixdrinks.mixdrinks.features.common.ui.LoaderIndicatorScreen +import org.mixdrinks.mixdrinks.features.common.ui.widgets.IconTextFieldIcon +import org.mixdrinks.mixdrinks.features.data.FilterGroupFull +import org.mixdrinks.mixdrinks.features.data.SelectedFilter +import org.mixdrinks.mixdrinks.features.start.filter.ui.main.ApplyButton + +@Composable +fun FilterSearchScreen( + modifier: Modifier, + groupId: Int, + viewModel: FilterSearchScreenViewModel = koinViewModel { parametersOf(groupId) }, + onNavigateToFilter: () -> Unit +) { + val filters by viewModel.uiState.collectAsState() + when (filters) { + is FilterSearchScreenViewModel.FilterUiState.Loaded -> { + val data = (filters as FilterSearchScreenViewModel.FilterUiState.Loaded).itemState + + FilterSearchScreenData( + modifier = modifier, + filters = data.filters, + filterGroupId = groupId, + onNavigateToFilter = onNavigateToFilter, + viewModel = viewModel + ) + } + + is FilterSearchScreenViewModel.FilterUiState.Loading -> { + LoaderIndicatorScreen(modifier = modifier) + } + + else -> { + val error = filters as FilterSearchScreenViewModel.FilterUiState.Error + Log.d("Exception", error.message) + ErrorLoadingScreen(modifier = modifier) + } + } +} + +@Composable +fun FilterSearchScreenData( + modifier: Modifier, + onNavigateToFilter: () -> Unit, + filters: List, + filterGroupId: Int, + viewModel: FilterSearchScreenViewModel +) { + Box( + modifier = modifier + .background(Color.Gray) + .padding(top = 23.dp) + .clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)) + + ) { + Column( + modifier = modifier + .background(Color.White) + .fillMaxSize() + .padding(horizontal = 12.dp), + ) { + SearchTextField(modifier = modifier, + searchAction = { viewModel.searchAction(it) } + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + + } + Box( + modifier = Modifier + .weight(1f) + .padding(vertical = 10.dp) + ) { + LazyColumn { + items( + items = filters, + itemContent = { filters -> + FilterItem( + modifier = modifier, filter = filters, + onClick = { + viewModel.checkedAction( + SelectedFilter( + filterId = it, + filterGroupId = filterGroupId + ) + ) + } + ) + }) + } + } + ApplyButton(modifier = modifier, onClick = { onNavigateToFilter() }) + } + } +} + +@Composable +fun SearchTextField( + modifier: Modifier, + searchAction: (searchText: String) -> Unit, +) { + var textState by rememberSaveable { mutableStateOf("") } + + TextField( + colors = TextFieldDefaults.textFieldColors( + backgroundColor = Color.White, + textColor = Color.Black, + cursorColor = MaterialTheme.colors.primary, + focusedIndicatorColor = MaterialTheme.colors.primary, + unfocusedIndicatorColor = MaterialTheme.colors.primary, + focusedLabelColor = MaterialTheme.colors.primary, + ), + value = textState, + onValueChange = { + textState = it + searchAction(textState) + }, + modifier = modifier + .fillMaxWidth() + .height(50.dp), + singleLine = true, + trailingIcon = { + IconTextFieldIcon(text = textState, onClick = { + textState = "" + searchAction(textState) + }) + }, + ) +} + +@Composable +private fun FilterItem( + modifier: Modifier, filter: FilterGroupFull.Filter, onClick: (id: Int) -> Unit +) { + OutlinedButton( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 5.dp), + contentPadding = PaddingValues(0.dp), + border = BorderStroke(1.dp, MaterialTheme.colors.primary), + shape = RoundedCornerShape(8.dp), + onClick = { + onClick(filter.id) + }, + colors = ButtonDefaults.buttonColors( + backgroundColor = if (filter.checked) GreenAlfa else Color.Transparent, + ) + ) { + Row( + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + AsyncImage( + model = ImageUrlCreators.createUrl( + CocktailId(filter.id), ImageUrlCreators.Size.SIZE_320 + ), + contentDescription = filter.name, + contentScale = ContentScale.Crop, + modifier = Modifier + .clip(RoundedCornerShape(corner = CornerSize(8.dp))) + .size(70.dp), + ) + Spacer(modifier = modifier.width(10.dp)) + Text( + modifier = modifier.weight(1f), + text = filter.name, + style = MaterialTheme.typography.h3, + ) + Box( + modifier = modifier + .height(24.dp) + .widthIn(min = 48.dp) + .padding(end = 8.dp) + .clip(shape = RoundedCornerShape(size = 16.dp)) + .background(MaterialTheme.colors.primary), contentAlignment = Alignment.Center + ) { + Text( + modifier = modifier.padding(horizontal = 2.dp), + text = filter.cocktailIds.size.toString(), + style = MaterialTheme.typography.h3, + color = Color.White, + ) + } + } + } +} diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/ui/search/FilterSearchScreenViewModel.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/ui/search/FilterSearchScreenViewModel.kt new file mode 100644 index 0000000..295853a --- /dev/null +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/filter/ui/search/FilterSearchScreenViewModel.kt @@ -0,0 +1,61 @@ +package org.mixdrinks.mixdrinks.features.start.filter.ui.search + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.mixdrinks.mixdrinks.features.data.FilterGroupFull +import org.mixdrinks.mixdrinks.features.data.SelectedFilter +import org.mixdrinks.mixdrinks.features.start.filter.FilterRepository +import org.mixdrinks.mixdrinks.features.start.filter.SelectedFilterStorage + +class FilterSearchScreenViewModel( + private val groupId: Int, + private val filterRepository: FilterRepository, + private val filterStorage: SelectedFilterStorage +) : ViewModel() { + private val _uiState = MutableStateFlow( + FilterUiState.Loading + ) + val uiState: StateFlow = _uiState + private var searchText = "" + + init { + viewModelScope.launch { + filterStorage.selectedFilters.collect { + updateFilters() + } + } + } + + private fun updateFilters() { + viewModelScope.launch { + _uiState.update { + FilterUiState.Loaded(FilterSearchItemUiState(filterRepository.getFiltersByGroupId(groupId,searchText))) + } + } + } + + fun searchAction(search: String) { + searchText = search + updateFilters() + } + + + fun checkedAction(selectedFilter: SelectedFilter) { + filterStorage.add(selectedFilter) + } + + sealed class FilterUiState { + object Loading : FilterUiState() + class Loaded(val itemState: FilterSearchItemUiState) : FilterUiState() + class Error(val message: String) : FilterUiState() + } +} + +data class FilterSearchItemUiState( + val filters: List +) + diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/main/ui/StartScreen.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/main/ui/StartScreen.kt new file mode 100644 index 0000000..554bcb8 --- /dev/null +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/main/ui/StartScreen.kt @@ -0,0 +1,149 @@ +package org.mixdrinks.mixdrinks.features.start.main.ui + +import android.util.Log +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import org.koin.androidx.compose.koinViewModel +import org.mixdrinks.domain.ImageUrlCreators +import org.mixdrinks.dto.CocktailId +import org.mixdrinks.mixdrinks.features.common.ui.ErrorLoadingScreen +import org.mixdrinks.mixdrinks.features.common.ui.LoaderIndicatorScreen +import org.mixdrinks.mixdrinks.features.common.ui.NotFoundScreen +import org.mixdrinks.mixdrinks.features.data.CocktailShort +import org.mixdrinks.mixdrinks.features.start.main.ui.header.Header + +@Composable +fun StartScreen( + modifier: Modifier, + onNavigateToDetail: (id: Int) -> Unit, + onNavigateToFilter: () -> Unit, + onNavigateToStart: () -> Unit, + viewModel: StartScreenViewModel = koinViewModel() +) { + val cocktail by viewModel.uiState.collectAsState() + when (cocktail) { + is StartScreenViewModel.StartUiState.Loaded -> { + val data = (cocktail as StartScreenViewModel.StartUiState.Loaded).itemState + StartScreenData( + modifier = modifier, + data = data, + onNavigateToDetail = onNavigateToDetail, + onNavigateToFilter = onNavigateToFilter, + onNavigateToStart = onNavigateToStart, + viewModel = viewModel + ) + } + + is StartScreenViewModel.StartUiState.Loading -> { + LoaderIndicatorScreen(modifier = modifier) + } + + is StartScreenViewModel.StartUiState.Error -> { + val error = cocktail as StartScreenViewModel.StartUiState.Error + Log.d("Exception", error.message) + ErrorLoadingScreen(modifier = modifier) + } + } +} +@Suppress("LongParameterList") +@Composable +fun StartScreenData( + modifier: Modifier, + data: StartItemUiState, + onNavigateToDetail: (id: Int) -> Unit, + onNavigateToFilter: () -> Unit, + onNavigateToStart: () -> Unit, + viewModel: StartScreenViewModel, +) { + Column { + Header( + modifier = modifier, + onNavigateToFilter = onNavigateToFilter, + viewModel = viewModel, + searchText = data.searchText + ) + if (data.cocktails.isEmpty()) NotFoundScreen( + modifier = modifier, + onNavigateToStart = onNavigateToStart + ) + else { + LazyColumn( + contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + items( + items = data.cocktails, + itemContent = { + CocktailListItem( + item = it, + modifier = modifier, + onClickAction = onNavigateToDetail + ) + } + ) + } + } + } +} + +@Composable +fun CocktailListItem(modifier: Modifier, item: CocktailShort, onClickAction: (id: Int) -> Unit) { + Card( + modifier = modifier + .clickable { onClickAction(item.cocktailId) } + ) { + Row( + modifier = modifier.padding(10.dp), + ) { + AsyncImage( + model = ImageUrlCreators.createUrl( + CocktailId(item.cocktailId), ImageUrlCreators.Size.SIZE_320 + ), + contentDescription = item.name, + contentScale = ContentScale.Crop, + modifier = Modifier + .clip(RoundedCornerShape(corner = CornerSize(16.dp))) + .size(100.dp), + ) + Column( + modifier = modifier + .fillMaxWidth(1f), verticalArrangement = Arrangement.SpaceBetween + ) { + Row( + modifier = modifier + .padding(start = 10.dp) + ) { + Text( + text = item.name, + fontWeight = FontWeight.W700, + fontSize = 18.sp, + color = MaterialTheme.colors.primary, + ) + } + } + } + } +} diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/ui/StartScreenViewModel.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/main/ui/StartScreenViewModel.kt similarity index 56% rename from app/src/main/java/org/mixdrinks/mixdrinks/features/start/ui/StartScreenViewModel.kt rename to app/src/main/java/org/mixdrinks/mixdrinks/features/start/main/ui/StartScreenViewModel.kt index 82b5023..183738b 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/ui/StartScreenViewModel.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/main/ui/StartScreenViewModel.kt @@ -1,34 +1,36 @@ -package org.mixdrinks.mixdrinks.features.start.ui +package org.mixdrinks.mixdrinks.features.start.main.ui -import android.content.res.Resources -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import org.mixdrinks.mixdrinks.database.AppDatabase -import org.mixdrinks.mixdrinks.database.dao.CocktailShort +import org.mixdrinks.mixdrinks.features.data.CocktailShort +import org.mixdrinks.mixdrinks.features.fetcher.Fetcher +import org.mixdrinks.mixdrinks.features.start.StartRepository import java.io.IOException @Suppress("TooGenericExceptionCaught", "MagicNumber") class StartScreenViewModel( - private val roomDatabase: AppDatabase + private val startRepository: StartRepository, + private val fetcher: Fetcher ) : ViewModel() { private val _uiState = MutableStateFlow(StartUiState.Loading) val uiState: StateFlow = _uiState init { - getCocktail() + getCocktails() } - private fun getCocktail() { + private fun getCocktails() { _uiState.value = StartUiState.Loading viewModelScope.launch { try { - val result = roomDatabase.cocktailDao().getAllShortCocktail() - _uiState.value = StartUiState.Loaded(StartItemUiState(result)) + fetcher.startFetch() + _uiState.value = + StartUiState.Loaded(StartItemUiState("", startRepository.getCocktails())) } catch (error: IOException) { _uiState.value = StartUiState.Error(error.toString()) } catch (error: Exception) { @@ -36,6 +38,14 @@ class StartScreenViewModel( } } } + + fun searchAction(search: String) { + val result = startRepository.searchCocktail(search) + _uiState.update { + StartUiState.Loaded(StartItemUiState(search, result)) + } + } + sealed class StartUiState { object Loading : StartUiState() class Loaded(val itemState: StartItemUiState) : StartUiState() @@ -44,7 +54,7 @@ class StartScreenViewModel( } data class StartItemUiState( - var cocktails: List + val searchText: String, var cocktails: List ) diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/header/ui/HeaderScreen.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/main/ui/header/Header.kt similarity index 72% rename from app/src/main/java/org/mixdrinks/mixdrinks/features/header/ui/HeaderScreen.kt rename to app/src/main/java/org/mixdrinks/mixdrinks/features/start/main/ui/header/Header.kt index 64a2477..9a9a62d 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/header/ui/HeaderScreen.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/main/ui/header/Header.kt @@ -1,6 +1,5 @@ -package org.mixdrinks.mixdrinks.features.header.ui +package org.mixdrinks.mixdrinks.features.start.main.ui.header -import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -21,9 +20,15 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import org.mixdrinks.mixdrinks.R +import org.mixdrinks.mixdrinks.features.start.main.ui.StartScreenViewModel @Composable -fun HeaderScreen(modifier: Modifier, onNavigateToFilter: () -> Unit) { +fun Header( + modifier: Modifier, + onNavigateToFilter: () -> Unit, + viewModel: StartScreenViewModel, + searchText: String +) { var isFocusedSearchTextField by remember { mutableStateOf(false) } Column { @@ -35,19 +40,20 @@ fun HeaderScreen(modifier: Modifier, onNavigateToFilter: () -> Unit) { .padding(start = 8.dp, end = 8.dp), verticalAlignment = Alignment.CenterVertically ) { - SearchTextField(modifier = modifier) { isFocusedSearchTextField = it } + SearchTextField( + modifier = modifier, + onFocusChanged = { isFocusedSearchTextField = it }, + searchAction = { viewModel.searchAction(it) }, + searchText = searchText + ) FilterAction(modifier = modifier, onNavigateToFilter = onNavigateToFilter) } - if(isFocusedSearchTextField) - SearchHintContent(modifier = modifier, onClickAction = { Log.d("MyLog", it) }) } } + @Composable private fun FilterAction(modifier: Modifier, onNavigateToFilter: () -> Unit) { - IconButton( - modifier = modifier.height(24.dp), - onClick = { onNavigateToFilter() } - ) { + IconButton(modifier = modifier.height(24.dp), onClick = { onNavigateToFilter() }) { Icon( painter = painterResource(id = R.drawable.baseline_tune_24), tint = Color.White, diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/header/ui/SearchTextField.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/main/ui/header/SearchTextField.kt similarity index 63% rename from app/src/main/java/org/mixdrinks/mixdrinks/features/header/ui/SearchTextField.kt rename to app/src/main/java/org/mixdrinks/mixdrinks/features/start/main/ui/header/SearchTextField.kt index 93ab998..366e7d0 100644 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/header/ui/SearchTextField.kt +++ b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/main/ui/header/SearchTextField.kt @@ -1,4 +1,4 @@ -package org.mixdrinks.mixdrinks.features.header.ui +package org.mixdrinks.mixdrinks.features.start.main.ui.header import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -6,14 +6,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.Icon -import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.material.TextFieldDefaults -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Close import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -28,16 +24,23 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import org.mixdrinks.mixdrinks.R +import org.mixdrinks.mixdrinks.features.common.ui.widgets.IconTextFieldIcon @Suppress("MagicNumber") @OptIn(ExperimentalComposeUiApi::class) @Composable -fun SearchTextField(modifier: Modifier, onFocusChanged: (isFocused: Boolean) -> Unit) { - var textState by remember { mutableStateOf("") } +fun SearchTextField( + modifier: Modifier, + onFocusChanged: (isFocused: Boolean) -> Unit, + searchAction: (searchText: String) -> Unit, + searchText: String +) { + var textState by remember { mutableStateOf(searchText) } + var isFocused by remember { mutableStateOf(false) } val keyboardController = LocalSoftwareKeyboardController.current - TextField ( + TextField( modifier = modifier .height(50.dp) .fillMaxWidth(if (isFocused) 1.0f else 0.9f) @@ -54,39 +57,25 @@ fun SearchTextField(modifier: Modifier, onFocusChanged: (isFocused: Boolean) -> value = textState, placeholder = { Text(text = stringResource(R.string.search_hint)) }, shape = RoundedCornerShape(10.dp), - onValueChange = { textState = it }, + onValueChange = { + textState = it + searchAction(textState) + }, trailingIcon = { - IconTextFieldIcon( - text = textState, - onClick = { textState = "" } - ) + IconTextFieldIcon(text = textState, onClick = { + textState = "" + searchAction(textState) + }) }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions( - onDone = { - keyboardController?.hide() - onFocusChanged(false) - isFocused = false - // send data to server - } - ), + keyboardActions = KeyboardActions(onDone = { + keyboardController?.hide() + onFocusChanged(false) + isFocused = false + }), ) } -@Composable -private fun IconTextFieldIcon(text: String, onClick: () -> Unit) { - if (text.isNotEmpty()) { - IconButton( - onClick = { onClick() } - ) { - Icon( - imageVector = Icons.Outlined.Close, - tint = MaterialTheme.colors.secondary, - contentDescription = null - ) - } - } -} diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/ui/CocktailListItem.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/ui/CocktailListItem.kt deleted file mode 100644 index 80438f9..0000000 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/ui/CocktailListItem.kt +++ /dev/null @@ -1,79 +0,0 @@ -package org.mixdrinks.mixdrinks.features.start.ui - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CornerSize -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Card -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import coil.compose.AsyncImage -import org.mixdrinks.domain.ImageUrlCreators -import org.mixdrinks.dto.CocktailId -import org.mixdrinks.mixdrinks.database.dao.CocktailShort - -@Composable -fun CocktailListItem(modifier: Modifier, item: CocktailShort, onClickAction: (id: Int) -> Unit) { - Card( - modifier = modifier - .clickable { onClickAction(item.cocktailId) } - ) { - Row( - modifier = modifier.padding(10.dp), - ) { - ListItemImage(item.cocktailId) - ListItemInfo(modifier = modifier, item = item) - } - } -} - -@Composable -private fun ListItemImage(id: Int) { - AsyncImage( - model = ImageUrlCreators.createUrl( - CocktailId(id), - ImageUrlCreators.Size.SIZE_320 - ), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .clip(RoundedCornerShape(corner = CornerSize(16.dp))) - .size(200.dp), - ) -} - -@Composable -private fun ListItemInfo(modifier: Modifier, item: CocktailShort) { - Column( - modifier = modifier - .fillMaxWidth(1f) - .height(200.dp), - verticalArrangement = Arrangement.SpaceBetween - ) { - Row( - modifier = modifier - .padding(start = 10.dp) - ) { - Text( - text = item.name, - fontWeight = FontWeight.W700, - fontSize = 18.sp, - color = MaterialTheme.colors.primary, - ) - } - } -} - diff --git a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/ui/StartScreen.kt b/app/src/main/java/org/mixdrinks/mixdrinks/features/start/ui/StartScreen.kt deleted file mode 100644 index 94aa641..0000000 --- a/app/src/main/java/org/mixdrinks/mixdrinks/features/start/ui/StartScreen.kt +++ /dev/null @@ -1,72 +0,0 @@ -package org.mixdrinks.mixdrinks.features.start.ui - -import android.util.Log -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import org.koin.androidx.compose.koinViewModel -import org.mixdrinks.mixdrinks.database.dao.CocktailShort -import org.mixdrinks.mixdrinks.features.common.ui.ErrorLoadingScreen -import org.mixdrinks.mixdrinks.features.common.ui.LoaderIndicatorScreen -import org.mixdrinks.mixdrinks.features.header.ui.HeaderScreen - -@Composable -fun StartScreen( - modifier: Modifier, - onNavigateToDetail: (id: Int) -> Unit, - onNavigateToFilter: () -> Unit, - viewModel: StartScreenViewModel = koinViewModel() -) { - val cocktail by viewModel.uiState.collectAsState() - when(cocktail) { - is StartScreenViewModel.StartUiState.Loaded -> { - val data = (cocktail as StartScreenViewModel.StartUiState.Loaded).itemState - StartScreenData( - modifier = modifier, - cocktails = data.cocktails, - onNavigateToDetail = onNavigateToDetail, - onNavigateToFilter = onNavigateToFilter, - ) - } - is StartScreenViewModel.StartUiState.Loading -> { - LoaderIndicatorScreen(modifier = modifier) - } - is StartScreenViewModel.StartUiState.Error -> { - val error = cocktail as StartScreenViewModel.StartUiState.Error - Log.d("Exception", error.message) - ErrorLoadingScreen(modifier = modifier) - } - } -} -@Suppress("UnusedPrivateMember") -@Composable -fun StartScreenData( - modifier: Modifier, - cocktails: List, - onNavigateToDetail: (id: Int) -> Unit, - onNavigateToFilter: () -> Unit, -) { - Column { - HeaderScreen(modifier = modifier, onNavigateToFilter = onNavigateToFilter) - LazyColumn( - contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - items( - items = cocktails, - itemContent = { - CocktailListItem(item = it, modifier = modifier, onClickAction = onNavigateToDetail ) - } - ) - } - } - -} - diff --git a/app/src/main/res/drawable/baseline_arrow_back_ios_24.xml b/app/src/main/res/drawable/baseline_arrow_back_ios_24.xml index 117a141..6c3197a 100644 --- a/app/src/main/res/drawable/baseline_arrow_back_ios_24.xml +++ b/app/src/main/res/drawable/baseline_arrow_back_ios_24.xml @@ -1,5 +1,5 @@ diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 11cded6..4ffbd08 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1,9 +1,9 @@ Помилка підключення - Рецепт Коктейлю - Склад коктейлю - Потрібні штучки для пригоування + Рецепт + Інградієнти + Інструменти Перегляди Пошук Помилка завантаження. Перевірте підключення до Інтернету @@ -13,4 +13,6 @@ Фільтри Опис Застосувати + Додати + До фільтра \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 461b233..99dc90b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,11 +2,9 @@ MixDrinks Failure connecting - MainActivity - - Cocktail recipe - Ingredients of the cocktail - Need tools for cooking cocktail + Recipe + Ingredients + Tools Visit count Search Error loading. Please check internet connection @@ -16,6 +14,8 @@ Filters Description Застосувати + Add + to filter