diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 70f3cff9..3d4da186 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -25,7 +25,7 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - buildConfigField("String", "BASE_URL", properties["BASE_URL"].toString()) + buildConfigField("String", "BASE_URL", "\"${properties["BASE_URL"]}\"") manifestPlaceholders["KAKAO_APP_KEY"] = properties["KAKAO_APP_KEY"].toString() buildConfigField("String", "KAKAO_APP_KEY", properties["KAKAO_APP_KEY"].toString()) @@ -96,6 +96,11 @@ dependencies { // Kakao SDK implementation("com.kakao.sdk:v2-all:2.20.6") implementation("com.kakao.sdk:v2-user:2.20.6") // 카카오 로그인 API 모듈 + + // image + implementation("io.coil-kt.coil3:coil-compose:3.1.0") + implementation("io.coil-kt.coil3:coil-network-okhttp:3.1.0") + implementation("io.coil-kt.coil3:coil-svg:3.1.0") } // Hilt를 사용할 때 필요한 Annotation Processor diff --git a/app/src/main/java/com/kuit/ourmenu/data/di/ServiceModule.kt b/app/src/main/java/com/kuit/ourmenu/data/di/ServiceModule.kt index a5dc9f44..eba4aef0 100644 --- a/app/src/main/java/com/kuit/ourmenu/data/di/ServiceModule.kt +++ b/app/src/main/java/com/kuit/ourmenu/data/di/ServiceModule.kt @@ -2,6 +2,7 @@ package com.kuit.ourmenu.data.di import com.kuit.ourmenu.data.service.AuthService import com.kuit.ourmenu.data.service.DummyService +import com.kuit.ourmenu.data.service.MenuFolderService import com.kuit.ourmenu.data.service.UserService import dagger.Module import dagger.Provides @@ -28,4 +29,10 @@ object ServiceModule { @Singleton fun providesUserService(retrofit: Retrofit): UserService = retrofit.create(UserService::class.java) + + @Provides + @Singleton + fun provideMenuFolderService(retrofit: Retrofit): MenuFolderService = + retrofit.create(MenuFolderService::class.java) + } \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/data/model/menuFolder/response/MenuFolderIconType.kt b/app/src/main/java/com/kuit/ourmenu/data/model/menuFolder/response/MenuFolderIconType.kt new file mode 100644 index 00000000..1cc975e1 --- /dev/null +++ b/app/src/main/java/com/kuit/ourmenu/data/model/menuFolder/response/MenuFolderIconType.kt @@ -0,0 +1,36 @@ +package com.kuit.ourmenu.data.model.menuFolder.response + +enum class MenuFolderIconType { + ANGRY, + BAEKSUK, + BASKET, + BREAD, + CLOUD, + COFFEE, + CONGRATS, + COUPLE, + CRY, + DICE, + DOUGHNUT, + FIRE, + FISH, + FISH_BREAD, + HAMBURGER, + HEART, + ICE_CREAM, + JJAMBBONG, + LEAF, + MAN, + MEAT, + NOODLE, + PEOPLE, + RAMEN, + RICE, + SMILE, + SNOWMAN, + SPOON_AND_CHOPSTICK, + SUN, + SUNNY, + SUSHI, + TABLE, +} \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/data/model/menuFolder/response/MenuFolderResponse.kt b/app/src/main/java/com/kuit/ourmenu/data/model/menuFolder/response/MenuFolderResponse.kt new file mode 100644 index 00000000..77e23550 --- /dev/null +++ b/app/src/main/java/com/kuit/ourmenu/data/model/menuFolder/response/MenuFolderResponse.kt @@ -0,0 +1,13 @@ +package com.kuit.ourmenu.data.model.menuFolder.response + +import kotlinx.serialization.Serializable + +@Serializable +data class MenuFolderResponse( + val menuFolderId: Int, + val menuFolderTitle: String, + val menuFolderUrl: String, + val menuFolderIcon: String, + val menuIds: List, + val index: Int, +) \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/data/repository/MenuFolderRepository.kt b/app/src/main/java/com/kuit/ourmenu/data/repository/MenuFolderRepository.kt new file mode 100644 index 00000000..79b2464d --- /dev/null +++ b/app/src/main/java/com/kuit/ourmenu/data/repository/MenuFolderRepository.kt @@ -0,0 +1,15 @@ +package com.kuit.ourmenu.data.repository + +import com.kuit.ourmenu.data.model.base.handleBaseResponse +import com.kuit.ourmenu.data.service.MenuFolderService +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class MenuFolderRepository @Inject constructor( + private val menuFolderService: MenuFolderService, +) { + suspend fun getMenuFolders() = runCatching { + menuFolderService.getMenuFolders().handleBaseResponse().getOrThrow() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/data/service/MenuFolderService.kt b/app/src/main/java/com/kuit/ourmenu/data/service/MenuFolderService.kt new file mode 100644 index 00000000..9c1274a7 --- /dev/null +++ b/app/src/main/java/com/kuit/ourmenu/data/service/MenuFolderService.kt @@ -0,0 +1,10 @@ +package com.kuit.ourmenu.data.service + +import com.kuit.ourmenu.data.model.base.BaseResponse +import com.kuit.ourmenu.data.model.menuFolder.response.MenuFolderResponse +import retrofit2.http.GET + +interface MenuFolderService { + @GET("api/menu-folders") + suspend fun getMenuFolders(): BaseResponse> +} \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/addmenu/component/bottomsheet/TagSelectBottomSheet.kt b/app/src/main/java/com/kuit/ourmenu/ui/addmenu/component/bottomsheet/TagSelectBottomSheet.kt index 35d1f19e..e9aadedc 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/addmenu/component/bottomsheet/TagSelectBottomSheet.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/addmenu/component/bottomsheet/TagSelectBottomSheet.kt @@ -83,7 +83,7 @@ fun TagSelectBottomSheet( //종류 TagChipGroup( - groupLabel = "종류", + groupLabel = stringResource(R.string.type), tags = categoryTagList, selectedTags = selectedTagList, ) { tag -> @@ -97,7 +97,7 @@ fun TagSelectBottomSheet( } //나라 별 음식 TagChipGroup( - groupLabel = "나라 별 음식", + groupLabel = stringResource(R.string.nationality), tags = nationalityTagList, selectedTags = selectedTagList, ) { tag -> @@ -111,7 +111,7 @@ fun TagSelectBottomSheet( } //맛 TagChipGroup( - groupLabel = "맛", + groupLabel = stringResource(R.string.taste), tags = tasteTagList, selectedTags = selectedTagList, ) { tag -> @@ -125,7 +125,7 @@ fun TagSelectBottomSheet( } //상황 TagChipGroup( - groupLabel = "상황", + groupLabel = stringResource(R.string.occasion), tags = occasionTagList, selectedTags = selectedTagList, ) { tag -> @@ -173,7 +173,6 @@ fun TagSelectBottomSheet( .padding(bottom = 60.dp), hostState = snackbarHostState, isChecked = false, - message = stringResource(R.string.tag_number_warning) ) } } diff --git a/app/src/main/java/com/kuit/ourmenu/ui/addmenu/screen/AddMenuScreen.kt b/app/src/main/java/com/kuit/ourmenu/ui/addmenu/screen/AddMenuScreen.kt index 0530f95e..44be08e7 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/addmenu/screen/AddMenuScreen.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/addmenu/screen/AddMenuScreen.kt @@ -34,6 +34,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import com.kuit.ourmenu.R import com.kuit.ourmenu.ui.addmenu.component.AddMenuSearchBackground import com.kuit.ourmenu.ui.addmenu.component.bottomsheet.AddMenuBottomSheetContent @@ -46,7 +48,7 @@ import com.kuit.ourmenu.ui.theme.ourMenuTypography @OptIn(ExperimentalMaterial3Api::class) @Composable -fun AddMenuScreen(modifier: Modifier = Modifier) { +fun AddMenuScreen(navController: NavController) { var scaffoldState = rememberBottomSheetScaffoldState() var showBottomSheet by rememberSaveable { mutableStateOf(false) } var showSearchBackground by rememberSaveable { mutableStateOf(false) } @@ -162,5 +164,7 @@ fun AddMenuScreen(modifier: Modifier = Modifier) { @Preview(showBackground = true) @Composable private fun AddMenuScreenPreview() { - AddMenuScreen() + val navController = rememberNavController() + + AddMenuScreen(navController) } \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/common/chip/TagChipGroup.kt b/app/src/main/java/com/kuit/ourmenu/ui/common/chip/TagChipGroup.kt index 92c56fff..b3b06c1b 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/common/chip/TagChipGroup.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/common/chip/TagChipGroup.kt @@ -15,9 +15,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.kuit.ourmenu.R +import com.kuit.ourmenu.ui.theme.Neutral900 import com.kuit.ourmenu.ui.theme.ourMenuTypography @OptIn(ExperimentalLayoutApi::class) @@ -32,7 +34,8 @@ fun TagChipGroup( Column(modifier = modifier.fillMaxWidth()){ Text( text = groupLabel, - style = ourMenuTypography().pretendard_500_14, + style = ourMenuTypography().pretendard_400_14, + color = Neutral900 ) Spacer(modifier = modifier.height(8.dp)) FlowRow( @@ -73,7 +76,7 @@ private fun TagChipGroupPreview() { var selectedTags by rememberSaveable { mutableStateOf(listOf()) } TagChipGroup( - groupLabel = "종류", + groupLabel = stringResource(R.string.type), tags = tags, selectedTags = selectedTags, ){ tag -> diff --git a/app/src/main/java/com/kuit/ourmenu/ui/common/topappbar/BackButtonTopAppBar.kt b/app/src/main/java/com/kuit/ourmenu/ui/common/topappbar/BackButtonTopAppBar.kt index 62865e0f..f75ee916 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/common/topappbar/BackButtonTopAppBar.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/common/topappbar/BackButtonTopAppBar.kt @@ -17,10 +17,10 @@ import com.kuit.ourmenu.ui.theme.Neutral500 @OptIn(ExperimentalMaterial3Api::class) @Composable -fun BackButtonTopAppBar(color: Color, isKebabVisible: Boolean) { +fun BackButtonTopAppBar(color: Color, isKebabVisible: Boolean, onBackClick: () -> Unit = {}) { TopAppBar( title = { - IconButton(onClick = { TODO("뒤로가기 구현") }) { + IconButton(onClick = onBackClick) { Icon( painter = painterResource(R.drawable.ic_back), contentDescription = "Back", @@ -34,7 +34,7 @@ fun BackButtonTopAppBar(color: Color, isKebabVisible: Boolean) { painter = painterResource(R.drawable.ic_kebab), modifier = Modifier.padding(end = 18.dp), contentDescription = "Menu", - tint = color + tint = color, ) } }, diff --git a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/FilterBottomSheet.kt b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/FilterBottomSheet.kt new file mode 100644 index 00000000..d357c9e2 --- /dev/null +++ b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/FilterBottomSheet.kt @@ -0,0 +1,358 @@ +package com.kuit.ourmenu.ui.menuFolder.component + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +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.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.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.RangeSlider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +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.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.kuit.ourmenu.R +import com.kuit.ourmenu.ui.common.BottomHalfWidthButton +import com.kuit.ourmenu.ui.common.OurSnackbarHost +import com.kuit.ourmenu.ui.common.chip.TagChipGroup +import com.kuit.ourmenu.ui.theme.Neutral300 +import com.kuit.ourmenu.ui.theme.Neutral400 +import com.kuit.ourmenu.ui.theme.Neutral500 +import com.kuit.ourmenu.ui.theme.Neutral900 +import com.kuit.ourmenu.ui.theme.NeutralWhite +import com.kuit.ourmenu.ui.theme.Primary500Main +import com.kuit.ourmenu.ui.theme.ourMenuTypography +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FilterBottomSheet( + modifier: Modifier = Modifier, + categoryTagList: List>, + nationalityTagList: List>, + tasteTagList: List>, + occasionTagList: List>, + onSelectedTagsChange: (List) -> Unit, + onApplyButtonClick: () -> Unit, +) { + // toast를 위한 context + val context = LocalContext.current + val snackbarHostState = remember { SnackbarHostState() } + val scope = rememberCoroutineScope() + + // 가격 범위 설정 + val minPrice = 0f + val maxPrice = 50_000f + var priceRange by remember { mutableStateOf(minPrice..maxPrice) } + val stepSize = 5000f // 5000원 단위 조정 + + // 각 항목별로 선택된 태그 상태를 개별적으로 관리 + var selectedCategoryTag by rememberSaveable { mutableStateOf(null) } + var selectedNationalityTag by rememberSaveable { mutableStateOf(null) } + var selectedTasteTag by rememberSaveable { mutableStateOf(null) } + var selectedOccasionTag by rememberSaveable { mutableStateOf(null) } + + fun updateSelectedTags() { + val selectedTags = listOfNotNull( + selectedCategoryTag, + selectedNationalityTag, + selectedTasteTag, + selectedOccasionTag + ) + onSelectedTagsChange(selectedTags) + } + + Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween + ) { + Column( + modifier = Modifier + .fillMaxWidth() + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(R.string.filtering), + style = ourMenuTypography().pretendard_700_16 + ) + Text( + text = stringResource(R.string.select_one), + style = ourMenuTypography().pretendard_600_14, + color = Neutral500 + ) + } + + Spacer(modifier = Modifier.height(20.dp)) + + LazyColumn( + modifier = Modifier + .padding(horizontal = 20.dp), + ) { + // 종류 + item { + TagChipGroup( + groupLabel = stringResource(R.string.type), + tags = categoryTagList, + selectedTags = listOfNotNull(selectedCategoryTag), + ) { tag -> + if (selectedCategoryTag != null && selectedCategoryTag != tag) { + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.tag_max_one)) + } + } else { + selectedCategoryTag = if (selectedCategoryTag == tag) null else tag + updateSelectedTags() + } + } + } + + // 나라 별 음식 + item { + TagChipGroup( + groupLabel = stringResource(R.string.nationality), + tags = nationalityTagList, + selectedTags = listOfNotNull(selectedNationalityTag), + ) { tag -> + if (selectedNationalityTag != null && selectedNationalityTag != tag) { + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.tag_max_one)) + } + } else { + selectedNationalityTag = + if (selectedNationalityTag == tag) null else tag + updateSelectedTags() + } + } + } + + // 맛 + item { + TagChipGroup( + groupLabel = stringResource(R.string.taste), + tags = tasteTagList, + selectedTags = listOfNotNull(selectedTasteTag), + ) { tag -> + if (selectedTasteTag != null && selectedTasteTag != tag) { + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.tag_max_one)) + } + } else { + selectedTasteTag = if (selectedTasteTag == tag) null else tag + updateSelectedTags() + } + } + } + + // 상황 + item { + TagChipGroup( + groupLabel = stringResource(R.string.occasion), + tags = occasionTagList, + selectedTags = listOfNotNull(selectedOccasionTag), + ) { tag -> + if (selectedOccasionTag != null && selectedOccasionTag != tag) { + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.tag_max_one)) + } + } else { + selectedOccasionTag = if (selectedOccasionTag == tag) null else tag + updateSelectedTags() + } + } + } + + item { Spacer(modifier = Modifier.height(12.dp)) } + + // 가격 슬라이더 + item { + Text( + text = stringResource(R.string.price_range_title), + style = ourMenuTypography().pretendard_400_14, + color = Neutral900 + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource( + R.string.price_range, + priceRange.start.toInt(), + priceRange.endInclusive.toInt() + ), + style = ourMenuTypography().pretendard_700_18 + ) + + Spacer(modifier = Modifier.height(8.dp)) + + RangeSlider( + value = priceRange, + onValueChange = { newValue -> + val adjustedStart = + (Math.round(newValue.start / stepSize) * stepSize).coerceAtLeast( + minPrice + ) + val adjustedEnd = + (Math.round(newValue.endInclusive / stepSize) * stepSize).coerceAtMost( + maxPrice + ) + + priceRange = adjustedStart..adjustedEnd // 5000 단위 반올림 적용 + }, + valueRange = minPrice..maxPrice, + steps = ((maxPrice - minPrice) / stepSize - 1).toInt(), // 5000원 단위로 이동 + colors = SliderDefaults.colors( + thumbColor = Primary500Main, + activeTrackColor = Primary500Main, + inactiveTrackColor = Neutral300, + activeTickColor = Primary500Main, + inactiveTickColor = Neutral300 + ), + modifier = Modifier.padding(horizontal = 8.dp), + startThumb = { + SliderDefaults.Thumb( + interactionSource = remember { MutableInteractionSource() }, + colors = SliderDefaults.colors( + thumbColor = Primary500Main + ), + modifier = Modifier + .size(28.dp) + .clip(CircleShape) + ) + }, + endThumb = { + SliderDefaults.Thumb( + interactionSource = remember { MutableInteractionSource() }, + colors = SliderDefaults.colors( + thumbColor = Primary500Main + ), + modifier = Modifier + .size(28.dp) + .clip(CircleShape) + ) + }, + ) + + Spacer(modifier = Modifier.height(24.dp)) + } + } + + Spacer(modifier = Modifier.height(24.dp)) + + } + + Row( + modifier = modifier + .fillMaxWidth() + .padding(bottom = 20.dp, start = 20.dp, end = 20.dp), + horizontalArrangement = Arrangement.Center + ) { + BottomHalfWidthButton( + modifier = modifier.weight(1f), + containerColor = Neutral400, + contentColor = NeutralWhite, + text = stringResource(R.string.reset) + ) { + // 모든 선택된 필터 초기화 + selectedCategoryTag = null + selectedNationalityTag = null + selectedTasteTag = null + selectedOccasionTag = null + priceRange = minPrice..maxPrice + + onSelectedTagsChange(emptyList()) + } + Spacer(modifier = modifier.width(12.dp)) + BottomHalfWidthButton( + modifier = modifier.weight(1f), + containerColor = Primary500Main, + contentColor = NeutralWhite, + text = stringResource(R.string.apply) + ) { + onApplyButtonClick() + } + } + } + + OurSnackbarHost( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 60.dp), + hostState = snackbarHostState, + isChecked = false, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun FilterBottomSheetPreview() { + val categoryTags = listOf( + R.drawable.ic_tag_rice to "밥", + R.drawable.ic_tag_rice to "빵", + R.drawable.ic_tag_rice to "면", + R.drawable.ic_tag_rice to "고기", + R.drawable.ic_tag_rice to "생선", + R.drawable.ic_tag_rice to "카페", + R.drawable.ic_tag_rice to "디저트", + R.drawable.ic_tag_rice to "패스트푸드", + ) + val nationalityTags = listOf( + R.drawable.ic_tag_rice to "한식", + R.drawable.ic_tag_rice to "중식", + R.drawable.ic_tag_rice to "일식", + R.drawable.ic_tag_rice to "양식", + R.drawable.ic_tag_rice to "아시안", + ) + val tasteTags = listOf( + R.drawable.ic_tag_rice to "매콤함", + R.drawable.ic_tag_rice to "달달함", + R.drawable.ic_tag_rice to "시원함", + R.drawable.ic_tag_rice to "뜨끈함", + R.drawable.ic_tag_rice to "얼큰함", + ) + val occasionTags = listOf( + R.drawable.ic_tag_rice to "혼밥", + R.drawable.ic_tag_rice to "비즈니스 미팅", + R.drawable.ic_tag_rice to "친구 약속", + R.drawable.ic_tag_rice to "데이트", + R.drawable.ic_tag_rice to "밥약", + R.drawable.ic_tag_rice to "단체", + ) + var selectedTags by rememberSaveable { mutableStateOf(listOf()) } + + FilterBottomSheet( + categoryTagList = categoryTags, + nationalityTagList = nationalityTags, + tasteTagList = tasteTags, + occasionTagList = occasionTags, + onSelectedTagsChange = { newSelectedTags -> selectedTags = newSelectedTags }, + onApplyButtonClick = {} + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/MenuFolderButton.kt b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/MenuFolderButton.kt index 0e79c7cc..4e6cb28b 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/MenuFolderButton.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/MenuFolderButton.kt @@ -2,6 +2,7 @@ package com.kuit.ourmenu.ui.menuFolder.component import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -34,7 +35,9 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage import com.kuit.ourmenu.R +import com.kuit.ourmenu.data.model.menuFolder.response.MenuFolderResponse import com.kuit.ourmenu.ui.theme.Neutral300 import com.kuit.ourmenu.ui.theme.Neutral700 import com.kuit.ourmenu.ui.theme.NeutralWhite @@ -45,9 +48,13 @@ import kotlinx.coroutines.launch @Composable fun MenuFolderButton( + menuFolder: MenuFolderResponse, isSwiped: Boolean, // 현재 버튼이 스와이프된 상태인지 확인 onSwipe: () -> Unit, // 새로운 버튼이 스와이프될 때 호출 - onReset: () -> Unit // 버튼이 닫히면 호출 + onReset: () -> Unit, // 버튼이 닫히면 호출 + onButtonClick: () -> Unit = {}, + onEditClick: () -> Unit = {}, + onDeleteClick: () -> Unit = {} ) { var offsetX by remember { mutableFloatStateOf(0f) } val scope = rememberCoroutineScope() @@ -80,26 +87,33 @@ fun MenuFolderButton( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd ) { - MenuFolderDeleteButton() - MenuFolderEditButton() + MenuFolderDeleteButton(onDeleteClick) + MenuFolderEditButton(onEditClick) } // 스와이프 상태일 때만 offset 적용 - Box(modifier = Modifier.offset(x = if (isSwiped) offsetX.dp else 0.dp)) { - MenuFolderContent() + Box( + modifier = Modifier + .offset(x = if (isSwiped) offsetX.dp else 0.dp) + .clickable(onClick = onButtonClick) + ) { + MenuFolderContent( + menuFolder = menuFolder + ) } } } @Composable -fun MenuFolderEditButton() { +fun MenuFolderEditButton(onEditClick: () -> Unit = {}) { Box( modifier = Modifier .padding(end = 64.dp) .width(80.dp) .fillMaxHeight() .clip(RoundedCornerShape(topEnd = 12.dp, bottomEnd = 12.dp)) - .background(color = Neutral300), + .background(color = Neutral300) + .clickable(onClick = onEditClick), contentAlignment = Alignment.CenterEnd ) { Column(modifier = Modifier.padding(end = 20.dp)) { @@ -119,14 +133,15 @@ fun MenuFolderEditButton() { } @Composable -fun MenuFolderDeleteButton() { +fun MenuFolderDeleteButton(onDeleteClick: () -> Unit = {}) { Box( modifier = Modifier .width(80.dp) .fillMaxHeight() .clip(RoundedCornerShape(topEnd = 12.dp, bottomEnd = 12.dp)) - .background(color = Primary500Main), - contentAlignment = Alignment.CenterEnd + .background(color = Primary500Main) + .clickable(onClick = onDeleteClick), + contentAlignment = Alignment.CenterEnd, ) { Column(modifier = Modifier.padding(end = 20.dp)) { Image( @@ -145,15 +160,17 @@ fun MenuFolderDeleteButton() { } @Composable -fun MenuFolderContent() { - val menuCount = 5 // 임의로 정한 값 +fun MenuFolderContent( + menuFolder: MenuFolderResponse, +) { + val menuCount = menuFolder.menuIds.size Box( modifier = Modifier .fillMaxSize() ) { - Image( - painter = painterResource(id = R.drawable.img_dummy_pizza), + AsyncImage( + model = menuFolder.menuFolderUrl, contentDescription = "Folder Image", contentScale = ContentScale.FillWidth, modifier = Modifier @@ -185,7 +202,7 @@ fun MenuFolderContent() { ) Spacer(modifier = Modifier.width(4.dp)) Text( - text = stringResource(R.string.menu_folder_name), + text = menuFolder.menuFolderTitle, color = NeutralWhite, style = ourMenuTypography().pretendard_500_24, ) @@ -211,5 +228,16 @@ fun gradientBrush(): Brush { @Preview(showBackground = true) @Composable private fun MenuFolderButtonPreview() { - MenuFolderButton(false, {}, {}) + val dummyMenuFolder = MenuFolderResponse( + menuFolderId = 1, + menuFolderTitle = "인기 메뉴", + menuFolderUrl = "https://ourmenu-default.s3.ap-northeast-2.amazonaws.com/default_menu_folder_img.svg", + menuFolderIcon = "DICE", + menuIds = listOf(1, 2, 3), + index = 0 + ) + + MenuFolderButton( + menuFolder = dummyMenuFolder, + false, {}, {}) } \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/MenuFolderMenuButton.kt b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/MenuFolderMenuButton.kt index 6d96467c..97626489 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/MenuFolderMenuButton.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/MenuFolderMenuButton.kt @@ -1,6 +1,7 @@ package com.kuit.ourmenu.ui.menuFolder.component import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -29,16 +30,16 @@ import com.kuit.ourmenu.ui.theme.Neutral700 import com.kuit.ourmenu.ui.theme.Neutral900 import com.kuit.ourmenu.ui.theme.ourMenuTypography -// TODO: 버튼 누르면 해당 페이지로 이동 @Composable -fun MenuFolderMenuButton(modifier: Modifier = Modifier) { +fun MenuFolderMenuButton(onMenuClick: () -> Unit = {}, onMapClick: () -> Unit = {}) { val menuPrice = 12000 Row( - modifier = modifier + modifier = Modifier .fillMaxWidth() .height(114.dp) .padding(horizontal = 20.dp, vertical = 12.dp) + .clickable(onClick = onMenuClick) ) { Image( painter = painterResource(id = R.drawable.img_dummy_pizza), @@ -111,6 +112,7 @@ fun MenuFolderMenuButton(modifier: Modifier = Modifier) { Image( painter = painterResource(id = R.drawable.ic_to_map), contentDescription = "To Map", + modifier = Modifier.clickable(onClick = onMapClick) ) } } diff --git a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/MenuFolderTopAppBar.kt b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/MenuFolderTopAppBar.kt index 4da18245..b6d8b8df 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/MenuFolderTopAppBar.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/MenuFolderTopAppBar.kt @@ -21,7 +21,7 @@ import com.kuit.ourmenu.ui.theme.NeutralWhite @OptIn(ExperimentalMaterial3Api::class) @Composable -fun MenuFolderTopAppBar(modifier: Modifier = Modifier) { +fun MenuFolderTopAppBar(onClick: () -> Unit) { // 기본 TopAppBar( modifier = Modifier @@ -45,7 +45,7 @@ fun MenuFolderTopAppBar(modifier: Modifier = Modifier) { }, actions = { IconButton( - onClick = { /* TODO : Add Menu Button Click Event */ }, + onClick = onClick, modifier = Modifier.padding(end = 20.dp) ) { Icon( @@ -61,5 +61,7 @@ fun MenuFolderTopAppBar(modifier: Modifier = Modifier) { @Preview(showBackground = true) @Composable private fun HomeTopAppBarPreview() { - MenuFolderTopAppBar() + MenuFolderTopAppBar( + onClick = {} + ) } \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/SortDropdown.kt b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/SortDropdown.kt new file mode 100644 index 00000000..23b98e90 --- /dev/null +++ b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/component/SortDropdown.kt @@ -0,0 +1,115 @@ +package com.kuit.ourmenu.ui.menuFolder.component + +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.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +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.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.kuit.ourmenu.R +import com.kuit.ourmenu.ui.theme.Neutral500 +import com.kuit.ourmenu.ui.theme.Neutral700 +import com.kuit.ourmenu.ui.theme.NeutralWhite +import com.kuit.ourmenu.ui.theme.Primary500Main +import com.kuit.ourmenu.ui.theme.ourMenuTypography + +@Composable +fun SortDropdown( + color: Color = Neutral700, + options: List, + selectedOption: String, + onSelectedOptionChange: (String) -> Unit +) { + var expanded by remember { mutableStateOf(false) } + + Box { + Row( + modifier = Modifier.clickable { + expanded = true + }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = selectedOption, + style = ourMenuTypography().pretendard_500_14, + color = color + ) + + Spacer(modifier = Modifier.width(8.dp)) + + Icon( + painter = if (expanded) painterResource(id = R.drawable.ic_dropdown_btn_up) else painterResource( + id = R.drawable.ic_dropdown_btn + ), + tint = color, + modifier = Modifier.size(16.dp), + contentDescription = "Expand Down" + ) + } + + DropdownMenu( + modifier = Modifier + .width(72.dp) + .background(color = NeutralWhite, shape = RoundedCornerShape(8.dp)), + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + options.forEach { option -> + val isSelected = option == selectedOption + + DropdownMenuItem( + modifier = Modifier + .width(72.dp), + text = { + Text( + text = option, + style = ourMenuTypography().pretendard_500_14.copy( + color = if (isSelected) Primary500Main else Neutral500 + ) + ) + }, + onClick = { + onSelectedOptionChange(option) + } + ) + } + } + } + +} + +@Preview(showBackground = true) +@Composable +private fun SortDropdownPreview() { + val options = listOf("이름순", "등록순", "가격순") + var selectedOption by rememberSaveable { mutableStateOf("이름순") } + + Column(modifier = Modifier.fillMaxSize()) { + SortDropdown( + options = options, + selectedOption = selectedOption + ) { + selectedOption = it + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/screen/MenuFolderAllMenuScreen.kt b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/screen/MenuFolderAllMenuScreen.kt index c25ec209..6de8731b 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/screen/MenuFolderAllMenuScreen.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/screen/MenuFolderAllMenuScreen.kt @@ -1,6 +1,8 @@ package com.kuit.ourmenu.ui.menuFolder.screen +import android.util.Log import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -10,40 +12,111 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Scaffold +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text +import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import com.kuit.ourmenu.R +import com.kuit.ourmenu.ui.common.bottomsheet.BottomSheetDragHandle import com.kuit.ourmenu.ui.common.topappbar.BackButtonTopAppBar import com.kuit.ourmenu.ui.menuFolder.component.AddButton +import com.kuit.ourmenu.ui.menuFolder.component.FilterBottomSheet import com.kuit.ourmenu.ui.menuFolder.component.MenuFolderMenuButton +import com.kuit.ourmenu.ui.menuFolder.component.SortDropdown +import com.kuit.ourmenu.ui.navigator.Routes import com.kuit.ourmenu.ui.theme.Neutral500 import com.kuit.ourmenu.ui.theme.Neutral700 import com.kuit.ourmenu.ui.theme.Neutral900 import com.kuit.ourmenu.ui.theme.NeutralWhite import com.kuit.ourmenu.ui.theme.Primary500Main import com.kuit.ourmenu.ui.theme.ourMenuTypography +import kotlinx.coroutines.launch +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun MenuFolderAllMenuScreen(modifier: Modifier = Modifier) { +fun MenuFolderAllMenuScreen(navController: NavController) { val menuCount = 13 - val filterCount = 1 + var filterCount by rememberSaveable { mutableIntStateOf(0) } // 선택된 필터 개수 상태 관리 + var selectedFilters by rememberSaveable { mutableStateOf(listOf()) } // 선택된 필터 리스트 + + val options = listOf("이름순", "등록순", "가격순") + var selectedOption by rememberSaveable { mutableStateOf("이름순") } + + val scaffoldState = rememberBottomSheetScaffoldState() + val coroutineScope = rememberCoroutineScope() + LaunchedEffect(scaffoldState.bottomSheetState) { + snapshotFlow { scaffoldState.bottomSheetState.currentValue } + .collect { state -> + Log.d("AddMenuTagScreen", "BottomSheetState changed: $state") + } + } - Scaffold( + BottomSheetScaffold( + scaffoldState = scaffoldState, topBar = { - BackButtonTopAppBar(Neutral500, false) + BackButtonTopAppBar(Neutral500, false) { + navController.popBackStack() + } + }, + sheetContainerColor = NeutralWhite, + sheetPeekHeight = 0.dp, + sheetContent = { + FilterBottomSheet( + categoryTagList = listOf( + R.drawable.ic_tag_rice to "밥", + R.drawable.ic_tag_rice to "빵", + R.drawable.ic_tag_rice to "면" + ), + nationalityTagList = listOf( + R.drawable.ic_tag_rice to "한식", + R.drawable.ic_tag_rice to "중식", + R.drawable.ic_tag_rice to "일식" + ), + tasteTagList = listOf( + R.drawable.ic_tag_rice to "매콤함", + R.drawable.ic_tag_rice to "달달함", + R.drawable.ic_tag_rice to "시원함" + ), + occasionTagList = listOf( + R.drawable.ic_tag_rice to "혼밥", + R.drawable.ic_tag_rice to "친구 약속", + R.drawable.ic_tag_rice to "데이트" + ), + onApplyButtonClick = { + coroutineScope.launch { + filterCount = selectedFilters.size // 적용 버튼 클릭 시 선택된 필터 개수 반영 + scaffoldState.bottomSheetState.partialExpand() // 적용 버튼 클릭 시 BottomSheet 닫기 + } + + }, + onSelectedTagsChange = { newSelectedTags -> selectedFilters = newSelectedTags }, + ) + }, + sheetDragHandle = { + BottomSheetDragHandle() } ) { innerPadding -> Column( - modifier = modifier + modifier = Modifier .padding(innerPadding) ) { Row( @@ -71,27 +144,24 @@ fun MenuFolderAllMenuScreen(modifier: Modifier = Modifier) { ) } - // TODO: 드롭다운 만들기 - Row { - Text( - text = stringResource(R.string.sort_type), - style = ourMenuTypography().pretendard_500_14, - color = Neutral700 - ) - - Spacer(modifier = Modifier.width(8.dp)) - - Image( - painter = painterResource(id = R.drawable.ic_dropdown_btn), - contentDescription = "Expand Down" - ) + SortDropdown( + options = options, + selectedOption = selectedOption, + ) { + selectedOption = it } } - // TODO: 버튼 누르면 필터 적용 + // 필터 버튼 (필터 개수 반영) Card( shape = RoundedCornerShape(12.dp), - modifier = Modifier.padding(top = 24.dp, bottom = 16.dp, start = 20.dp), + modifier = Modifier + .padding(top = 24.dp, bottom = 16.dp, start = 20.dp) + .clickable { + coroutineScope.launch { + scaffoldState.bottomSheetState.expand() // 버튼 클릭 시 BottomSheet 열기 + } + }, colors = CardDefaults.cardColors(containerColor = Primary500Main), elevation = CardDefaults.cardElevation(defaultElevation = 8.dp) ) { @@ -107,7 +177,7 @@ fun MenuFolderAllMenuScreen(modifier: Modifier = Modifier) { Spacer(modifier = Modifier.width(8.dp)) Text( - text = "$filterCount", + text = "$filterCount", // 선택된 필터 개수 반영 color = NeutralWhite, style = ourMenuTypography().pretendard_700_16 ) @@ -117,9 +187,15 @@ fun MenuFolderAllMenuScreen(modifier: Modifier = Modifier) { LazyColumn( modifier = Modifier, ) { - // TODO: 버튼 누르면 해당 페이지로 이동 items(menuCount) { index -> - MenuFolderMenuButton() + MenuFolderMenuButton( + onMenuClick = { + navController.navigate(route = Routes.MenuInfo) + }, + onMapClick = { + navController.navigate(route = Routes.MenuInfoMap) + } + ) } item { @@ -127,12 +203,10 @@ fun MenuFolderAllMenuScreen(modifier: Modifier = Modifier) { stringResource(R.string.add_menu), modifier = Modifier.padding(start = 20.dp, end = 20.dp, top = 20.dp) ) { - // TODO: 버튼 누르면 메뉴 추가 페이지로 이동 + navController.navigate(route = Routes.AddMenu) } } } - - } } } @@ -140,5 +214,7 @@ fun MenuFolderAllMenuScreen(modifier: Modifier = Modifier) { @Preview(showBackground = true) @Composable private fun MenuFolderAllMenuScreenPreview() { - MenuFolderAllMenuScreen() + val navController = rememberNavController() + + MenuFolderAllMenuScreen(navController) } \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/screen/MenuFolderDetailScreen.kt b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/screen/MenuFolderDetailScreen.kt index 767c7e43..a7d8f914 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/screen/MenuFolderDetailScreen.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/screen/MenuFolderDetailScreen.kt @@ -14,9 +14,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +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.graphics.Brush @@ -26,121 +30,137 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import com.kuit.ourmenu.R import com.kuit.ourmenu.ui.common.topappbar.BackButtonTopAppBar import com.kuit.ourmenu.ui.menuFolder.component.AddButton import com.kuit.ourmenu.ui.menuFolder.component.MenuFolderMenuButton +import com.kuit.ourmenu.ui.menuFolder.component.SortDropdown +import com.kuit.ourmenu.ui.navigator.Routes import com.kuit.ourmenu.ui.theme.Neutral50 import com.kuit.ourmenu.ui.theme.NeutralWhite import com.kuit.ourmenu.ui.theme.ourMenuTypography @Composable -fun MenuFolderDetailScreen(modifier: Modifier = Modifier) { +fun MenuFolderDetailScreen(navController: NavController) { val menuCount = 13 // 임의로 정한 값 - Column( - modifier = modifier.fillMaxSize() - ) { - Box( - modifier = Modifier - .height(192.dp), - ) { - Image( - painter = painterResource(R.drawable.img_dummy_pizza), - contentDescription = "menu folder image", - contentScale = ContentScale.FillWidth, - modifier = Modifier.fillMaxWidth() - ) + val options = listOf("이름순", "등록순", "가격순") + var selectedOption by rememberSaveable { mutableStateOf("이름순") } - // Gradient Overlay - Box( + Scaffold( + topBar = {}, + content = { innerPadding -> + Column( modifier = Modifier - .matchParentSize() - .background( - brush = Brush.verticalGradient( - colors = listOf( - Color.Black.copy(alpha = 0f), // 투명 (rgba(0, 0, 0, 0.00)) - Color.Black.copy(alpha = 0.5f) // 불투명 (rgba(0, 0, 0, 0.70)) - ), - startY = 0f, - endY = 400f // 점점 어두워지는 위치 설정 - ) - ) - ) - - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(top = 144.dp, start = 20.dp, end = 20.dp).fillMaxWidth() + .padding(innerPadding) + .fillMaxSize() ) { + Box( + modifier = Modifier + .height(192.dp), + ) { + Image( + painter = painterResource(R.drawable.img_dummy_pizza), + contentDescription = "menu folder image", + contentScale = ContentScale.FillWidth, + modifier = Modifier.fillMaxWidth() + ) - Row( - verticalAlignment = Alignment.CenterVertically, + Box( + modifier = Modifier + .matchParentSize() + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color.Black.copy(alpha = 0f), // 투명 (rgba(0, 0, 0, 0.00)) + Color.Black.copy(alpha = 0.5f) // 불투명 (rgba(0, 0, 0, 0.70)) + ), + startY = 0f, + endY = 400f // 점점 어두워지는 위치 설정 + ) + ) + ) - ) { - Image( - painter = painterResource(id = R.drawable.img_popup_dice), - contentDescription = "Folder Icon", - modifier = Modifier.size(32.dp) - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.menu_folder_name), - color = NeutralWhite, - style = ourMenuTypography().pretendard_600_20, - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = String.format(stringResource(R.string.count), menuCount), - color = Neutral50, - style = ourMenuTypography().pretendard_500_14, - ) - } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding(top = 144.dp, start = 20.dp, end = 20.dp) + .fillMaxWidth() + ) { - // TODO: 드롭다운 만들기 - Row { - Text( - text = stringResource(R.string.sort_type), - style = ourMenuTypography().pretendard_500_14, - color = Neutral50 - ) + Row( + verticalAlignment = Alignment.CenterVertically, - Spacer(modifier = Modifier.width(8.dp)) + ) { + Image( + painter = painterResource(id = R.drawable.img_popup_dice), + contentDescription = "Folder Icon", + modifier = Modifier.size(32.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.menu_folder_name), + color = NeutralWhite, + style = ourMenuTypography().pretendard_600_20, + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = String.format(stringResource(R.string.count), menuCount), + color = Neutral50, + style = ourMenuTypography().pretendard_500_14, + ) + } - Icon( - painter = painterResource(id = R.drawable.ic_dropdown_btn), - contentDescription = "Expand Down", - tint = Neutral50 - ) + SortDropdown( + options = options, + selectedOption = selectedOption, + color = NeutralWhite + ) { + selectedOption = it + } + } } - } - BackButtonTopAppBar(NeutralWhite, true) - } + LazyColumn( + modifier = Modifier.padding(top = 16.dp), + ) { + items(menuCount) { index -> + MenuFolderMenuButton( + onMenuClick = { + navController.navigate(route = Routes.MenuInfo) + }, + onMapClick = { + navController.navigate(route = Routes.MenuInfoMap) + } + ) + } - LazyColumn( - modifier = Modifier.padding(top = 16.dp), - ) { - // TODO: 버튼 누르면 해당 페이지로 이동 - items(menuCount) { index -> - MenuFolderMenuButton() + item { + AddButton( + stringResource(R.string.add_menu), + modifier = Modifier.padding(start = 20.dp, end = 20.dp, top = 20.dp) + ) { + navController.navigate(route = Routes.AddMenu) + } + } + } } - item { - AddButton( - stringResource(R.string.add_menu), - modifier = Modifier.padding(start = 20.dp, end = 20.dp, top = 20.dp) - ) { - // TODO: 버튼 누르면 메뉴 추가 페이지로 이동 - } + BackButtonTopAppBar(NeutralWhite, true) { + navController.popBackStack() } } + ) - } } @Preview(showBackground = true) @Composable private fun MenuFolderDetailScreenPreview() { - MenuFolderDetailScreen() + val navController = rememberNavController() + + MenuFolderDetailScreen(navController) } \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/screen/MenuFolderScreen.kt b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/screen/MenuFolderScreen.kt index 4b544d45..63154350 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/screen/MenuFolderScreen.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/screen/MenuFolderScreen.kt @@ -1,12 +1,15 @@ package com.kuit.ourmenu.ui.menuFolder.screen +import android.util.Log import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column 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.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -21,25 +24,40 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import com.kuit.ourmenu.R import com.kuit.ourmenu.ui.menuFolder.component.AddButton import com.kuit.ourmenu.ui.menuFolder.component.MenuFolderButton import com.kuit.ourmenu.ui.menuFolder.component.MenuFolderTopAppBar +import com.kuit.ourmenu.ui.menuFolder.viewmodel.MenuFolderViewModel +import com.kuit.ourmenu.ui.navigator.Routes import com.kuit.ourmenu.ui.theme.NeutralWhite import com.kuit.ourmenu.ui.theme.Primary500Main import com.kuit.ourmenu.ui.theme.ourMenuTypography @Composable -fun MenuFolderScreen(modifier: Modifier = Modifier) { - val menuCount = 5 // 임의로 정한 값 - val menuFolderCount = 8 // 임의로 정한 값 - +fun MenuFolderScreen( + navController: NavController, + viewModel: MenuFolderViewModel = hiltViewModel() +) { // 현재 스와이프된 버튼의 인덱스를 관리 (한 번에 하나만 스와이프되도록) var swipedIndex by remember { mutableIntStateOf(-1) } + val menuFolders by viewModel.menuFolders.collectAsStateWithLifecycle() + val totalMenuCount = menuFolders.sumOf { it.menuIds.size } // 전체 메뉴 개수 - 서버에서 받아오도록 수정 + + Log.d("MenuFolderScreen", "menuFolders: $menuFolders") + Scaffold( topBar = { - MenuFolderTopAppBar() + MenuFolderTopAppBar( + onClick = { + navController.navigate(route = Routes.AddMenu) + } + ) } ) { innerPadding -> LazyColumn( @@ -55,7 +73,10 @@ fun MenuFolderScreen(modifier: Modifier = Modifier) { .height(64.dp) .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) - .background(Primary500Main), + .background(Primary500Main) + .clickable(onClick = { + navController.navigate(route = Routes.MenuFolderAllMenu) + }), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { @@ -66,20 +87,23 @@ fun MenuFolderScreen(modifier: Modifier = Modifier) { ) Text( - text = String.format(stringResource(R.string.menu_count), menuCount), + text = String.format(stringResource(R.string.menu_count), totalMenuCount), color = NeutralWhite, style = ourMenuTypography().pretendard_500_14, ) } } - // 스와이프 제어 // TODO: 드래그 앤 드롭 구현 - items(menuFolderCount) { index -> + itemsIndexed(menuFolders) { index, folder -> MenuFolderButton( - isSwiped = swipedIndex == index, // 현재 스와이프된 아이템인지 확인 - onSwipe = { swipedIndex = index }, // 새로운 버튼이 스와이프되면 상태 변경 - onReset = { if (swipedIndex == index) swipedIndex = -1 } // 닫히면 초기화 + menuFolder = folder, + isSwiped = swipedIndex == index, + onSwipe = { swipedIndex = index }, + onReset = { if (swipedIndex == index) swipedIndex = -1 }, + onButtonClick = { + navController.navigate(route = Routes.MenuFolderDetail) + } ) } @@ -98,5 +122,7 @@ fun MenuFolderScreen(modifier: Modifier = Modifier) { @Preview(showBackground = true) @Composable private fun MenuFolderScreenPreview() { - MenuFolderScreen() + val navController = rememberNavController() + + MenuFolderScreen(navController) } \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/viewmodel/MenuFolderViewModel.kt b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/viewmodel/MenuFolderViewModel.kt new file mode 100644 index 00000000..8f753473 --- /dev/null +++ b/app/src/main/java/com/kuit/ourmenu/ui/menuFolder/viewmodel/MenuFolderViewModel.kt @@ -0,0 +1,54 @@ +package com.kuit.ourmenu.ui.menuFolder.viewmodel + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.kuit.ourmenu.data.model.menuFolder.response.MenuFolderResponse +import com.kuit.ourmenu.data.repository.MenuFolderRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class MenuFolderViewModel @Inject constructor( + private val menuFolderRepository: MenuFolderRepository +) : ViewModel() { + + private val _menuFolders = MutableStateFlow>(emptyList()) + val menuFolders = _menuFolders.asStateFlow() + + private val _error: MutableStateFlow = MutableStateFlow(null) + val error = _error.asStateFlow() + + private val _isLoading = MutableStateFlow(false) + val isLoading = _isLoading.asStateFlow() + + init { + getMenuFolders() + } + + fun getMenuFolders() { + viewModelScope.launch { + _isLoading.value = true + _error.value = null + + menuFolderRepository.getMenuFolders() + .fold( + onSuccess = { response -> + if (response != null) { + _menuFolders.value = response.sortedBy { it.index } + Log.d("test", _menuFolders.value.toString()) + } + }, + onFailure = { throwable -> + _error.value = throwable.message ?: "메뉴 폴더를 불러오는 중 오류가 발생했습니다." + Log.d("test2", _error.value.toString()) + } + ) + + _isLoading.value = false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/menuinfo/screen/MenuInfoDefaultScreen.kt b/app/src/main/java/com/kuit/ourmenu/ui/menuinfo/screen/MenuInfoDefaultScreen.kt index f270eddf..c940db35 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/menuinfo/screen/MenuInfoDefaultScreen.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/menuinfo/screen/MenuInfoDefaultScreen.kt @@ -12,6 +12,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import com.kuit.ourmenu.ui.menuinfo.component.info.MenuInfoAdditionalContent import com.kuit.ourmenu.ui.menuinfo.component.info.MenuInfoChipContent import com.kuit.ourmenu.ui.menuinfo.component.info.MenuInfoContent @@ -22,7 +24,7 @@ import com.kuit.ourmenu.ui.menuinfo.dummy.MenuInfoDummyData import com.kuit.ourmenu.ui.theme.Neutral300 @Composable -fun MenuInfoDefaultScreen() { +fun MenuInfoDefaultScreen(navController: NavController) { val pagerState = rememberPagerState { 3 } @@ -67,7 +69,7 @@ fun MenuInfoDefaultScreen() { modifier = Modifier .align(Alignment.BottomEnd) .padding(end = 20.dp, bottom = 28.dp), - ) { } + ) { } } } @@ -75,5 +77,7 @@ fun MenuInfoDefaultScreen() { @Preview(showBackground = true) @Composable private fun MenuInfoDefaultPreview() { - MenuInfoDefaultScreen() + val navController = rememberNavController() + + MenuInfoDefaultScreen(navController) } \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/menuinfo/screen/MenuInfoMapScreen.kt b/app/src/main/java/com/kuit/ourmenu/ui/menuinfo/screen/MenuInfoMapScreen.kt index a791faa7..45976c43 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/menuinfo/screen/MenuInfoMapScreen.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/menuinfo/screen/MenuInfoMapScreen.kt @@ -18,15 +18,17 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import com.kuit.ourmenu.ui.common.GoToMapButton import com.kuit.ourmenu.ui.common.bottomsheet.BottomSheetDragHandle -import com.kuit.ourmenu.ui.common.topappbar.OurMenuAddButtonTopAppBar import com.kuit.ourmenu.ui.common.bottomsheet.MenuInfoBottomSheetContent +import com.kuit.ourmenu.ui.common.topappbar.OurMenuAddButtonTopAppBar import com.kuit.ourmenu.ui.theme.NeutralWhite @OptIn(ExperimentalMaterial3Api::class) @Composable -fun MenuInfoMapScreen(modifier: Modifier = Modifier) { +fun MenuInfoMapScreen(navController: NavController) { val scaffoldState = rememberBottomSheetScaffoldState() var dragHandleHeight by remember { mutableStateOf(0.dp) } var bottomSheetContentHeight by remember { mutableStateOf(0.dp) } @@ -60,7 +62,7 @@ fun MenuInfoMapScreen(modifier: Modifier = Modifier) { sheetPeekHeight = bottomSheetContentHeight, ) { innerPaddings -> Box( - modifier = modifier + modifier = Modifier .fillMaxSize() .padding(innerPaddings) ) { @@ -79,5 +81,7 @@ fun MenuInfoMapScreen(modifier: Modifier = Modifier) { @Preview(showBackground = true) @Composable private fun MenuInfoMapScreenPreview() { - MenuInfoMapScreen() + val navController = rememberNavController() + + MenuInfoMapScreen(navController) } \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/navigator/MainNavGraph.kt b/app/src/main/java/com/kuit/ourmenu/ui/navigator/MainNavGraph.kt index 6b148220..54adc541 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/navigator/MainNavGraph.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/navigator/MainNavGraph.kt @@ -5,8 +5,14 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import com.kuit.ourmenu.ui.addmenu.screen.AddMenuScreen import androidx.navigation.compose.navigation import com.kuit.ourmenu.ui.home.screen.HomeScreen +import com.kuit.ourmenu.ui.menuFolder.screen.MenuFolderAllMenuScreen +import com.kuit.ourmenu.ui.menuFolder.screen.MenuFolderDetailScreen +import com.kuit.ourmenu.ui.menuFolder.screen.MenuFolderScreen +import com.kuit.ourmenu.ui.menuinfo.screen.MenuInfoDefaultScreen +import com.kuit.ourmenu.ui.menuinfo.screen.MenuInfoMapScreen import com.kuit.ourmenu.ui.onboarding.screen.LandingScreen import com.kuit.ourmenu.ui.onboarding.screen.LoginScreen import com.kuit.ourmenu.ui.onboarding.screen.signup.SignupEmailScreen @@ -45,5 +51,29 @@ fun MainNavGraph(navController: NavHostController) { SignupVerifyScreen(navController = navController, viewModel = viewModel) } } + + // 메뉴판 + composable { + MenuFolderScreen(navController = navController) + } + composable { + MenuFolderDetailScreen(navController = navController) + } + composable { + MenuFolderAllMenuScreen(navController = navController) + } + + // 메뉴 + composable { + MenuInfoDefaultScreen(navController = navController) + } + composable { + MenuInfoMapScreen(navController = navController) + } + + // 메뉴 추가 + composable { + AddMenuScreen(navController = navController) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/kuit/ourmenu/ui/navigator/Routes.kt b/app/src/main/java/com/kuit/ourmenu/ui/navigator/Routes.kt index 21cf374d..91736858 100644 --- a/app/src/main/java/com/kuit/ourmenu/ui/navigator/Routes.kt +++ b/app/src/main/java/com/kuit/ourmenu/ui/navigator/Routes.kt @@ -10,6 +10,22 @@ sealed interface Routes{ // 지도 // 메뉴판 + @Serializable + data object MenuFolder: Routes + @Serializable + data object MenuFolderDetail: Routes + @Serializable + data object MenuFolderAllMenu: Routes + + // 메뉴 + @Serializable + data object MenuInfo: Routes + @Serializable + data object MenuInfoMap: Routes + + // 메뉴 추가 + @Serializable + data object AddMenu: Routes // Mypage diff --git a/app/src/main/res/drawable/ic_dropdown_btn_up.xml b/app/src/main/res/drawable/ic_dropdown_btn_up.xml new file mode 100644 index 00000000..0a7f15f1 --- /dev/null +++ b/app/src/main/res/drawable/ic_dropdown_btn_up.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 566ed508..fb74a352 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -95,6 +95,15 @@ %d 원 전체 메뉴 %d 개 - 이름순 한강 뷰 맛집 + 필터링 + 각 항목 당 1개 선택 + 가격대 + %d원 ~ %d원 + 태그는 항목 당 1개만 선택할 수 있어요 + + 종류 + 나라 별 음식 + + 상황 \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 20e2a015..2661ee69 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,6 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true + +android.defaults.buildfeatures.buildconfig=true \ No newline at end of file