diff --git a/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/Constant.kt b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/Constant.kt new file mode 100644 index 0000000000..9c2a16314a --- /dev/null +++ b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/Constant.kt @@ -0,0 +1,3 @@ +package `in`.koreatech.koin.feature.callvan + +const val TEXT_FIELD_MAX_LENGTH = 255 diff --git a/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/enums/CallvanFilterType.kt b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/enums/CallvanFilterType.kt new file mode 100644 index 0000000000..380c972027 --- /dev/null +++ b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/enums/CallvanFilterType.kt @@ -0,0 +1,115 @@ +package `in`.koreatech.koin.feature.callvan.enums + +import android.os.Parcelable +import androidx.annotation.StringRes +import `in`.koreatech.koin.feature.callvan.R +import kotlinx.parcelize.Parcelize + +@Parcelize +sealed class CallvanFilterType( + @StringRes open val stringRes: Int, + open val value: String? +) : Parcelable { + + @Parcelize + sealed class DeparturesFilterType( + @StringRes override val stringRes: Int, + override val value: String? + ) : CallvanFilterType(stringRes, value) { + @Parcelize + object All : DeparturesFilterType(R.string.filter_list_all, null) + + @Parcelize + object FrontGate : DeparturesFilterType(R.string.filter_list_front_gate, "FRONT_GATE") + + @Parcelize + object BackGate : DeparturesFilterType(R.string.filter_list_back_gate, "BACK_GATE") + + @Parcelize + object TennisCourt : DeparturesFilterType(R.string.filter_list_tennis_court, "TENNIS_COURT") + + @Parcelize + object DormitoryMain : DeparturesFilterType(R.string.filter_list_dormitory_main, "DORMITORY_MAIN") + + @Parcelize + object DormitorySub : DeparturesFilterType(R.string.filter_list_dormitory_sub, "DORMITORY_SUB") + + @Parcelize + object Terminal : DeparturesFilterType(R.string.filter_list_terminal, "TERMINAL") + + @Parcelize + object Station : DeparturesFilterType(R.string.filter_list_station, "STATION") + + @Parcelize + object AsanStation : DeparturesFilterType(R.string.filter_list_asan_station, "ASAN_STATION") + } + + @Parcelize + sealed class ArrivalsFilterType( + @StringRes override val stringRes: Int, + override val value: String? + ) : CallvanFilterType(stringRes, value) { + @Parcelize + object All : ArrivalsFilterType(R.string.filter_list_all, null) + + @Parcelize + object FrontGate : ArrivalsFilterType(R.string.filter_list_front_gate, "FRONT_GATE") + + @Parcelize + object BackGate : ArrivalsFilterType(R.string.filter_list_back_gate, "BACK_GATE") + + @Parcelize + object TennisCourt : ArrivalsFilterType(R.string.filter_list_tennis_court, "TENNIS_COURT") + + @Parcelize + object DormitoryMain : ArrivalsFilterType(R.string.filter_list_dormitory_main, "DORMITORY_MAIN") + + @Parcelize + object DormitorySub : ArrivalsFilterType(R.string.filter_list_dormitory_sub, "DORMITORY_SUB") + + @Parcelize + object Terminal : ArrivalsFilterType(R.string.filter_list_terminal, "TERMINAL") + + @Parcelize + object Station : ArrivalsFilterType(R.string.filter_list_station, "STATION") + + @Parcelize + object AsanStation : ArrivalsFilterType(R.string.filter_list_asan_station, "ASAN_STATION") + } + + @Parcelize + sealed class StatusesType( + @StringRes override val stringRes: Int, + override val value: String? + ) : CallvanFilterType(stringRes, value) { + @Parcelize + object All : StatusesType(R.string.filter_list_all, null) + + @Parcelize + object Recruiting : StatusesType(R.string.filter_list_recruiting, "RECRUITING") + + @Parcelize + object Closed : StatusesType(R.string.filter_list_closed, "CLOSED") + + @Parcelize + object Completed : StatusesType(R.string.filter_list_completed, "COMPLETED") + } + + @Parcelize + sealed class SortType( + @StringRes override val stringRes: Int, + override val value: String? + ) : CallvanFilterType(stringRes, value) { + @Parcelize + object LatestDesc : SortType(R.string.filter_list_latest_desc, "LATEST_DESC") + + @Parcelize + object LatestAsc : SortType(R.string.filter_list_latest_asc, "LATEST_ASC") + + @Parcelize + object DepartureDesc : SortType(R.string.filter_list_departure_desc, "DEPARTURE_DESC") + + @Parcelize + object DepartureAsc : SortType(R.string.filter_list_departure_asc, "DEPARTURE_ASC") + } +} diff --git a/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/FilterBottomSheetActions.kt b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/FilterBottomSheetActions.kt new file mode 100644 index 0000000000..0c718bd1d6 --- /dev/null +++ b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/FilterBottomSheetActions.kt @@ -0,0 +1,15 @@ +package `in`.koreatech.koin.feature.callvan.ui.list + +import androidx.compose.runtime.Stable +import `in`.koreatech.koin.feature.callvan.enums.CallvanFilterType +import kotlinx.collections.immutable.ImmutableList + +@Stable +data class FilterBottomSheetActions( + val onSortTypeChange: (CallvanFilterType.SortType) -> Unit, + val onStatusesTypeChange: (CallvanFilterType.StatusesType) -> Unit, + val onDeparturesTypeChange: (ImmutableList) -> Unit, + val onArrivalsTypeChange: (ImmutableList) -> Unit, + val onReset: () -> Unit, + val onApplyClick: () -> Unit +) diff --git a/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/FilterBottomSheetState.kt b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/FilterBottomSheetState.kt new file mode 100644 index 0000000000..1ad3ddc865 --- /dev/null +++ b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/FilterBottomSheetState.kt @@ -0,0 +1,17 @@ +package `in`.koreatech.koin.feature.callvan.ui.list + +import androidx.compose.runtime.Immutable +import `in`.koreatech.koin.feature.callvan.enums.CallvanFilterType.ArrivalsFilterType +import `in`.koreatech.koin.feature.callvan.enums.CallvanFilterType.DeparturesFilterType +import `in`.koreatech.koin.feature.callvan.enums.CallvanFilterType.SortType +import `in`.koreatech.koin.feature.callvan.enums.CallvanFilterType.StatusesType +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@Immutable +data class FilterBottomSheetState( + val selectedSortType: SortType = SortType.LatestDesc, + val selectedStatusesType: StatusesType = StatusesType.All, + val selectedDeparturesType: ImmutableList = persistentListOf(DeparturesFilterType.All), + val selectedArrivalsType: ImmutableList = persistentListOf(ArrivalsFilterType.All) +) diff --git a/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/component/CallvanFilterChip.kt b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/component/CallvanFilterChip.kt new file mode 100644 index 0000000000..19fbb52015 --- /dev/null +++ b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/component/CallvanFilterChip.kt @@ -0,0 +1,61 @@ +package `in`.koreatech.koin.feature.callvan.ui.list.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.core.designsystem.theme.KoinTheme +import `in`.koreatech.koin.core.designsystem.theme.RebrandKoinTheme +import `in`.koreatech.koin.feature.callvan.R + +@Composable +fun CallvanFilterChip(onClick: () -> Unit) { + Surface( + onClick = { + onClick() + }, + modifier = Modifier + .height(34.dp), + shape = RoundedCornerShape(24.dp), + color = RebrandKoinTheme.colors.primary100, + contentColor = RebrandKoinTheme.colors.primary900 + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp) + + ) { + Text( + text = stringResource(R.string.filter_container), + style = KoinTheme.typography.bold14 + ) + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_list_filter), + contentDescription = "", + modifier = Modifier.size(16.dp) + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun CallvanFilterChipPreview() { + KoinTheme { + CallvanFilterChip(onClick = {}) + } +} diff --git a/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/component/FilterBottomSheet.kt b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/component/FilterBottomSheet.kt new file mode 100644 index 0000000000..8c16da4e0a --- /dev/null +++ b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/component/FilterBottomSheet.kt @@ -0,0 +1,316 @@ +package `in`.koreatech.koin.feature.callvan.ui.list.component + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +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.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.core.designsystem.theme.KoinTheme +import `in`.koreatech.koin.core.designsystem.theme.RebrandKoinTheme +import `in`.koreatech.koin.feature.callvan.R +import `in`.koreatech.koin.feature.callvan.enums.CallvanFilterType +import `in`.koreatech.koin.feature.callvan.enums.CallvanFilterType.ArrivalsFilterType +import `in`.koreatech.koin.feature.callvan.enums.CallvanFilterType.DeparturesFilterType +import `in`.koreatech.koin.feature.callvan.enums.CallvanFilterType.SortType +import `in`.koreatech.koin.feature.callvan.enums.CallvanFilterType.StatusesType +import `in`.koreatech.koin.feature.callvan.ui.component.CallvanBottomSheet +import `in`.koreatech.koin.feature.callvan.ui.list.FilterBottomSheetActions +import `in`.koreatech.koin.feature.callvan.ui.list.FilterBottomSheetState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FilterBottomSheet( + onDismissRequest: () -> Unit, + selectedSortType: SortType, + selectedStatusesType: StatusesType, + selectedArrivalsType: ImmutableList, + selectedDeparturesType: ImmutableList, + onApply: (SortType, StatusesType, ImmutableList, ImmutableList) -> Unit +) { + var currentSortType by remember { mutableStateOf(selectedSortType) } + var currentStatusesType by remember { mutableStateOf(selectedStatusesType) } + var currentArrivalsType by remember { mutableStateOf(selectedArrivalsType) } + var currentDeparturesType by remember { mutableStateOf(selectedDeparturesType) } + + CallvanBottomSheet( + title = stringResource(R.string.filter_container), + onDismiss = onDismissRequest, + showCloseButton = true + ) { + FilterBottomSheetContent( + state = FilterBottomSheetState( + selectedSortType = currentSortType, + selectedStatusesType = currentStatusesType, + selectedDeparturesType = currentDeparturesType, + selectedArrivalsType = currentArrivalsType + ), + actions = FilterBottomSheetActions( + onSortTypeChange = { currentSortType = it }, + onStatusesTypeChange = { currentStatusesType = it }, + onArrivalsTypeChange = { currentArrivalsType = it }, + onDeparturesTypeChange = { currentDeparturesType = it }, + onReset = { + currentSortType = SortType.LatestDesc + currentStatusesType = StatusesType.All + currentDeparturesType = persistentListOf(DeparturesFilterType.All) + currentArrivalsType = persistentListOf(ArrivalsFilterType.All) + }, + onApplyClick = { + onApply( + currentSortType, + currentStatusesType, + currentDeparturesType, + currentArrivalsType + ) + onDismissRequest() + } + ) + ) + } +} + +@Composable +fun FilterBottomSheetContent( + state: FilterBottomSheetState, + actions: FilterBottomSheetActions +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 20.dp) + ) { + val scrollState = rememberScrollState() + Column( + modifier = Modifier + .weight(1f, fill = false) + .verticalScroll(scrollState) + .padding(top = 12.dp, start = 32.dp, bottom = 12.dp, end = 12.dp) + ) { + FilterSection( + title = stringResource(R.string.filter_list_sort_order), + items = persistentListOf( + SortType.LatestDesc, + SortType.LatestAsc, + SortType.DepartureDesc, + SortType.DepartureAsc + ), + selectedItem = state.selectedSortType, + onItemSelected = actions.onSortTypeChange + ) + HorizontalDivider(color = KoinTheme.colors.neutral300) + FilterSection( + title = stringResource(R.string.filter_list_recruitment_status), + items = persistentListOf( + StatusesType.All, + StatusesType.Recruiting, + StatusesType.Closed, + StatusesType.Completed + ), + selectedItem = state.selectedStatusesType, + onItemSelected = actions.onStatusesTypeChange + ) + HorizontalDivider(color = KoinTheme.colors.neutral300) + FilterDuplicateSection( + title = stringResource(R.string.filter_list_origin), + items = persistentListOf( + DeparturesFilterType.All, DeparturesFilterType.FrontGate, + DeparturesFilterType.BackGate, DeparturesFilterType.TennisCourt, + DeparturesFilterType.DormitoryMain, DeparturesFilterType.DormitorySub, + DeparturesFilterType.Terminal, DeparturesFilterType.Station, + DeparturesFilterType.AsanStation + ), + selectedItems = state.selectedDeparturesType, + allItem = DeparturesFilterType.All, + onItemSelected = actions.onDeparturesTypeChange + ) + HorizontalDivider(color = KoinTheme.colors.neutral300) + FilterDuplicateSection( + title = stringResource(R.string.filter_list_destination), + items = persistentListOf( + ArrivalsFilterType.All, ArrivalsFilterType.FrontGate, + ArrivalsFilterType.BackGate, ArrivalsFilterType.TennisCourt, + ArrivalsFilterType.DormitoryMain, ArrivalsFilterType.DormitorySub, + ArrivalsFilterType.Terminal, ArrivalsFilterType.Station, + ArrivalsFilterType.AsanStation + ), + selectedItems = state.selectedArrivalsType, + allItem = ArrivalsFilterType.All, + onItemSelected = actions.onArrivalsTypeChange + ) + HorizontalDivider(color = KoinTheme.colors.neutral300) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 12.dp, start = 32.dp, bottom = 12.dp, end = 12.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + OutlinedButton( + onClick = actions.onReset, + shape = KoinTheme.shapes.medium, + border = BorderStroke(1.dp, KoinTheme.colors.neutral300), + modifier = Modifier.weight(1f).height(48.dp), + colors = ButtonDefaults.outlinedButtonColors(containerColor = KoinTheme.colors.neutral0) + ) { + Text( + text = stringResource(R.string.filter_list_reset), + color = KoinTheme.colors.neutral600, + style = KoinTheme.typography.bold16 + ) + Spacer(modifier = Modifier.width(4.dp)) + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_process), + contentDescription = null, + modifier = Modifier.size(16.dp), + tint = KoinTheme.colors.neutral500 + ) + } + Button( + onClick = actions.onApplyClick, + shape = KoinTheme.shapes.medium, + colors = ButtonDefaults.buttonColors(containerColor = RebrandKoinTheme.colors.primary500), + modifier = Modifier.weight(2f).height(48.dp) + ) { + Text( + text = stringResource(R.string.filter_list_adapt), + color = KoinTheme.colors.neutral0, + style = KoinTheme.typography.bold16 + ) + } + } + } +} + +@Composable +fun FilterSection( + title: String, + items: ImmutableList, + selectedItem: T, + modifier: Modifier = Modifier, + onItemSelected: (T) -> Unit +) { + Column(modifier = modifier.padding(vertical = 12.dp)) { + Text( + text = title, + style = KoinTheme.typography.bold16, + color = KoinTheme.colors.neutral800, + modifier = Modifier.padding(bottom = 12.dp) + ) + FlowRow( + modifier = Modifier.fillMaxWidth(), + maxItemsInEachRow = 4 + ) { + items.forEach { item -> + FilterBottomSheetItem( + text = stringResource(item.stringRes), + isSelected = item == selectedItem, + onClick = { onItemSelected(item) } + ) + } + } + } +} + +@Composable +fun FilterDuplicateSection( + title: String, + items: ImmutableList, + selectedItems: ImmutableList, + allItem: T, + modifier: Modifier = Modifier, + onItemSelected: (ImmutableList) -> Unit +) { + Column(modifier = modifier.padding(vertical = 12.dp)) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = title, + style = KoinTheme.typography.bold16, + color = KoinTheme.colors.neutral800 + ) + Text( + text = stringResource(R.string.filter_list_other_place_hint), + style = KoinTheme.typography.regular12, + color = KoinTheme.colors.neutral500 + ) + } + FlowRow( + modifier = Modifier.fillMaxWidth(), + maxItemsInEachRow = 5 + ) { + items.forEach { item -> + FilterBottomSheetItem( + text = stringResource(item.stringRes), + isSelected = item in selectedItems, + onClick = { + val newSelection = when (item) { + allItem -> persistentListOf(allItem) + in selectedItems -> { + val removed = selectedItems.filter { it != item }.toPersistentList() + removed.ifEmpty { persistentListOf(allItem) } + } + else -> (selectedItems.filter { it != allItem } + item).toPersistentList() + } + onItemSelected(newSelection) + } + ) + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun FilterBottomSheetContentPreview() { + RebrandKoinTheme { + FilterBottomSheetContent( + state = FilterBottomSheetState( + selectedSortType = SortType.LatestDesc, + selectedStatusesType = StatusesType.All, + selectedDeparturesType = persistentListOf(DeparturesFilterType.All), + selectedArrivalsType = persistentListOf(ArrivalsFilterType.All) + ), + actions = FilterBottomSheetActions( + onSortTypeChange = {}, + onStatusesTypeChange = {}, + onDeparturesTypeChange = {}, + onArrivalsTypeChange = {}, + onReset = {}, + onApplyClick = {} + ) + ) + } +} diff --git a/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/component/FilterBottomSheetItem.kt b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/component/FilterBottomSheetItem.kt new file mode 100644 index 0000000000..2592a34b3b --- /dev/null +++ b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/component/FilterBottomSheetItem.kt @@ -0,0 +1,47 @@ +package `in`.koreatech.koin.feature.callvan.ui.list.component + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.selected +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.core.designsystem.theme.KoinTheme +import `in`.koreatech.koin.core.designsystem.theme.RebrandKoinTheme + +@Composable +fun FilterBottomSheetItem( + text: String, + isSelected: Boolean, + onClick: () -> Unit +) { + Surface( + onClick = onClick, + shape = RoundedCornerShape(24.dp), + border = BorderStroke( + width = 1.dp, + color = if (isSelected) RebrandKoinTheme.colors.primary500 else KoinTheme.colors.neutral300 + ), + color = KoinTheme.colors.neutral0, + modifier = Modifier + .padding(end = 8.dp) + .semantics { selected = isSelected } + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.padding(vertical = 8.dp, horizontal = 12.dp) + ) { + Text( + text = text, + color = if (isSelected) RebrandKoinTheme.colors.primary500 else KoinTheme.colors.neutral500, + style = KoinTheme.typography.bold14 + ) + } + } +} diff --git a/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/component/ItemSearchTextField.kt b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/component/ItemSearchTextField.kt new file mode 100644 index 0000000000..5aecf7c8ce --- /dev/null +++ b/feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/list/component/ItemSearchTextField.kt @@ -0,0 +1,77 @@ +package `in`.koreatech.koin.feature.callvan.ui.list.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.core.designsystem.theme.KoinTheme +import `in`.koreatech.koin.feature.callvan.R +import `in`.koreatech.koin.feature.callvan.TEXT_FIELD_MAX_LENGTH + +@Composable +fun ItemSearchTextField( + value: String, + modifier: Modifier = Modifier, + maxLength: Int = TEXT_FIELD_MAX_LENGTH, + hint: String = stringResource(R.string.callvan_search_hint), + textStyle: TextStyle = KoinTheme.typography.regular14.copy(color = KoinTheme.colors.neutral600), + onValueChange: (String) -> Unit = {} +) { + val safeMaxLength = maxLength.coerceAtLeast(0) + Row( + modifier = modifier + .fillMaxWidth() + .background( + color = KoinTheme.colors.neutral100, + shape = KoinTheme.shapes.extraSmall + ) + .padding(vertical = 8.dp, horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + BasicTextField( + modifier = Modifier.weight(1f), + value = value, + textStyle = textStyle, + singleLine = true, + onValueChange = { onValueChange(it.take(safeMaxLength)) }, + decorationBox = { innerTextField -> + Box { + if (value.isEmpty()) { + Text( + text = hint, + color = KoinTheme.colors.neutral600 + ) + } + innerTextField() + } + } + ) + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_search_vector), + contentDescription = null + ) + } +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) +@Composable +private fun ItemSearchTextFieldPreview() { + ItemSearchTextField( + value = "", + hint = "검색어를 입력해주세요.", + onValueChange = {} + ) +} diff --git a/feature/callvan/src/main/res/drawable/ic_list_filter.xml b/feature/callvan/src/main/res/drawable/ic_list_filter.xml new file mode 100644 index 0000000000..91115f4a85 --- /dev/null +++ b/feature/callvan/src/main/res/drawable/ic_list_filter.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/feature/callvan/src/main/res/drawable/ic_process.xml b/feature/callvan/src/main/res/drawable/ic_process.xml new file mode 100644 index 0000000000..304d0fe774 --- /dev/null +++ b/feature/callvan/src/main/res/drawable/ic_process.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/feature/callvan/src/main/res/drawable/ic_search_vector.xml b/feature/callvan/src/main/res/drawable/ic_search_vector.xml new file mode 100644 index 0000000000..eaa922c07f --- /dev/null +++ b/feature/callvan/src/main/res/drawable/ic_search_vector.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/feature/callvan/src/main/res/values/strings.xml b/feature/callvan/src/main/res/values/strings.xml index 11d4ba9a4c..17e6f5b91b 100644 --- a/feature/callvan/src/main/res/values/strings.xml +++ b/feature/callvan/src/main/res/values/strings.xml @@ -53,6 +53,32 @@ 욕설 욕설, 성적인 언어, 비방하는 언어를 사용했습니다. 기타 + + 필터 + 초기화 + 적용하기 + 정렬 + 모집 상태 + 출발지 + 도착지 + 기타 장소는 검색창을 이용해주세요. + 전체 + 정문 + 후문 + 테니스장 + 본관동 + 별관동 + 천안터미널 + 천안역 + 천안아산역 + 모집중 + 모집마감 + 모집완료 + 게시글 등록순 (내림차순) + 게시글 등록순 (오름차순) + 출발일 (내림차순) + 출발일 (오름차순) + 검색어를 입력해주세요. 사용자가 신고되었습니다. 이미지 업로드에 실패했습니다.