Skip to content

Commit

Permalink
[Android] feat: 레시피 추천 목록 프로토타입 구현(#13) (#14)
Browse files Browse the repository at this point in the history
* Feat: Android Version Catalog 적용

* Feat: Hilt Application 설정

* Feat: Glide 의존성 추가

* Feat: Recipe ViewModel 임시 정의

* Feat: 레시피 추천 프로토 타입 구현

* Feat: Glide 를 사용해 NetworkImage 를 구현한다

* Refactor: Composable 분리 및 패키지 정리

* feat: ktlintCheck dependency true
  • Loading branch information
SeongHoonC committed May 28, 2024
1 parent 5787085 commit 2864336
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 56 deletions.
17 changes: 17 additions & 0 deletions Android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.org.jetbrains.kotlin.kapt)
alias(libs.plugins.hilt)
}

android {
Expand Down Expand Up @@ -59,11 +61,26 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.lifecycle.runtime.compose.android)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)

implementation(libs.dagger.hilt.android)
kapt(libs.dagger.hilt.compiler)
implementation(libs.lifecycle.viewmodel.ktx)

// navigation
implementation(libs.navigation.compose)
implementation(libs.hilt.navigation.compose)

// status bar
implementation(libs.accompanist.systemuicontroller)

// glide
implementation(libs.glide.compose)
}
7 changes: 5 additions & 2 deletions Android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".BanchangoApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -11,9 +14,9 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Banchango"
tools:targetApi="31">
tools:targetApi="34">
<activity
android:name=".MainActivity"
android:name=".presentation.reciperecommend.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Banchango">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.sundaegukbap.banchango

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class BanchangoApplication : Application()

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.sundaegukbap.banchango.core.designsystem

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.layout.ContentScale
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
import com.bumptech.glide.integration.compose.GlideImage
import com.bumptech.glide.integration.compose.placeholder

@OptIn(ExperimentalGlideComposeApi::class)
@Composable
fun NetworkImage(modifier: Modifier, url: String) {
GlideImage(
model = url,
contentScale = ContentScale.Crop,
contentDescription = null,
modifier = modifier.fillMaxSize(),
loading = placeholder(ColorPainter(Color(0xD9FFFFFF))),
failure = placeholder(ColorPainter(Color(0xD9FFFFFF))),
)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.sundaegukbap.banchango.model

data class Recipe(
val id: Long,
val name: String,
val introduction: String,
val image: String,
val link: String,
val cookingTime: Int,
val servings: Int,
val difficulty: String,
val have: List<Int>,
val need: List<Int>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.sundaegukbap.banchango.presentation.reciperecommend

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.ui.graphics.Color
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.sundaegukbap.banchango.ui.theme.BanchangoTheme
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

private val viewModel: RecipeRecommendViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
viewModel.getRecipeRecommendation()
setContent {
BanchangoTheme {
val systemUiController = rememberSystemUiController()
systemUiController.setSystemBarsColor(color = Color(0xBFFFFFFF), darkIcons = true)
systemUiController.setNavigationBarColor(
color = Color(0xFFFFFFFF)
)
RecipesRecommendScreen()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.sundaegukbap.banchango.presentation.reciperecommend

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.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.text.TextStyle
import androidx.compose.ui.text.font.FontWeight.Companion.Bold
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.sundaegukbap.banchango.core.designsystem.NetworkImage
import com.sundaegukbap.banchango.model.Recipe
import com.sundaegukbap.banchango.ui.theme.BanchangoTheme

@Composable
fun RecipeCard(
page: Int,
recipe: Recipe,
onHateClick: (page: Int) -> Unit = {},
onLikeClick: (page: Int) -> Unit = {},
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black),
contentAlignment = Alignment.Center,
) {
NetworkImage(
modifier = Modifier
.fillMaxSize(),
url = recipe.image,
)
RecipeInfo(recipe, page, onHateClick, onLikeClick)
}
}

@Composable
private fun RecipeInfo(
recipe: Recipe,
page: Int,
onHateClick: (page: Int) -> Unit,
onLikeClick: (page: Int) -> Unit
) {
Box {
Column {
Text(
recipe.name,
color = Color.White,
fontSize = 24.sp,
style = TextStyle(fontWeight = Bold),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
)
Text(
text = page.toString(),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
color = Color.White,
fontSize = 60.sp
)
Row(
modifier = Modifier
.align(Alignment.CenterHorizontally)
) {
Button(
modifier = Modifier.padding(end = 16.dp),
onClick = {
onHateClick(page + 1)
}
) {
Text("싫어요")
}
Button(
onClick = {
onLikeClick(page + 1)
},
) {
Text("좋아요")
}
}
}
}
}


@Preview(showBackground = true)
@Composable
fun RecipeCardPreview() {
BanchangoTheme {
RecipeCard(
page = 1, recipe = Recipe(
id = 1,
name = "간장계란볶음밥",
introduction = "아주 간단하면서 맛있는 계란간장볶음밥으로 한끼식사 만들어보세요. 아이들이 더 좋아할거예요.",
image = "https://recipe1.ezmember.co.kr/cache/recipe/2018/05/26/d0c6701bc673ac5c18183b47212a58571.jpg",
link = "https://www.10000recipe.com/recipe/6889616",
cookingTime = 10,
servings = 2,
difficulty = "Easy",
have = listOf(1, 2, 3, 4, 5),
need = listOf(6, 7, 8, 9, 10),
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.sundaegukbap.banchango.presentation.reciperecommend

import androidx.lifecycle.ViewModel
import com.sundaegukbap.banchango.model.Recipe
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject

@HiltViewModel
class RecipeRecommendViewModel @Inject constructor() : ViewModel() {

private val _recipes: MutableStateFlow<List<Recipe>> = MutableStateFlow(emptyList())
val recipes: StateFlow<List<Recipe>> = _recipes.asStateFlow()

fun getRecipeRecommendation() {
_recipes.value = List(10) {
Recipe(
id = (it + 1).toLong(),
name = "간장계란볶음밥",
introduction = "아주 간단하면서 맛있는 계란간장볶음밥으로 한끼식사 만들어보세요. 아이들이 더 좋아할거예요.",
image = "https://recipe1.ezmember.co.kr/cache/recipe/2018/05/26/d0c6701bc673ac5c18183b47212a58571.jpg",
link = "https://www.10000recipe.com/recipe/6889616",
cookingTime = 10,
servings = 2,
difficulty = "Easy",
have = listOf(1, 2, 3, 4, 5),
need = listOf(6, 7, 8, 9, 10)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.sundaegukbap.banchango.presentation.reciperecommend

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.launch

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun RecipesRecommendScreen(
modifier: Modifier = Modifier,
viewModel: RecipeRecommendViewModel = hiltViewModel(),
) {
val recipesUiState by viewModel.recipes.collectAsStateWithLifecycle()

val pagerState = rememberPagerState(
pageCount = {
recipesUiState.size
}
)
val coroutineScope = rememberCoroutineScope()
VerticalPager(
modifier = modifier,
state = pagerState,
contentPadding = PaddingValues(vertical = 200.dp, horizontal = 40.dp),
pageSpacing = 40.dp,
) { page ->
RecipeCard(
page = page,
recipe = recipesUiState[page],
onLikeClick = {
coroutineScope.launch { pagerState.animateScrollToPage(it) }
},
onHateClick = {
coroutineScope.launch { pagerState.animateScrollToPage(it) }
},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.sundaegukbap.banchango.repository

import com.sundaegukbap.banchango.model.Recipe

interface RecipeRepository {
fun getRecipeRecommendation(): List<Recipe>
}
Loading

0 comments on commit 2864336

Please sign in to comment.