From 3d59182e22a4eccec086c435a4c8f8c9c72b2566 Mon Sep 17 00:00:00 2001 From: oungsi2000 Date: Mon, 1 Dec 2025 13:31:47 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat(placeMap):=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=EB=8C=80=20=EC=84=A0=ED=83=9D=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?TimeTagMenu=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 지도 화면에서 사용될 시간대 선택 드롭다운 메뉴인 `TimeTagMenu` 컴포저블을 새로 추가했습니다. - **주요 기능:** - `ExposedDropdownMenuBox`를 사용하여 커스텀 드롭다운 메뉴를 구현했습니다. - 사용자가 시간 태그(`TimeTag`)를 선택하면 `onTimeTagClick` 콜백을 통해 선택된 값을 전달합니다. - 선택된 `TimeTag`의 이름(`title`)을 버튼 텍스트로 표시합니다. - 메뉴 아이템 클릭 시, 리플(ripple) 효과가 끝난 후 메뉴가 닫히도록 `waitForRipple` 유틸리티 함수를 사용했습니다. - **컴포저블 구성:** - `TimeTagMenu`: 드롭다운 메뉴의 전체적인 레이아웃과 상태를 관리합니다. - `TimeTagButton`: 드롭다운을 열고 현재 선택된 시간대를 표시하는 버튼입니다. - `@Preview`를 포함하여 컴포저블의 시각적 확인이 가능하도록 했습니다. --- .../timeTagSpinner/component/TimeTagMenu.kt | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt new file mode 100644 index 0000000..652cb8f --- /dev/null +++ b/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt @@ -0,0 +1,180 @@ +package com.daedan.festabook.presentation.placeMap.timeTagSpinner.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +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.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuBoxScope +import androidx.compose.material3.Icon +import androidx.compose.material3.MenuAnchorType +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.ripple +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.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import com.daedan.festabook.R +import com.daedan.festabook.domain.model.TimeTag +import com.daedan.festabook.presentation.theme.FestabookColor +import com.daedan.festabook.presentation.theme.FestabookTypography +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TimeTagMenu( + title: String, + timeTags: List, + modifier: Modifier = Modifier, + onTimeTagClick: (TimeTag) -> Unit = {}, +) { + var expanded by remember { mutableStateOf(false) } + var dropdownWidth by remember { mutableStateOf(IntSize.Zero) } + val density = LocalDensity.current + val scope = rememberCoroutineScope() + + Row( + modifier = modifier.fillMaxWidth(), + ) { + ExposedDropdownMenuBox( + modifier = + Modifier + .wrapContentSize() + .background(Color.Transparent), + expanded = expanded, + onExpandedChange = { expanded = !expanded }, + ) { + TimeTagButton( + title = title, + onSizeDetermined = { dropdownWidth = it }, + ) + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + offset = DpOffset(x = 0.dp, y = 8.dp), + modifier = + Modifier + .width( + with(density) { dropdownWidth.width.toDp() }, + ).background(color = FestabookColor.white) + .border( + width = 2.dp, + color = FestabookColor.gray300, + shape = RoundedCornerShape(8.dp), + ), + shape = RoundedCornerShape(8.dp), + ) { + timeTags.forEach { item -> + DropdownMenuItem( + text = { + Text( + text = item.name, + fontStyle = FestabookTypography.bodyLarge.fontStyle, + ) + }, + onClick = { + scope.launch { + onTimeTagClick(item) + waitForRipple { + expanded = false + } + } + }, + ) + } + } + } + } +} + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +private fun ExposedDropdownMenuBoxScope.TimeTagButton( + title: String, + onSizeDetermined: (IntSize) -> Unit, +) { + Row( + modifier = + Modifier + .width(140.dp) + .onGloballyPositioned { coordinates -> + onSizeDetermined(coordinates.size) + }.menuAnchor( + type = MenuAnchorType.PrimaryNotEditable, + enabled = true, + ).height(TopAppBarDefaults.MediumAppBarCollapsedHeight) // Festabook TopAppbar Size + .background(Color.Transparent) + .clickable( + onClick = {}, + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(), + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = title, + style = FestabookTypography.displaySmall, + ) + + Icon( + painter = painterResource(id = R.drawable.ic_chevron_down), + contentDescription = "드롭다운", + ) + } +} + +private suspend inline fun waitForRipple( + timeMillis: Long = 100, + after: () -> Unit = {}, +) { + delay(timeMillis) + after() +} + +@Composable +@Preview(showBackground = true) +private fun TimeTagMenuPreview() { + val timeTags = + listOf( + TimeTag(1, "1일차 오전"), + TimeTag(2, "오후"), + ) + var title by remember { mutableStateOf("1일차 오전") } + + TimeTagMenu( + title = title, + timeTags = timeTags, + modifier = + Modifier + .background(FestabookColor.white) + .padding(horizontal = 16.dp), + // Festabook Gutter + onTimeTagClick = { title = it.name }, + ) +} From 924b1f19f2ba73770b713a897201109d045cfff9 Mon Sep 17 00:00:00 2001 From: oungsi2000 Date: Mon, 1 Dec 2025 14:53:42 +0900 Subject: [PATCH 2/8] =?UTF-8?q?refactor(PlaceMap):=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=ED=8F=AC=EB=A7=B7=ED=8C=85=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `PlaceMapFragment`의 `ComposeView` 내부 Modifier 체이닝에서 `background` 함수의 괄호 위치를 수정하여 코드 포맷을 정리했습니다. --- .../presentation/placeMap/PlaceMapFragment.kt | 62 +++++++++---------- .../placeMap/PlaceMapViewModel.kt | 7 ++- .../main/res/layout/fragment_place_map.xml | 33 +++------- 3 files changed, 43 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt index a80d71a..3505b47 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt @@ -3,12 +3,21 @@ package com.daedan.festabook.presentation.placeMap import android.content.Context import android.os.Bundle import android.view.View -import android.widget.AdapterView +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.commit import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import com.daedan.festabook.R import com.daedan.festabook.databinding.FragmentPlaceMapBinding @@ -31,7 +40,8 @@ import com.daedan.festabook.presentation.placeMap.placeCategory.PlaceCategoryFra import com.daedan.festabook.presentation.placeMap.placeDetailPreview.PlaceDetailPreviewFragment import com.daedan.festabook.presentation.placeMap.placeDetailPreview.PlaceDetailPreviewSecondaryFragment import com.daedan.festabook.presentation.placeMap.placeList.PlaceListFragment -import com.daedan.festabook.presentation.placeMap.timeTagSpinner.adapter.TimeTagSpinnerAdapter +import com.daedan.festabook.presentation.placeMap.timeTagSpinner.component.TimeTagMenu +import com.daedan.festabook.presentation.theme.FestabookColor import com.naver.maps.map.MapFragment import com.naver.maps.map.NaverMap import com.naver.maps.map.OnMapReadyCallback @@ -88,23 +98,6 @@ class PlaceMapFragment( savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) - binding.spinnerSelectTimeTag.onItemSelectedListener = - object : AdapterView.OnItemSelectedListener { - override fun onItemSelected( - parent: AdapterView<*>, - view: View?, - position: Int, - id: Long, - ) { - val item = parent.getItemAtPosition(position) as TimeTag - - onTimeTagSelected(item) - } - - override fun onNothingSelected(parent: AdapterView<*>) { - onNothingSelected() - } - } if (savedInstanceState == null) { childFragmentManager.commit { addWithSimpleTag(R.id.fcv_map_container, mapFragment) @@ -170,18 +163,25 @@ class PlaceMapFragment( } private fun setUpObserver() { - viewModel.timeTags.observe(viewLifecycleOwner) { timeTags -> - // 타임태그가 없는 경우 메뉴 GONE - binding.layoutMapMenu.visibility = - if (timeTags.isNullOrEmpty()) View.GONE else View.VISIBLE - - if (binding.spinnerSelectTimeTag.adapter == null) { - val adapter = TimeTagSpinnerAdapter(requireContext(), timeTags.toMutableList()) - binding.spinnerSelectTimeTag.adapter = adapter - } else { - val adapter = binding.spinnerSelectTimeTag.adapter as TimeTagSpinnerAdapter - adapter.updateItems(timeTags) - adapter.notifyDataSetChanged() + binding.cvTimeTag.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + binding.cvTimeTag.setContent { + val timeTags by viewModel.timeTags.collectAsStateWithLifecycle(viewLifecycleOwner) + if (!timeTags.isEmpty()) { + val initialTitle = timeTags.first().name + var title by remember { mutableStateOf(initialTitle) } + TimeTagMenu( + title = title, + timeTags = timeTags, + onTimeTagClick = { timeTag -> + title = timeTag.name + onTimeTagSelected(timeTag) + }, + modifier = + Modifier + .background( + FestabookColor.white, + ).padding(horizontal = 24.dp), + ) } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapViewModel.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapViewModel.kt index 7836f60..57e18f7 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapViewModel.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapViewModel.kt @@ -21,6 +21,9 @@ import com.daedan.festabook.presentation.placeMap.model.toUiModel import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoMap import dev.zacsweers.metro.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @ContributesIntoMap(AppScope::class) @@ -38,8 +41,8 @@ class PlaceMapViewModel @Inject constructor( val placeGeographies: LiveData>> get() = _placeGeographies - private val _timeTags = MutableLiveData>() - val timeTags: LiveData> = _timeTags + private val _timeTags = MutableStateFlow>(emptyList()) + val timeTags: StateFlow> = _timeTags.asStateFlow() private val _selectedTimeTag = MutableLiveData() val selectedTimeTag: LiveData = _selectedTimeTag diff --git a/app/src/main/res/layout/fragment_place_map.xml b/app/src/main/res/layout/fragment_place_map.xml index e1d7e7f..047b3fa 100644 --- a/app/src/main/res/layout/fragment_place_map.xml +++ b/app/src/main/res/layout/fragment_place_map.xml @@ -1,7 +1,7 @@ - @@ -24,38 +24,19 @@ - - - - - - + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toBottomOf="@id/cv_timeTag" /> Date: Mon, 1 Dec 2025 16:00:03 +0900 Subject: [PATCH 3/8] =?UTF-8?q?refactor(TimeTagMenu):=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=EA=B4=80=EB=A6=AC=20=EB=A1=9C=EC=A7=81=EC=9D=84=20`TimeTagM?= =?UTF-8?q?enu`=20=EB=82=B4=EB=B6=80=EB=A1=9C=20=EC=9D=B4=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `TimeTagMenu` 컴포저블의 상태 관리 방식을 리팩토링했습니다. 기존에는 외부 컴포저블(`PlaceMapFragment`)에서 `title` 상태를 관리하고 `TimeTagMenu`에 전달했지만, 이제 `TimeTagMenu`가 내부적으로 `title` 상태를 관리하도록 변경했습니다. - **`TimeTagMenu.kt` 수정:** - `title` 파라미터를 `initialTitle`로 변경하여 초기값만 받도록 수정했습니다. - `remember { mutableStateOf(initialTitle) }`를 사용하여 `title` 상태를 내부적으로 관리합니다. - 메뉴 아이템 클릭 시, `onTimeTagClick` 콜백을 호출하기 전에 내부 `title` 상태를 먼저 업데이트하도록 로직을 변경했습니다. - **`PlaceMapFragment.kt` 수정:** - `TimeTagMenu`를 호출하는 부분에서 불필요해진 `title` 상태 관리 로직(`var title by remember ...`)을 제거했습니다. - `onTimeTagClick` 람다를 단순화하여 선택된 `timeTag`를 `onTimeTagSelected` 함수에 바로 전달하도록 수정했습니다. --- .../presentation/placeMap/PlaceMapFragment.kt | 11 +++-------- .../placeMap/timeTagSpinner/component/TimeTagMenu.kt | 8 +++++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt index 3505b47..95409af 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt @@ -6,9 +6,6 @@ import android.view.View import androidx.compose.foundation.background import androidx.compose.foundation.layout.padding import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.unit.dp @@ -168,13 +165,11 @@ class PlaceMapFragment( val timeTags by viewModel.timeTags.collectAsStateWithLifecycle(viewLifecycleOwner) if (!timeTags.isEmpty()) { val initialTitle = timeTags.first().name - var title by remember { mutableStateOf(initialTitle) } TimeTagMenu( - title = title, + initialTitle = initialTitle, timeTags = timeTags, - onTimeTagClick = { timeTag -> - title = timeTag.name - onTimeTagSelected(timeTag) + onTimeTagClick = { + onTimeTagSelected(it) }, modifier = Modifier diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt index 652cb8f..4237a51 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt @@ -48,11 +48,12 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun TimeTagMenu( - title: String, + initialTitle: String, timeTags: List, modifier: Modifier = Modifier, onTimeTagClick: (TimeTag) -> Unit = {}, ) { + var title by remember { mutableStateOf(initialTitle) } var expanded by remember { mutableStateOf(false) } var dropdownWidth by remember { mutableStateOf(IntSize.Zero) } val density = LocalDensity.current @@ -99,6 +100,7 @@ fun TimeTagMenu( }, onClick = { scope.launch { + title = item.name onTimeTagClick(item) waitForRipple { expanded = false @@ -168,13 +170,13 @@ private fun TimeTagMenuPreview() { var title by remember { mutableStateOf("1일차 오전") } TimeTagMenu( - title = title, + initialTitle = title, timeTags = timeTags, modifier = Modifier .background(FestabookColor.white) .padding(horizontal = 16.dp), // Festabook Gutter - onTimeTagClick = { title = it.name }, + onTimeTagClick = { }, ) } From 82064c33e1a40ab5b6c8807df7ba1a8b71a5d3b6 Mon Sep 17 00:00:00 2001 From: oungsi2000 Date: Mon, 1 Dec 2025 16:14:52 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refactor(PlaceMap):=20ComposeView=20ID=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 지도 화면(`PlaceMapFragment`)에서 시간 태그 메뉴를 표시하는 `ComposeView`의 ID를 `cv_timeTag`에서 `cv_place_map`으로 변경하고, 관련 코드 구조를 개선했습니다. - **`fragment_place_map.xml` 수정:** - `ComposeView`의 ID를 `cv_timeTag`에서 `cv_place_map`으로 변경하여 이름의 범용성을 높였습니다. - `FragmentContainerView`의 제약 조건이 새 ID를 참조하도록 업데이트했습니다. - **`PlaceMapFragment.kt` 리팩토링:** - `setUpObserver()` 메서드에 있던 `ComposeView` 설정 로직을 `setupComposeView()`라는 별도의 메서드로 분리했습니다. - `onCreateView()`에서 `setupComposeView()`를 호출하도록 추가하여 코드의 역할과 가독성을 개선했습니다. - 변경된 `ComposeView` ID(`cv_place_map`)를 참조하도록 코드를 수정했습니다. --- .../presentation/placeMap/PlaceMapFragment.kt | 41 +++++++++++-------- .../main/res/layout/fragment_place_map.xml | 6 +-- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt index 95409af..097c061 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt @@ -108,6 +108,7 @@ class PlaceMapFragment( } lifecycleScope.launch { setUpMapManager() + setupComposeView() setUpObserver() } binding.logger.log( @@ -159,27 +160,31 @@ class PlaceMapFragment( } } - private fun setUpObserver() { - binding.cvTimeTag.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - binding.cvTimeTag.setContent { - val timeTags by viewModel.timeTags.collectAsStateWithLifecycle(viewLifecycleOwner) - if (!timeTags.isEmpty()) { - val initialTitle = timeTags.first().name - TimeTagMenu( - initialTitle = initialTitle, - timeTags = timeTags, - onTimeTagClick = { - onTimeTagSelected(it) - }, - modifier = - Modifier - .background( - FestabookColor.white, - ).padding(horizontal = 24.dp), - ) + private fun setupComposeView() { + binding.cvPlaceMap.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + val timeTags by viewModel.timeTags.collectAsStateWithLifecycle(viewLifecycleOwner) + if (!timeTags.isEmpty()) { + val initialTitle = timeTags.first().name + TimeTagMenu( + initialTitle = initialTitle, + timeTags = timeTags, + onTimeTagClick = { + onTimeTagSelected(it) + }, + modifier = + Modifier + .background( + FestabookColor.white, + ).padding(horizontal = 24.dp), + ) + } } } + } + private fun setUpObserver() { viewModel.placeGeographies.observe(viewLifecycleOwner) { placeGeographies -> when (placeGeographies) { is PlaceListUiState.Loading -> Unit diff --git a/app/src/main/res/layout/fragment_place_map.xml b/app/src/main/res/layout/fragment_place_map.xml index 047b3fa..c2df411 100644 --- a/app/src/main/res/layout/fragment_place_map.xml +++ b/app/src/main/res/layout/fragment_place_map.xml @@ -6,12 +6,10 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - + app:layout_constraintTop_toBottomOf="@id/cv_place_map" /> Date: Tue, 2 Dec 2025 00:05:43 +0900 Subject: [PATCH 5/8] =?UTF-8?q?refactor(PlaceMap):=20TimeTagSpinnerAdapter?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `TimeTag` 선택 메뉴가 Compose `DropdownMenu`로 교체됨에 따라, 더 이상 사용되지 않는 기존의 `TimeTagSpinnerAdapter` 파일을 삭제했습니다. 이 클래스는 `Spinner`를 커스터마이징하는 데 사용되었습니다. --- .../adapter/TimeTagSpinnerAdapter.kt | 72 ------------------- 1 file changed, 72 deletions(-) delete mode 100644 app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/adapter/TimeTagSpinnerAdapter.kt diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/adapter/TimeTagSpinnerAdapter.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/adapter/TimeTagSpinnerAdapter.kt deleted file mode 100644 index 2d21415..0000000 --- a/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/adapter/TimeTagSpinnerAdapter.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.daedan.festabook.presentation.placeMap.timeTagSpinner.adapter - -import android.content.Context -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ArrayAdapter -import com.daedan.festabook.databinding.ItemSpinnerDropdownBinding -import com.daedan.festabook.databinding.ItemSpinnerSelectedBinding -import com.daedan.festabook.domain.model.TimeTag - -class TimeTagSpinnerAdapter( - context: Context, - private val items: MutableList, -) : ArrayAdapter(context, 0, items) { - override fun getView( - position: Int, - convertView: View?, - parent: ViewGroup, - ): View { - val binding: ItemSpinnerSelectedBinding - val view: View - - if (convertView == null) { - binding = - ItemSpinnerSelectedBinding.inflate( - LayoutInflater.from(context), - parent, - false, - ) - view = binding.root - view.tag = binding - } else { - view = convertView - binding = view.tag as ItemSpinnerSelectedBinding - } - - binding.tvSelectedItem.text = items[position].name - return view - } - - override fun getDropDownView( - position: Int, - convertView: View?, - parent: ViewGroup, - ): View { - val binding: ItemSpinnerDropdownBinding - val view: View - - if (convertView == null) { - binding = - ItemSpinnerDropdownBinding.inflate( - LayoutInflater.from(context), - parent, - false, - ) - view = binding.root - view.tag = binding - } else { - view = convertView - binding = view.tag as ItemSpinnerDropdownBinding - } - - binding.tvDropdownItem.text = items[position].name - return view - } - - fun updateItems(newItems: List) { - items.clear() - items.addAll(newItems) - } -} From 8014dc29b25d818b7ad6523190c90869072e3651 Mon Sep 17 00:00:00 2001 From: oungsi2000 Date: Tue, 2 Dec 2025 13:39:06 +0900 Subject: [PATCH 6/8] =?UTF-8?q?refactor(PlaceMap):=20=EC=A7=80=EB=8F=84=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94?= =?UTF-8?q?=EC=97=90=20FestabookTheme=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 지도 화면(`PlaceMapFragment`)과 시간대 선택 메뉴(`TimeTagMenu`) 컴포저블에 `FestabookTheme`를 적용하고, 디자인 시스템에 정의된 값을 사용하도록 코드를 리팩토링했습니다. - **`PlaceMapFragment.kt`** - `cvPlaceMap`의 `setContent` 블록 전체를 `FestabookTheme`로 감싸 일관된 테마를 적용했습니다. - **`TimeTagMenu.kt`** - `DropdownMenu`의 테두리 모양(`shape`)과 `DropdownMenuItem`의 텍스트 스타일(`style`)에 `festabookShapes`와 `FestabookTypography`를 사용하도록 수정했습니다. - 여백과 간격 등 하드코딩된 `dp` 값을 `festabookSpacing`에 정의된 값으로 대체했습니다. - `@Preview`에서도 `FestabookTheme`를 적용하여 실제 앱 환경과 동일한 디자인을 확인할 수 있도록 개선했습니다. --- .../presentation/placeMap/PlaceMapFragment.kt | 33 +++++++++-------- .../timeTagSpinner/component/TimeTagMenu.kt | 35 ++++++++++--------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt index 097c061..8828f28 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt @@ -39,6 +39,7 @@ import com.daedan.festabook.presentation.placeMap.placeDetailPreview.PlaceDetail import com.daedan.festabook.presentation.placeMap.placeList.PlaceListFragment import com.daedan.festabook.presentation.placeMap.timeTagSpinner.component.TimeTagMenu import com.daedan.festabook.presentation.theme.FestabookColor +import com.daedan.festabook.presentation.theme.FestabookTheme import com.naver.maps.map.MapFragment import com.naver.maps.map.NaverMap import com.naver.maps.map.OnMapReadyCallback @@ -164,21 +165,25 @@ class PlaceMapFragment( binding.cvPlaceMap.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { - val timeTags by viewModel.timeTags.collectAsStateWithLifecycle(viewLifecycleOwner) - if (!timeTags.isEmpty()) { - val initialTitle = timeTags.first().name - TimeTagMenu( - initialTitle = initialTitle, - timeTags = timeTags, - onTimeTagClick = { - onTimeTagSelected(it) - }, - modifier = - Modifier - .background( - FestabookColor.white, - ).padding(horizontal = 24.dp), + FestabookTheme { + val timeTags by viewModel.timeTags.collectAsStateWithLifecycle( + viewLifecycleOwner, ) + if (!timeTags.isEmpty()) { + val initialTitle = timeTags.first().name + TimeTagMenu( + initialTitle = initialTitle, + timeTags = timeTags, + onTimeTagClick = { + onTimeTagSelected(it) + }, + modifier = + Modifier + .background( + FestabookColor.white, + ).padding(horizontal = 24.dp), + ) + } } } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt index 4237a51..71c20bf 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api @@ -41,7 +40,10 @@ import androidx.compose.ui.unit.dp import com.daedan.festabook.R import com.daedan.festabook.domain.model.TimeTag import com.daedan.festabook.presentation.theme.FestabookColor +import com.daedan.festabook.presentation.theme.FestabookTheme import com.daedan.festabook.presentation.theme.FestabookTypography +import com.daedan.festabook.presentation.theme.festabookShapes +import com.daedan.festabook.presentation.theme.festabookSpacing import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -77,7 +79,7 @@ fun TimeTagMenu( DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, - offset = DpOffset(x = 0.dp, y = 8.dp), + offset = DpOffset(x = 0.dp, y = festabookSpacing.paddingBody2), modifier = Modifier .width( @@ -86,16 +88,16 @@ fun TimeTagMenu( .border( width = 2.dp, color = FestabookColor.gray300, - shape = RoundedCornerShape(8.dp), + shape = festabookShapes.radius2, ), - shape = RoundedCornerShape(8.dp), + shape = festabookShapes.radius2, ) { timeTags.forEach { item -> DropdownMenuItem( text = { Text( text = item.name, - fontStyle = FestabookTypography.bodyLarge.fontStyle, + style = FestabookTypography.bodyLarge, ) }, onClick = { @@ -168,15 +170,16 @@ private fun TimeTagMenuPreview() { TimeTag(2, "오후"), ) var title by remember { mutableStateOf("1일차 오전") } - - TimeTagMenu( - initialTitle = title, - timeTags = timeTags, - modifier = - Modifier - .background(FestabookColor.white) - .padding(horizontal = 16.dp), - // Festabook Gutter - onTimeTagClick = { }, - ) + FestabookTheme { + TimeTagMenu( + initialTitle = title, + timeTags = timeTags, + modifier = + Modifier + .background(FestabookColor.white) + .padding(horizontal = festabookSpacing.paddingScreenGutter), + // Festabook Gutter + onTimeTagClick = { }, + ) + } } From 2334452aa437b874f9d770b47679d2d2e56dc232 Mon Sep 17 00:00:00 2001 From: oungsi2000 Date: Wed, 3 Dec 2025 15:26:13 +0900 Subject: [PATCH 7/8] =?UTF-8?q?refactor(PlaceMap):=20title=EC=9D=98=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=A5=BC=20viewModel=EC=9D=98=20selectedTime?= =?UTF-8?q?Tag=EC=99=B8=20=EB=8F=99=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에 `Fragment`에서 콜백 인터페이스(`OnTimeTagSelectedListener`)를 통해 처리하던 시간대(`TimeTag`) 선택 로직을 `ViewModel`이 직접 상태를 관리하고 Compose UI가 이를 구독하는 단방향 데이터 흐름(UDF) 방식으로 리팩토링했습니다. - **`PlaceMapViewModel.kt` 수정:** - `LiveData`로 관리되던 `selectedTimeTag`를 `StateFlow`(`selectedTimeTagFlow`)로 변환하여 Compose 환경에서 더 효율적으로 상태를 관찰할 수 있도록 했습니다. - 시간대 선택 시 장소 선택을 해제하는 로직(`unselectPlace()`)을 `onDaySelected` 메서드 내부로 이동시켜 관련 로직을 통합했습니다. - **`PlaceMapFragment.kt` 리팩토링:** - `OnTimeTagSelectedListener` 인터페이스와 관련 콜백 메서드(`onTimeTagSelected`, `onNothingSelected`)를 제거했습니다. - `TimeTagMenu` 컴포저블의 `onTimeTagClick` 람다 내에서 `ViewModel`의 `onDaySelected`를 직접 호출하도록 변경하여 `Fragment`의 역할을 줄였습니다. - `ViewModel`의 `StateFlow`를 구독하여 `TimeTagMenu`의 제목(`title`)을 동적으로 업데이트하도록 개선했습니다. - **`TimeTagMenu.kt` 수정:** - `initialTitle` 파라미터를 `title`로 변경하고, 컴포저블 내부에서 `title`을 관리하던 `remember` 상태를 제거했습니다. - 이제 `TimeTagMenu`는 외부에서 전달받은 `title`을 그대로 표시하는 상태 비저장(Stateless) 컴포저블로 변경되었습니다. --- .../presentation/placeMap/PlaceMapFragment.kt | 37 +++++++------------ .../placeMap/PlaceMapViewModel.kt | 19 ++++++++-- .../timeTagSpinner/component/TimeTagMenu.kt | 6 +-- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt index 8828f28..773b642 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapFragment.kt @@ -20,7 +20,6 @@ import com.daedan.festabook.R import com.daedan.festabook.databinding.FragmentPlaceMapBinding import com.daedan.festabook.di.fragment.FragmentKey import com.daedan.festabook.di.mapManager.MapManagerGraph -import com.daedan.festabook.domain.model.TimeTag import com.daedan.festabook.logging.logger import com.daedan.festabook.presentation.common.BaseFragment import com.daedan.festabook.presentation.common.OnMenuItemReClickListener @@ -65,8 +64,7 @@ class PlaceMapFragment( placeDetailPreviewSecondaryFragment: PlaceDetailPreviewSecondaryFragment, mapFragment: MapFragment, ) : BaseFragment(), - OnMenuItemReClickListener, - OnTimeTagSelectedListener { + OnMenuItemReClickListener { override val layoutId: Int = R.layout.fragment_place_map @Inject @@ -132,19 +130,6 @@ class PlaceMapFragment( mapManager?.moveToPosition() } - override fun onTimeTagSelected(item: TimeTag) { - viewModel.unselectPlace() - viewModel.onDaySelected(item) - binding.logger.log( - PlaceTimeTagSelected( - baseLogData = binding.logger.getBaseLogData(), - timeTagName = item.name, - ), - ) - } - - override fun onNothingSelected() = Unit - private suspend fun setUpMapManager() { naverMap = mapFragment.getMap() naverMap.addOnLocationChangeListener { @@ -166,16 +151,20 @@ class PlaceMapFragment( setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { FestabookTheme { - val timeTags by viewModel.timeTags.collectAsStateWithLifecycle( - viewLifecycleOwner, - ) - if (!timeTags.isEmpty()) { - val initialTitle = timeTags.first().name + val timeTags by viewModel.timeTags.collectAsStateWithLifecycle() + val title by viewModel.selectedTimeTagFlow.collectAsStateWithLifecycle() + if (timeTags.isNotEmpty()) { TimeTagMenu( - initialTitle = initialTitle, + title = title.name, timeTags = timeTags, - onTimeTagClick = { - onTimeTagSelected(it) + onTimeTagClick = { timeTag -> + viewModel.onDaySelected(timeTag) + binding.logger.log( + PlaceTimeTagSelected( + baseLogData = binding.logger.getBaseLogData(), + timeTagName = timeTag.name, + ), + ) }, modifier = Modifier diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapViewModel.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapViewModel.kt index 57e18f7..0cf4241 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapViewModel.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/placeMap/PlaceMapViewModel.kt @@ -3,6 +3,7 @@ package com.daedan.festabook.presentation.placeMap import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import com.daedan.festabook.di.viewmodel.ViewModelKey import com.daedan.festabook.domain.model.TimeTag @@ -22,8 +23,10 @@ import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoMap import dev.zacsweers.metro.Inject import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @ContributesIntoMap(AppScope::class) @@ -47,6 +50,13 @@ class PlaceMapViewModel @Inject constructor( private val _selectedTimeTag = MutableLiveData() val selectedTimeTag: LiveData = _selectedTimeTag + // 임시 StateFlow + val selectedTimeTagFlow: StateFlow = + _selectedTimeTag.asFlow().stateIn( + scope = viewModelScope, + started = SharingStarted.Lazily, + initialValue = TimeTag.EMPTY, + ) private val _selectedPlace: MutableLiveData = MutableLiveData() val selectedPlace: LiveData = _selectedPlace @@ -80,16 +90,17 @@ class PlaceMapViewModel @Inject constructor( _timeTags.value = emptyList() } - // 기본 선택값 - if (!timeTags.value.isNullOrEmpty()) { - _selectedTimeTag.value = _timeTags.value?.first() + // 기본 선택값 + if (!timeTags.value.isEmpty()) { + _selectedTimeTag.value = _timeTags.value.first() } else { - _selectedTimeTag.value = TimeTag.Companion.EMPTY + _selectedTimeTag.value = TimeTag.EMPTY } } } fun onDaySelected(item: TimeTag) { + unselectPlace() _selectedTimeTag.value = item } diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt index 71c20bf..a0cf67f 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt @@ -50,12 +50,11 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun TimeTagMenu( - initialTitle: String, + title: String, timeTags: List, modifier: Modifier = Modifier, onTimeTagClick: (TimeTag) -> Unit = {}, ) { - var title by remember { mutableStateOf(initialTitle) } var expanded by remember { mutableStateOf(false) } var dropdownWidth by remember { mutableStateOf(IntSize.Zero) } val density = LocalDensity.current @@ -102,7 +101,6 @@ fun TimeTagMenu( }, onClick = { scope.launch { - title = item.name onTimeTagClick(item) waitForRipple { expanded = false @@ -172,7 +170,7 @@ private fun TimeTagMenuPreview() { var title by remember { mutableStateOf("1일차 오전") } FestabookTheme { TimeTagMenu( - initialTitle = title, + title = title, timeTags = timeTags, modifier = Modifier From 8c3e41c479127299c672f597698f8720c8166b4b Mon Sep 17 00:00:00 2001 From: oungsi2000 Date: Wed, 3 Dec 2025 15:38:32 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refactor(PlaceMap):=20cardBackground=20Modi?= =?UTF-8?q?fier=20=EC=82=AC=EC=9A=A9,=20=EB=B0=8F=20typhography=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 시간대 선택 메뉴(`TimeTagMenu.kt`)의 코드를 리팩토링하고 디자인 시스템을 적용하여 재사용성을 높이고 일관성을 개선했습니다. - **`TimeTagMenu.kt` 수정:** - `cardBackground` 커스텀 Modifier를 사용하여 배경색, 테두리, 모양을 한 번에 설정하도록 코드를 간소화했습니다. - 기존 `FestabookTypography` 대신 `MaterialTheme.typography`를 사용하도록 변경하여 테마 시스템과의 일관성을 강화했습니다. - 하드코딩된 아이콘의 `contentDescription`을 `stringResource`를 사용하도록 수정하여 다국어 지원 및 접근성을 개선했습니다. - 더 이상 사용하지 않는 `border` import를 제거했습니다. --- .../timeTagSpinner/component/TimeTagMenu.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt b/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt index a0cf67f..657db70 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/placeMap/timeTagSpinner/component/TimeTagMenu.kt @@ -1,7 +1,6 @@ package com.daedan.festabook.presentation.placeMap.timeTagSpinner.component import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -17,6 +16,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuBoxScope import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults @@ -33,15 +33,16 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.daedan.festabook.R import com.daedan.festabook.domain.model.TimeTag +import com.daedan.festabook.presentation.common.component.cardBackground import com.daedan.festabook.presentation.theme.FestabookColor import com.daedan.festabook.presentation.theme.FestabookTheme -import com.daedan.festabook.presentation.theme.FestabookTypography import com.daedan.festabook.presentation.theme.festabookShapes import com.daedan.festabook.presentation.theme.festabookSpacing import kotlinx.coroutines.delay @@ -83,10 +84,10 @@ fun TimeTagMenu( Modifier .width( with(density) { dropdownWidth.width.toDp() }, - ).background(color = FestabookColor.white) - .border( - width = 2.dp, - color = FestabookColor.gray300, + ).cardBackground( + backgroundColor = FestabookColor.white, + borderStroke = 2.dp, + borderColor = FestabookColor.gray300, shape = festabookShapes.radius2, ), shape = festabookShapes.radius2, @@ -96,7 +97,7 @@ fun TimeTagMenu( text = { Text( text = item.name, - style = FestabookTypography.bodyLarge, + style = MaterialTheme.typography.bodyLarge, ) }, onClick = { @@ -141,12 +142,12 @@ private fun ExposedDropdownMenuBoxScope.TimeTagButton( ) { Text( text = title, - style = FestabookTypography.displaySmall, + style = MaterialTheme.typography.displaySmall, ) Icon( painter = painterResource(id = R.drawable.ic_chevron_down), - contentDescription = "드롭다운", + contentDescription = stringResource(R.string.chevron_down), ) } }