diff --git a/domain/src/main/java/com/project200/domain/model/FilterModel.kt b/domain/src/main/java/com/project200/domain/model/FilterModel.kt new file mode 100644 index 00000000..282967b5 --- /dev/null +++ b/domain/src/main/java/com/project200/domain/model/FilterModel.kt @@ -0,0 +1,43 @@ +package com.project200.domain.model + +enum class Gender(val code: String) { + MALE("M"), + FEMALE("F") +} + +enum class AgeGroup(val code: String) { + TEEN("10"), + TWENTIES("20"), + THIRTIES("30"), + FORTIES("40"), + FIFTIES("50"), + SIXTIES_PLUS("60") +} + +enum class DayOfWeek(val index: Int) { + MONDAY(0), + TUESDAY(1), + WEDNESDAY(2), + THURSDAY(3), + FRIDAY(4), + SATURDAY(5), + SUNDAY(6) +} + + +enum class SkillLevel(val code: String) { + NOVICE("NOVICE"), + BEGINNER("BEGINNER"), + INTERMEDIATE("INTERMEDIATE"), + ADVANCED("ADVANCED"), + EXPERT("EXPERT"), + PROFESSIONAL("PROFESSIONAL") +} + + +enum class ExerciseScore(val minScore: Int) { + OVER_20(20), + OVER_40(40), + OVER_60(60), + OVER_80(80) +} \ No newline at end of file diff --git a/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapFragment.kt b/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapFragment.kt index 7038e326..82bebe33 100644 --- a/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapFragment.kt +++ b/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapFragment.kt @@ -27,6 +27,10 @@ import com.project200.domain.model.MapPosition import com.project200.domain.model.MatchingMember import com.project200.feature.matching.map.cluster.ClusterCalculator import com.project200.feature.matching.map.cluster.MapClusterItem +import com.project200.feature.matching.map.filter.FilterBottomSheetDialog +import com.project200.feature.matching.map.filter.MatchingFilterRVAdapter +import com.project200.feature.matching.utils.FilterUiMapper +import com.project200.feature.matching.utils.MatchingFilterType import com.project200.presentation.base.BindingFragment import com.project200.undabang.feature.matching.R import com.project200.undabang.feature.matching.databinding.FragmentMatchingMapBinding @@ -43,6 +47,17 @@ class MatchingMapFragment : private lateinit var fusedLocationClient: FusedLocationProviderClient private val viewModel: MatchingMapViewModel by viewModels() + private val filterAdapter by lazy { + MatchingFilterRVAdapter( + onFilterClick = { type -> + viewModel.onFilterTypeClicked(type) + }, + onClearClick = { + viewModel.clearFilters() + }, + ) + } + private var isMapInitialized: Boolean = false // 클러스터링 계산을 위한 헬퍼 클래스 @@ -69,6 +84,8 @@ class MatchingMapFragment : initMapView() initListeners() + binding.matchingFilterRv.adapter = filterAdapter + filterAdapter.submitFilterList(MatchingFilterType.entries) } private fun initMapView() { @@ -157,6 +174,16 @@ class MatchingMapFragment : Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() } } + launch { + viewModel.filterState.collect { state -> + filterAdapter.submitFilterState(state) + } + } + launch { + viewModel.currentFilterType.collect { type -> + showFilterBottomSheet(type) + } + } } } } @@ -252,6 +279,24 @@ class MatchingMapFragment : bottomSheet.show(parentFragmentManager, MembersBottomSheetDialog::class.java.simpleName) } + private fun showFilterBottomSheet(type: MatchingFilterType) { + // 필터 옵션들을 UI 모델로 매핑 + val options = + FilterUiMapper.mapToUiModels( + type = type, + currentState = viewModel.filterState.value, + ) + + val bottomSheet = + FilterBottomSheetDialog( + filterType = type, + onOptionSelected = { selectedDomainData -> + viewModel.onFilterOptionSelected(type, selectedDomainData) + }, + ) + bottomSheet.show(childFragmentManager, FilterBottomSheetDialog::class.java.simpleName) + } + private fun checkPermissionAndMove() { if (isLocationPermissionGranted()) { moveToCurrentLocation() diff --git a/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapViewModel.kt b/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapViewModel.kt index 5e0d0331..d192d6e0 100644 --- a/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapViewModel.kt +++ b/feature/matching/src/main/java/com/project200/feature/matching/map/MatchingMapViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.project200.domain.model.BaseResult +import com.project200.domain.model.DayOfWeek import com.project200.domain.model.ExercisePlace import com.project200.domain.model.MapPosition import com.project200.domain.model.MatchingMember @@ -12,14 +13,18 @@ import com.project200.domain.usecase.GetExercisePlaceUseCase import com.project200.domain.usecase.GetLastMapPositionUseCase import com.project200.domain.usecase.GetMatchingMembersUseCase import com.project200.domain.usecase.SaveLastMapPositionUseCase +import com.project200.feature.matching.utils.FilterState +import com.project200.feature.matching.utils.MatchingFilterType import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @@ -43,6 +48,14 @@ class MatchingMapViewModel private val _errorEvents = MutableSharedFlow() val errorEvents: SharedFlow = _errorEvents + // 필터 상태 + private val _filterState = MutableStateFlow(FilterState()) + val filterState: StateFlow = _filterState.asStateFlow() + + // 현재 선택된 필터 타입 + private val _currentFilterType = MutableSharedFlow() + val currentFilterType: SharedFlow = _currentFilterType + val combinedMapData: StateFlow, List>> = combine( matchingMembers, @@ -84,6 +97,8 @@ class MatchingMapViewModel */ fun fetchMatchingMembers() { viewModelScope.launch { + val filters = _filterState.value + // useCase 호출 시 filters.gender, filters.ageGroup 등을 전달 matchingMembers.value = getMatchingMembersUseCase() } } @@ -140,7 +155,56 @@ class MatchingMapViewModel } } - companion object { - const val NO_URL = "404" + // 필터 버튼 클릭 시 호출 + fun onFilterTypeClicked(type: MatchingFilterType) { + viewModelScope.launch { + _currentFilterType.emit(type) + } + } + + /** + * 필터 초기화 + */ + fun clearFilters() { + _filterState.value = FilterState() + fetchMatchingMembers() + } + + /** + * 필터 옵션 선택 시 호출 + */ + fun onFilterOptionSelected( + type: MatchingFilterType, + option: Any?, + ) { + _filterState.update { current -> + when (type) { + MatchingFilterType.GENDER -> current.copy(gender = toggle(current.gender, option)) + MatchingFilterType.AGE -> current.copy(ageGroup = toggle(current.ageGroup, option)) + MatchingFilterType.SKILL -> current.copy(skillLevel = toggle(current.skillLevel, option)) + MatchingFilterType.SCORE -> current.copy(exerciseScore = toggle(current.exerciseScore, option)) + MatchingFilterType.DAY -> { + val newDays = + if (option == null) { + // 전체 선택 시 모두 비움 (Empty == 전체) + emptySet() + } else { + val day = option as DayOfWeek + // 요일 토글 + if (day in current.days) current.days - day else current.days + day + } + current.copy(days = newDays) + } + } + } + fetchMatchingMembers() + } + + private fun toggle( + current: T?, + selected: Any?, + ): T? { + val selectedCasted = selected as T + return if (current == selectedCasted) null else selectedCasted } } diff --git a/feature/matching/src/main/java/com/project200/feature/matching/map/filter/FilterBottomSheetDialog.kt b/feature/matching/src/main/java/com/project200/feature/matching/map/filter/FilterBottomSheetDialog.kt new file mode 100644 index 00000000..62f8f918 --- /dev/null +++ b/feature/matching/src/main/java/com/project200/feature/matching/map/filter/FilterBottomSheetDialog.kt @@ -0,0 +1,73 @@ +package com.project200.feature.matching.map.filter + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.project200.feature.matching.map.MatchingMapViewModel +import com.project200.feature.matching.utils.FilterUiMapper +import com.project200.feature.matching.utils.MatchingFilterType +import com.project200.undabang.feature.matching.databinding.DialogFilterBottomSheetBinding +import com.project200.undabang.presentation.R +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch + +@AndroidEntryPoint +class FilterBottomSheetDialog( + private val filterType: MatchingFilterType, + private val onOptionSelected: (Any?) -> Unit, +) : BottomSheetDialogFragment() { + private var _binding: DialogFilterBottomSheetBinding? = null + val binding get() = _binding!! + + private val viewModel: MatchingMapViewModel by viewModels({ requireParentFragment() }) + + private val adapter by lazy { + FilterOptionRVAdapter { selectedItem -> + onOptionSelected(selectedItem) + if (!filterType.isMultiSelect) dismiss() + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NORMAL, R.style.CustomBottomSheetDialogTheme) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = DialogFilterBottomSheetBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + binding.filterOptionsRv.adapter = adapter + + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.filterState.collect { state -> + adapter.submitList(FilterUiMapper.mapToUiModels(filterType, state)) + } + } + } + + binding.closeBtn.setOnClickListener { dismiss() } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/feature/matching/src/main/java/com/project200/feature/matching/map/filter/FilterOptionRVAdapter.kt b/feature/matching/src/main/java/com/project200/feature/matching/map/filter/FilterOptionRVAdapter.kt new file mode 100644 index 00000000..d621a694 --- /dev/null +++ b/feature/matching/src/main/java/com/project200/feature/matching/map/filter/FilterOptionRVAdapter.kt @@ -0,0 +1,60 @@ +package com.project200.feature.matching.map.filter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.core.widget.TextViewCompat +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.project200.feature.matching.utils.FilterOptionUiModel +import com.project200.undabang.feature.matching.databinding.ItemFilterOptionBinding +import com.project200.undabang.presentation.R + +class FilterOptionRVAdapter( + private val onClick: (Any?) -> Unit, +) : ListAdapter(DiffCallback) { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): ViewHolder { + val binding = ItemFilterOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding) + } + + override fun onBindViewHolder( + holder: ViewHolder, + position: Int, + ) { + holder.bind(getItem(position)) + } + + inner class ViewHolder(private val binding: ItemFilterOptionBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: FilterOptionUiModel) { + val context = binding.root.context + binding.optionTv.text = context.getString(item.labelResId) + binding.checkIv.isVisible = item.isSelected + TextViewCompat.setTextAppearance(binding.optionTv, if (item.isSelected) R.style.content_bold else R.style.content_regular) + binding.filterOptionLl.setOnClickListener { onClick(item.originalData) } + } + } + + companion object { + private val DiffCallback = + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: FilterOptionUiModel, + newItem: FilterOptionUiModel, + ): Boolean { + return oldItem.labelResId == newItem.labelResId + } + + override fun areContentsTheSame( + oldItem: FilterOptionUiModel, + newItem: FilterOptionUiModel, + ): Boolean { + return oldItem == newItem + } + } + } +} diff --git a/feature/matching/src/main/java/com/project200/feature/matching/map/filter/MatchingFilterRVAdapter.kt b/feature/matching/src/main/java/com/project200/feature/matching/map/filter/MatchingFilterRVAdapter.kt new file mode 100644 index 00000000..5d4b6cd2 --- /dev/null +++ b/feature/matching/src/main/java/com/project200/feature/matching/map/filter/MatchingFilterRVAdapter.kt @@ -0,0 +1,87 @@ +package com.project200.feature.matching.map.filter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.project200.feature.matching.utils.FilterListItem +import com.project200.feature.matching.utils.FilterState +import com.project200.feature.matching.utils.MatchingFilterType +import com.project200.undabang.feature.matching.databinding.ItemFilterBinding +import com.project200.undabang.feature.matching.databinding.ItemFilterClearBinding + +class MatchingFilterRVAdapter( + private val onFilterClick: (MatchingFilterType) -> Unit, + private val onClearClick: () -> Unit, +) : ListAdapter(DiffCallback) { + // 현재 필터 상태를 저장 (어떤 필터가 활성화되었는지 확인용) + private var currentFilterState: FilterState = FilterState() + + override fun getItemViewType(position: Int): Int { + return when (getItem(position)) { + is FilterListItem.ClearButton -> TYPE_CLEAR + is FilterListItem.FilterItem -> TYPE_FILTER + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): RecyclerView.ViewHolder { + return if (viewType == TYPE_CLEAR) { + val binding = ItemFilterClearBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ClearViewHolder(binding, onClearClick) + } else { + val binding = ItemFilterBinding.inflate(LayoutInflater.from(parent.context), parent, false) + FilterViewHolder(binding, onFilterClick) + } + } + + override fun onBindViewHolder( + holder: RecyclerView.ViewHolder, + position: Int, + ) { + when (val item = getItem(position)) { + is FilterListItem.ClearButton -> (holder as ClearViewHolder).bind() + is FilterListItem.FilterItem -> (holder as FilterViewHolder).bind(item.type, currentFilterState) + } + } + + /** + * 필터 상태가 변경될 때 호출하여 UI 갱신 (선택 여부 표시) + */ + fun submitFilterState(newState: FilterState) { + this.currentFilterState = newState + notifyDataSetChanged() + } + + fun submitFilterList(filters: List) { + val list = mutableListOf() + list.add(FilterListItem.ClearButton) // 맨 앞에 초기화 버튼 추가 + list.addAll(filters.map { FilterListItem.FilterItem(it) }) + submitList(list) + } + + companion object { + private const val TYPE_CLEAR = 0 + private const val TYPE_FILTER = 1 + + private val DiffCallback = + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: FilterListItem, + newItem: FilterListItem, + ): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: FilterListItem, + newItem: FilterListItem, + ): Boolean { + return oldItem == newItem + } + } + } +} diff --git a/feature/matching/src/main/java/com/project200/feature/matching/map/filter/MatchingFilterViewHolders.kt b/feature/matching/src/main/java/com/project200/feature/matching/map/filter/MatchingFilterViewHolders.kt new file mode 100644 index 00000000..593827f9 --- /dev/null +++ b/feature/matching/src/main/java/com/project200/feature/matching/map/filter/MatchingFilterViewHolders.kt @@ -0,0 +1,87 @@ +package com.project200.feature.matching.map.filter + +import android.content.res.ColorStateList +import androidx.recyclerview.widget.RecyclerView +import com.project200.feature.matching.utils.FilterState +import com.project200.feature.matching.utils.MatchingFilterType +import com.project200.presentation.utils.labelResId +import com.project200.undabang.feature.matching.databinding.ItemFilterBinding +import com.project200.undabang.feature.matching.databinding.ItemFilterClearBinding +import com.project200.undabang.presentation.R + +/** + * 초기화 뷰홀더 + */ +class ClearViewHolder( + private val binding: ItemFilterClearBinding, + private val onClearClick: () -> Unit, +) : RecyclerView.ViewHolder(binding.root) { + fun bind() { + binding.root.setOnClickListener { onClearClick() } + } +} + +/** + * 필터 항목 뷰홀더 + */ +class FilterViewHolder( + private val binding: ItemFilterBinding, + private val onFilterClick: (MatchingFilterType) -> Unit, +) : RecyclerView.ViewHolder(binding.root) { + fun bind( + item: MatchingFilterType, + currentState: FilterState, + ) { + val context = binding.root.context + + // 라벨 설정 + val labelResId = + if (item.isMultiSelect) { + item.labelResId + } else { + getSelectedOptionLabelResId(item, currentState) ?: item.labelResId + } + binding.filterTitleTv.text = context.getString(labelResId) + + // 선택 상태 디자인 + val isSelected = isFilterSelected(item, currentState) + binding.root.isSelected = isSelected + + binding.filterTitleTv.setTextColor( + context.getColor(if (isSelected) R.color.white300 else R.color.black), + ) + binding.filterIv.imageTintList = + ColorStateList.valueOf( + context.getColor(if (isSelected) R.color.white300 else R.color.black), + ) + + binding.root.setOnClickListener { onFilterClick(item) } + } + + private fun isFilterSelected( + type: MatchingFilterType, + state: FilterState, + ): Boolean { + return when (type) { + MatchingFilterType.GENDER -> state.gender != null + MatchingFilterType.AGE -> state.ageGroup != null + MatchingFilterType.DAY -> state.days.isNotEmpty() + MatchingFilterType.SKILL -> state.skillLevel != null + MatchingFilterType.SCORE -> state.exerciseScore != null + } + } + + // 선택된 옵션의 라벨 리소스 ID를 반환 + private fun getSelectedOptionLabelResId( + type: MatchingFilterType, + state: FilterState, + ): Int? { + return when (type) { + MatchingFilterType.GENDER -> state.gender?.labelResId + MatchingFilterType.AGE -> state.ageGroup?.labelResId + MatchingFilterType.SKILL -> state.skillLevel?.labelResId + MatchingFilterType.SCORE -> state.exerciseScore?.labelResId + else -> null + } + } +} diff --git a/feature/matching/src/main/java/com/project200/feature/matching/utils/FilterUiMapper.kt b/feature/matching/src/main/java/com/project200/feature/matching/utils/FilterUiMapper.kt new file mode 100644 index 00000000..919c47df --- /dev/null +++ b/feature/matching/src/main/java/com/project200/feature/matching/utils/FilterUiMapper.kt @@ -0,0 +1,75 @@ +package com.project200.feature.matching.utils + +import com.project200.domain.model.AgeGroup +import com.project200.domain.model.DayOfWeek +import com.project200.domain.model.ExerciseScore +import com.project200.domain.model.Gender +import com.project200.domain.model.SkillLevel +import com.project200.presentation.utils.labelResId + +object FilterUiMapper { + fun mapToUiModels( + type: MatchingFilterType, + currentState: FilterState, + ): List { + return when (type) { + MatchingFilterType.GENDER -> + Gender.entries.map { gender -> + FilterOptionUiModel( + labelResId = gender.labelResId, + isSelected = currentState.gender == gender, + originalData = gender, + ) + } + MatchingFilterType.AGE -> + AgeGroup.entries.map { age -> + FilterOptionUiModel( + labelResId = age.labelResId, + isSelected = currentState.ageGroup == age, + originalData = age, + ) + } + MatchingFilterType.DAY -> { + val list = mutableListOf() + + // 전체 옵션 + list.add( + FilterOptionUiModel( + labelResId = com.project200.undabang.presentation.R.string.filter_all, + isSelected = currentState.days.isEmpty(), + originalData = null, + ), + ) + + // 요일 옵션 + list.addAll( + DayOfWeek.entries.map { day -> + FilterOptionUiModel( + labelResId = day.labelResId, + isSelected = currentState.days.contains(day), + originalData = day, + ) + }, + ) + + list + } + MatchingFilterType.SKILL -> + SkillLevel.entries.map { skill -> + FilterOptionUiModel( + labelResId = skill.labelResId, + isSelected = currentState.skillLevel == skill, + originalData = skill, + ) + } + MatchingFilterType.SCORE -> + ExerciseScore.entries.map { score -> + FilterOptionUiModel( + labelResId = score.labelResId, + isSelected = currentState.exerciseScore == score, + originalData = score, + ) + } + } + } +} diff --git a/feature/matching/src/main/java/com/project200/feature/matching/utils/FilterUiModel.kt b/feature/matching/src/main/java/com/project200/feature/matching/utils/FilterUiModel.kt new file mode 100644 index 00000000..fdee4c41 --- /dev/null +++ b/feature/matching/src/main/java/com/project200/feature/matching/utils/FilterUiModel.kt @@ -0,0 +1,39 @@ +package com.project200.feature.matching.utils + +import com.project200.domain.model.AgeGroup +import com.project200.domain.model.DayOfWeek +import com.project200.domain.model.ExerciseScore +import com.project200.domain.model.Gender +import com.project200.domain.model.SkillLevel +import com.project200.undabang.feature.matching.R + +sealed class FilterListItem { + data object ClearButton : FilterListItem() // 초기화 버튼 + + data class FilterItem(val type: MatchingFilterType) : FilterListItem() // 필터 버튼 +} + +enum class MatchingFilterType( + val labelResId: Int, + val isMultiSelect: Boolean = false, +) { + GENDER(R.string.filter_gender), // 성별 + AGE(R.string.filter_age), // 나이 + DAY(R.string.filter_day, true), // 요일 + SKILL(R.string.filter_skill), // 숙련도 + SCORE(R.string.filter_score), // 점수 +} + +data class FilterState( + val gender: Gender? = null, + val ageGroup: AgeGroup? = null, + val days: Set = emptySet(), + val skillLevel: SkillLevel? = null, + val exerciseScore: ExerciseScore? = null, +) + +data class FilterOptionUiModel( + val labelResId: Int, + val isSelected: Boolean, + val originalData: Any?, // 선택된 Enum 객체 (Gender, AgeGroup 등) +) diff --git a/feature/matching/src/main/res/drawable/bg_filter_btn.xml b/feature/matching/src/main/res/drawable/bg_filter_btn.xml new file mode 100644 index 00000000..96acc1e6 --- /dev/null +++ b/feature/matching/src/main/res/drawable/bg_filter_btn.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/feature/matching/src/main/res/drawable/bg_filter_btn_selected.xml b/feature/matching/src/main/res/drawable/bg_filter_btn_selected.xml new file mode 100644 index 00000000..f8b7a304 --- /dev/null +++ b/feature/matching/src/main/res/drawable/bg_filter_btn_selected.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/feature/matching/src/main/res/drawable/ic_filter_check.xml b/feature/matching/src/main/res/drawable/ic_filter_check.xml new file mode 100644 index 00000000..25fc294b --- /dev/null +++ b/feature/matching/src/main/res/drawable/ic_filter_check.xml @@ -0,0 +1,10 @@ + + + diff --git a/feature/matching/src/main/res/drawable/ic_filter_clear.xml b/feature/matching/src/main/res/drawable/ic_filter_clear.xml new file mode 100644 index 00000000..55a80212 --- /dev/null +++ b/feature/matching/src/main/res/drawable/ic_filter_clear.xml @@ -0,0 +1,9 @@ + + + diff --git a/feature/matching/src/main/res/drawable/selector_filter_btn.xml b/feature/matching/src/main/res/drawable/selector_filter_btn.xml new file mode 100644 index 00000000..f76528f7 --- /dev/null +++ b/feature/matching/src/main/res/drawable/selector_filter_btn.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/feature/matching/src/main/res/layout/dialog_filter_bottom_sheet.xml b/feature/matching/src/main/res/layout/dialog_filter_bottom_sheet.xml new file mode 100644 index 00000000..706ab7b6 --- /dev/null +++ b/feature/matching/src/main/res/layout/dialog_filter_bottom_sheet.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/feature/matching/src/main/res/layout/fragment_matching_map.xml b/feature/matching/src/main/res/layout/fragment_matching_map.xml index d1d0b92d..07e18ce9 100644 --- a/feature/matching/src/main/res/layout/fragment_matching_map.xml +++ b/feature/matching/src/main/res/layout/fragment_matching_map.xml @@ -2,15 +2,16 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + + + + + \ No newline at end of file diff --git a/feature/matching/src/main/res/layout/item_filter_clear.xml b/feature/matching/src/main/res/layout/item_filter_clear.xml new file mode 100644 index 00000000..02c4b2c9 --- /dev/null +++ b/feature/matching/src/main/res/layout/item_filter_clear.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/feature/matching/src/main/res/layout/item_filter_option.xml b/feature/matching/src/main/res/layout/item_filter_option.xml new file mode 100644 index 00000000..dc654e76 --- /dev/null +++ b/feature/matching/src/main/res/layout/item_filter_option.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/feature/matching/src/main/res/values/strings.xml b/feature/matching/src/main/res/values/strings.xml index 5eb1fc86..a9c144bc 100644 --- a/feature/matching/src/main/res/values/strings.xml +++ b/feature/matching/src/main/res/values/strings.xml @@ -86,4 +86,13 @@ 운동장소 등록에 실패했습니다. 1:1 채팅하기 + + 초기화 + + 성별 + 나이 + 운동 요일 + 운동 종목 + 숙련도 + 운동 점수 \ No newline at end of file diff --git a/presentation/src/main/java/com/project200/presentation/utils/FilterToStringMapper.kt b/presentation/src/main/java/com/project200/presentation/utils/FilterToStringMapper.kt new file mode 100644 index 00000000..f8d093a0 --- /dev/null +++ b/presentation/src/main/java/com/project200/presentation/utils/FilterToStringMapper.kt @@ -0,0 +1,58 @@ +package com.project200.presentation.utils + +import com.project200.domain.model.AgeGroup +import com.project200.domain.model.DayOfWeek +import com.project200.domain.model.ExerciseScore +import com.project200.domain.model.Gender +import com.project200.domain.model.SkillLevel +import com.project200.undabang.presentation.R + +val Gender.labelResId: Int + get() = + when (this) { + Gender.MALE -> R.string.gender_male + Gender.FEMALE -> R.string.gender_female + } + +val AgeGroup.labelResId: Int + get() = + when (this) { + AgeGroup.TEEN -> R.string.age_10 + AgeGroup.TWENTIES -> R.string.age_20 + AgeGroup.THIRTIES -> R.string.age_30 + AgeGroup.FORTIES -> R.string.age_40 + AgeGroup.FIFTIES -> R.string.age_50 + AgeGroup.SIXTIES_PLUS -> R.string.age_60 + } + +val DayOfWeek.labelResId: Int + get() = + when (this) { + DayOfWeek.MONDAY -> R.string.mon + DayOfWeek.TUESDAY -> R.string.tue + DayOfWeek.WEDNESDAY -> R.string.wed + DayOfWeek.THURSDAY -> R.string.thu + DayOfWeek.FRIDAY -> R.string.fri + DayOfWeek.SATURDAY -> R.string.sat + DayOfWeek.SUNDAY -> R.string.sun + } + +val SkillLevel.labelResId: Int + get() = + when (this) { + SkillLevel.NOVICE -> R.string.novice + SkillLevel.BEGINNER -> R.string.beginner + SkillLevel.INTERMEDIATE -> R.string.intermediate + SkillLevel.ADVANCED -> R.string.advanced + SkillLevel.EXPERT -> R.string.expert + SkillLevel.PROFESSIONAL -> R.string.professional + } + +val ExerciseScore.labelResId: Int + get() = + when (this) { + ExerciseScore.OVER_20 -> R.string.score_over_20 + ExerciseScore.OVER_40 -> R.string.score_over_40 + ExerciseScore.OVER_60 -> R.string.score_over_60 + ExerciseScore.OVER_80 -> R.string.score_over_80 + } diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 905f07b3..cd69bd4f 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -15,7 +15,37 @@ 업데이트 알림 새로운 버전이 출시되었습니다.\n앱을 업데이트해주세요. - 업데이트 + 업데이트 남성 + 여성 + + 전체 + + 10대 + 20대 + 30대 + 40대 + 50대 + 60대 이상 + + 20점 이상 + 40점 이상 + 60점 이상 + 80점 이상 + + + + + + + + + + 입문 + 초급 + 중급 + 고급 + 숙련 + 선출 나중에