Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:drawable="@drawable/ic_check" />
<item android:state_selected="true" android:drawable="@drawable/ic_select_check" />
<item android:state_selected="false" android:drawable="@drawable/ic_uncheck" />
</selector>
6 changes: 6 additions & 0 deletions feature/exercise/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,10 @@ dependencies {

// Shimmer
implementation(libs.shimmer)

// Google Maps
implementation(libs.play.services.location)

// Kakao Map
implementation(libs.kakao.map)
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.project200.presentation.utils.KeyboardAdjustHelper.applyEdgeToEdgeIns
import com.project200.presentation.utils.TimeEditTextLimiter.addRangeLimit
import com.project200.presentation.utils.UiUtils.dpToPx
import com.project200.presentation.utils.UiUtils.getScreenWidthPx
import com.project200.presentation.view.SelectionBottomSheetDialog
import com.project200.undabang.feature.exercise.R
import com.project200.undabang.feature.exercise.databinding.FragmentExerciseFormBinding
import dagger.hilt.android.AndroidEntryPoint
Expand Down Expand Up @@ -125,11 +126,38 @@ class ExerciseFormFragment : BindingFragment<FragmentExerciseFormBinding>(R.layo
)
}
viewModel.loadInitialRecord(args.recordId)
viewModel.loadExerciseTypes()
viewModel.loadExerciseLocation()

setupRVAdapter((getScreenWidthPx(requireActivity()) - dpToPx(requireContext(), GRID_SPAN_MARGIN)) / GRID_SPAN_COUNT)
initListeners()
}

private fun initListeners() {
// 운동 종류 선택 버튼
binding.recordTypeSelectBtn.setOnClickListener {
val items = viewModel.exerciseTypeList.value
if (items.isNullOrEmpty()) {
viewModel.loadExerciseTypes() // 데이터가 없으면 다시 요청
return@setOnClickListener
}

SelectionBottomSheetDialog(items, binding.recordTypeSelectBtn.text.toString()) { selectedType ->
if (selectedType == ExerciseFormViewModel.DIRECT_INPUT) {
// 직접 입력 선택
binding.recordTypeSelectBtn.setText(R.string.exercise_record_type_direct)
binding.recordTypeEt.apply {
setText("")
visibility = View.VISIBLE
requestFocus()
}
} else {
binding.recordTypeSelectBtn.setText(selectedType)
binding.recordTypeEt.visibility = View.GONE
}
}.show(parentFragmentManager, SelectionBottomSheetDialog::class.java.name)
}

// 시간/날짜 버튼 클릭 리스너 설정
binding.startDateBtn.setOnClickListener { viewModel.onTimeSelectionClick(TimeSelectionState.START_DATE) }
binding.startTimeBtn.setOnClickListener { viewModel.onTimeSelectionClick(TimeSelectionState.START_TIME) }
Expand All @@ -156,13 +184,49 @@ class ExerciseFormFragment : BindingFragment<FragmentExerciseFormBinding>(R.layo
viewModel.updateTime(hour, minute)
}

// 운동 장소 선택 버튼
binding.recordLocationSelectBtn.setOnClickListener {
val items = viewModel.exerciseLocation.value
if (items.isNullOrEmpty()) {
viewModel.loadExerciseLocation()
return@setOnClickListener
}

SelectionBottomSheetDialog(items, binding.recordLocationSelectBtn.text.toString()) { selectedLocation ->
if (selectedLocation == ExerciseFormViewModel.DIRECT_INPUT) {
// 직접 입력 선택
findNavController().navigate(
ExerciseFormFragmentDirections.actionExerciseFormFragmentToExercisePlaceSearchFragment(),
)
} else {
binding.recordLocationSelectBtn.setText(selectedLocation)
}
}.show(parentFragmentManager, SelectionBottomSheetDialog::class.java.name)
}

// 기록 완료 버튼
binding.recordCompleteBtn.setOnClickListener {
val type = binding.recordTypeSelectBtn.text.toString().trim()
if (type == getString(R.string.exercise_record_type_direct) &&
binding.recordTypeEt.text.toString().isBlank()
) {
Toast.makeText(requireContext(), R.string.exercise_record_type_direct_input_warning, Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
viewModel.submitRecord(
recordId = args.recordId,
title = binding.recordTitleEt.text.toString().trim(),
type = binding.recordTypeEt.text.toString().trim(),
location = binding.recordLocationEt.text.toString().trim(),
type =
if (type ==
getString(
R.string.exercise_record_type_direct,
)
) {
binding.recordTypeEt.text.toString().trim()
} else {
binding.recordTypeSelectBtn.text.toString().trim()
},
location = binding.recordLocationSelectBtn.text.toString().trim(),
detail = binding.recordDescEt.text.toString().trim(),
)
}
Expand Down Expand Up @@ -196,6 +260,16 @@ class ExerciseFormFragment : BindingFragment<FragmentExerciseFormBinding>(R.layo
updateTimeSelectionUi(state)
}

findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<String>(
ExercisePlaceSearchFragment.KEY_SELECTED_PLACE,
)?.observe(viewLifecycleOwner) { name ->
binding.recordLocationSelectBtn.setText(name)
// 선택 후에는 결과 삭제
findNavController().currentBackStackEntry?.savedStateHandle?.remove<String>(
ExercisePlaceSearchFragment.KEY_SELECTED_PLACE,
)
}

viewModel.imageItems.observe(viewLifecycleOwner) { items ->
imageAdapter.submitList(items.toList())
}
Expand Down Expand Up @@ -281,8 +355,8 @@ class ExerciseFormFragment : BindingFragment<FragmentExerciseFormBinding>(R.layo

private fun setupInitialData(record: ExerciseRecord) {
binding.recordTitleEt.setText(record.title)
binding.recordTypeEt.setText(record.personalType)
binding.recordLocationEt.setText(record.location)
binding.recordTypeSelectBtn.setText(record.personalType)
binding.recordLocationSelectBtn.setText(record.location)
binding.recordDescEt.setText(record.detail)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import com.project200.domain.model.ExerciseRecord
import com.project200.domain.model.SubmissionResult
import com.project200.domain.usecase.CreateExerciseRecordUseCase
import com.project200.domain.usecase.EditExerciseRecordUseCase
import com.project200.domain.usecase.GetExercisePlaceUseCase
import com.project200.domain.usecase.GetExerciseRecordDetailUseCase
import com.project200.domain.usecase.GetExpectedScoreInfoUseCase
import com.project200.domain.usecase.GetPreferredExerciseTypesUseCase
import com.project200.domain.usecase.GetPreferredExerciseUseCase
import com.project200.domain.usecase.UploadExerciseRecordImagesUseCase
import com.project200.feature.exercise.utils.ScoreGuidanceState
import com.project200.feature.exercise.utils.TimeSelectionState
Expand All @@ -35,6 +38,9 @@ class ExerciseFormViewModel
private val uploadExerciseRecordImagesUseCase: UploadExerciseRecordImagesUseCase,
private val editExerciseRecordUseCase: EditExerciseRecordUseCase,
private val getExpectedScoreInfoUseCase: GetExpectedScoreInfoUseCase,
private val getPreferredExerciseTypesUseCase: GetPreferredExerciseTypesUseCase,
private val getPreferredExerciseUseCase: GetPreferredExerciseUseCase,
private val getExercisePlaceUseCase: GetExercisePlaceUseCase,
private val clockProvider: ClockProvider,
) : ViewModel() {
private val _startTime = MutableLiveData<LocalDateTime?>()
Expand All @@ -54,6 +60,14 @@ class ExerciseFormViewModel
private var isEditMode = false
private val removedPictureIds = mutableListOf<Long>() // 삭제할 기존 이미지 ID 목록

// 운동 종류
private val _exerciseTypeList = MutableLiveData<List<String>>()
val exerciseTypeList: LiveData<List<String>> = _exerciseTypeList

// 운동 장소 목록
private val _exerciseLocation = MutableLiveData<List<String>>()
val exerciseLocation: LiveData<List<String>> = _exerciseLocation

private val _initialDataLoaded = MutableLiveData<ExerciseRecord?>()
val initialDataLoaded: LiveData<ExerciseRecord?> = _initialDataLoaded

Expand Down Expand Up @@ -159,7 +173,6 @@ class ExerciseFormViewModel

/**
* 시간 역전 보정 함수
*
*/
private fun applyTimeCorrection(isStartChanged: Boolean) {
val now = clockProvider.localDateTimeNow()
Expand Down Expand Up @@ -261,6 +274,61 @@ class ExerciseFormViewModel
return MAX_IMAGE - imageCount
}

/** 운동 종류 리스트 로드 */
fun loadExerciseTypes() {
if (!_exerciseTypeList.value.isNullOrEmpty()) return
viewModelScope.launch {
// 유저의 선호 운동 리스트
val preferredResult = getPreferredExerciseUseCase()
val preferredNames =
if (preferredResult is BaseResult.Success) {
preferredResult.data.map { it.name }
} else {
emptyList()
}

// 전체 운동 종류 리스트
val allTypesResult = getPreferredExerciseTypesUseCase()
val allNames =
if (allTypesResult is BaseResult.Success) {
allTypesResult.data.map { it.name }
} else {
emptyList()
}

// 직접 입력 + 선호 운동 + 그 외 전체 운동(중복 제거)
val combinedList =
mutableListOf<String>().apply {
add(DIRECT_INPUT)
addAll(preferredNames)
addAll(allNames.filterNot { preferredNames.contains(it) })
}

_exerciseTypeList.value = combinedList
}
}

/** 운동 장소 설정 */
fun loadExerciseLocation() {
if (!_exerciseLocation.value.isNullOrEmpty()) return
viewModelScope.launch {
val locationResult = getExercisePlaceUseCase()
val locationList =
if (locationResult is BaseResult.Success) {
locationResult.data.map { it.name }
} else {
emptyList()
}

val combinedList =
mutableListOf<String>().apply {
add(DIRECT_INPUT)
addAll(locationList)
}
_exerciseLocation.value = combinedList
}
}

/** 변경 사항이 있는지 확인 */
private fun hasChanges(record: ExerciseRecord): Boolean = hasContentChanges(record) || hasImageChanges()

Expand Down Expand Up @@ -454,4 +522,8 @@ class ExerciseFormViewModel
}
}
}

companion object {
const val DIRECT_INPUT = "직접 입력"
}
}
Loading