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
22 changes: 22 additions & 0 deletions common/src/main/java/com/project200/common/constants/DayOfWeek.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.project200.common.constants

import androidx.annotation.StringRes
import com.project200.undabang.common.R

enum class DayOfWeek(
val index: Int,
@StringRes val resId: Int,
) {
MON(0, R.string.day_mon),
TUE(1, R.string.day_tue),
WED(2, R.string.day_wed),
THU(3, R.string.day_thu),
FRI(4, R.string.day_fri),
SAT(5, R.string.day_sat),
SUN(6, R.string.day_sun),
;

companion object {
fun getByIndex(index: Int): DayOfWeek? = entries.find { it.index == index }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.project200.common.utils

import android.content.Context
import com.project200.common.constants.DayOfWeek
import com.project200.undabang.common.R
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject

class PreferredExerciseDayFormatter
@Inject
constructor(
@ApplicationContext private val context: Context,
) {
/**
* 월요일부터 일요일까지의 선택 여부를 담은 Boolean 리스트를
* 지정된 규칙에 따라 문자열로 포맷합니다.
* @param days 크기가 7인 Boolean 리스트 [월, 화, 수, 목, 금, 토, 일] 순서
* @return 포맷팅된 요일 문자열
*/
fun formatDaysOfWeek(days: List<Boolean>): String {
if (days.size != 7) return ""

// 모든 요일이 포함될 경우 "매일"로 표기
if (days.all { it }) return context.getString(R.string.day_everyday)

val resultParts = mutableListOf<String>()
val weekdays = days.subList(0, 5) // 월요일부터 금요일까지
val weekend = days.subList(5, 7) // 토요일, 일요일

// 평일이 모두 포함될 경우 "평일"로 표기
if (weekdays.all { it }) {
resultParts.add(context.getString(R.string.day_weekdays))
} else {
// 각각 해당하는 평일 날짜를 추가
weekdays.forEachIndexed { index, isSelected ->
if (isSelected) {
addDayName(index, resultParts)
}
}
}

// 주말이 모두 포함될 경우 "주말"로 표기
if (weekend.all { it }) {
resultParts.add(context.getString(R.string.day_weekend))
} else {
// 각각 해당하는 주말 날짜를 추가
weekend.forEachIndexed { index, isSelected ->
if (isSelected) {
addDayName(index + 5, resultParts)
}
}
}

return resultParts.joinToString(", ")
}

private fun addDayName(
index: Int,
resultList: MutableList<String>,
) {
DayOfWeek.getByIndex(index)?.let { day ->
resultList.add(context.getString(day.resId))
}
}
}
16 changes: 16 additions & 0 deletions common/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 요일 -->
<string name="day_mon">월</string>
<string name="day_tue">화</string>
<string name="day_wed">수</string>
<string name="day_thu">목</string>
<string name="day_fri">금</string>
<string name="day_sat">토</string>
<string name="day_sun">일</string>

<!-- 그룹 표기 -->
<string name="day_everyday">매일</string>
<string name="day_weekdays">평일</string>
<string name="day_weekend">주말</string>
</resources>
7 changes: 7 additions & 0 deletions data/src/main/java/com/project200/data/api/ApiService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ interface ApiService {
@AccessTokenApi
suspend fun getBlockedMembers(): BaseResponse<List<GetBlockedMemberDTO>>

/** 선호 운동 */
// 선호 운동 조회
// TODO: 실제 api 명세 나오면 수정 필요
@GET("api/v1/preferred-exercises")
@AccessTokenApi
suspend fun getPreferredExercises(): BaseResponse<List<Unit>>

/** 운동 기록 */
// 구간별 운동 기록 횟수 조회
@GET("api/v1/exercises/count")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.project200.data.utils.apiCallBuilder
import com.project200.domain.model.BaseResult
import com.project200.domain.model.BlockedMember
import com.project200.domain.model.OpenUrl
import com.project200.domain.model.PreferredExercise
import com.project200.domain.model.ProfileImageList
import com.project200.domain.model.Score
import com.project200.domain.model.UserProfile
Expand Down Expand Up @@ -158,6 +159,32 @@ class MemberRepositoryImpl
)
}

override suspend fun getPreferredExercises(): BaseResult<List<PreferredExercise>> {
/* return apiCallBuilder(
ioDispatcher = ioDispatcher,
apiCall = { apiService.getPreferredExercises() },
mapper = {
Unit
},
)*/
return BaseResult.Success(
List(6) { index ->
PreferredExercise(
preferredExerciseId = index,
name = "운동종류 $index",
skillLevel =
when (index % 3) {
0 -> "초급"
1 -> "중급"
else -> "고급"
},
daysOfWeek = List(7) { dayIndex -> (dayIndex + index) % 2 == 0 },
imageUrl = "https://example.com/exercise_image_$index.png",
)
},
)
}

companion object {
const val IMAGE_PART_ERROR = "IMAGE_PART_ERROR"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.project200.domain.repository
import com.project200.domain.model.BaseResult
import com.project200.domain.model.BlockedMember
import com.project200.domain.model.OpenUrl
import com.project200.domain.model.PreferredExercise
import com.project200.domain.model.ProfileImageList
import com.project200.domain.model.Score
import com.project200.domain.model.UserProfile
Expand All @@ -21,4 +22,5 @@ interface MemberRepository {
suspend fun blockMember(memberId: String): BaseResult<Unit>
suspend fun unblockMember(memberId: String): BaseResult<Unit>
suspend fun getBlockedMembers(): BaseResult<List<BlockedMember>>
suspend fun getPreferredExercises(): BaseResult<List<PreferredExercise>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.project200.domain.usecase

import com.project200.domain.model.BaseResult
import com.project200.domain.model.PreferredExercise
import com.project200.domain.repository.MemberRepository
import javax.inject.Inject

class GetPreferredExerciseUseCase @Inject constructor(
private val repository: MemberRepository,
) {
suspend operator fun invoke(): BaseResult<List<PreferredExercise>> {
return repository.getPreferredExercises()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.kizitonwose.calendar.core.CalendarDay
import com.kizitonwose.calendar.core.DayPosition
import com.kizitonwose.calendar.core.daysOfWeek
import com.kizitonwose.calendar.view.MonthDayBinder
import com.kizitonwose.calendar.view.ViewContainer
import com.project200.common.utils.CommonDateTimeFormatters.YYYY_M_KR
import com.project200.common.utils.PreferredExerciseDayFormatter
import com.project200.presentation.base.BindingFragment
import com.project200.undabang.feature.profile.R
import com.project200.undabang.feature.profile.databinding.CalendarDayLayoutBinding
Expand All @@ -27,11 +29,16 @@ import java.time.DayOfWeek
import java.time.LocalDate
import java.time.YearMonth
import java.time.ZoneId
import javax.inject.Inject

@AndroidEntryPoint
class MypageFragment : BindingFragment<FragmentMypageBinding>(R.layout.fragment_mypage) {
private val viewModel: MypageViewModel by viewModels()
private var exerciseCompleteDates: Set<LocalDate> = emptySet()
lateinit var preferredExerciseRVAdapter: PreferredExerciseRVAdapter

@Inject
lateinit var dayFormatter: PreferredExerciseDayFormatter

override fun getViewBinding(view: View): FragmentMypageBinding {
return FragmentMypageBinding.bind(view)
Expand All @@ -40,6 +47,7 @@ class MypageFragment : BindingFragment<FragmentMypageBinding>(R.layout.fragment_
override fun setupViews() {
initClickListener()
setupCalendar()
setupPreferredExercise()
}

private fun initClickListener() {
Expand Down Expand Up @@ -67,6 +75,14 @@ class MypageFragment : BindingFragment<FragmentMypageBinding>(R.layout.fragment_
binding.nextMonthBtn.setOnClickListener {
viewModel.onNextMonthClicked()
}

binding.preferredExerciseEditBtn.setOnClickListener {
// TODO: 운동 선호도 설정 화면으로 이동
}

binding.preferredExerciseEmptyTv.setOnClickListener {
// TODO: 운동 선호도 설정 화면으로 이동
}
}

override fun setupObservers() {
Expand Down Expand Up @@ -97,6 +113,12 @@ class MypageFragment : BindingFragment<FragmentMypageBinding>(R.layout.fragment_
binding.exerciseCalendar.scrollToMonth(month)
}

viewModel.preferredExercise.observe(viewLifecycleOwner) { exercises ->
preferredExerciseRVAdapter.setItems(exercises)
binding.preferredExerciseEmptyTv.isVisible = exercises.isEmpty()
binding.preferredExerciseRv.isVisible = exercises.isNotEmpty()
}

viewModel.exerciseDates.observe(viewLifecycleOwner) { dates ->
exerciseCompleteDates = dates
binding.exerciseCalendar.notifyCalendarChanged()
Expand Down Expand Up @@ -201,6 +223,14 @@ class MypageFragment : BindingFragment<FragmentMypageBinding>(R.layout.fragment_
}
}

private fun setupPreferredExercise() {
preferredExerciseRVAdapter = PreferredExerciseRVAdapter(formatter = dayFormatter)
binding.preferredExerciseRv.apply {
adapter = preferredExerciseRVAdapter
layoutManager = LinearLayoutManager(requireContext())
}
}

private fun setupProfileImage(
thumbnailUrl: String?,
imageUrl: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.project200.common.utils.ClockProvider
import com.project200.domain.model.BaseResult
import com.project200.domain.model.OpenUrl
import com.project200.domain.model.PreferredExercise
import com.project200.domain.model.UserProfile
import com.project200.domain.usecase.GetExerciseCountInMonthUseCase
import com.project200.domain.usecase.GetPreferredExerciseUseCase
import com.project200.domain.usecase.GetUserProfileUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
Expand All @@ -24,6 +25,7 @@ class MypageViewModel
constructor(
private val getExerciseCountInMonthUseCase: GetExerciseCountInMonthUseCase,
private val getUserProfileUseCase: GetUserProfileUseCase,
private val getPreferredExerciseUseCase: GetPreferredExerciseUseCase,
private val clockProvider: ClockProvider,
) : ViewModel() {
private val _profile = MutableLiveData<UserProfile>()
Expand All @@ -37,8 +39,8 @@ class MypageViewModel
private val _exerciseDates = MutableLiveData<Set<LocalDate>>(emptySet())
val exerciseDates: LiveData<Set<LocalDate>> = _exerciseDates

private val _openUrl = MutableLiveData<OpenUrl>()
val openUrl: LiveData<OpenUrl> = _openUrl
private val _preferredExercise = MutableLiveData<List<PreferredExercise>>()
val preferredExercise: LiveData<List<PreferredExercise>> = _preferredExercise

private val _toast = MutableSharedFlow<Boolean>()
val toast: SharedFlow<Boolean> = _toast
Expand All @@ -47,6 +49,7 @@ class MypageViewModel
getProfile()
val initialMonth = clockProvider.yearMonthNow()
onMonthChanged(initialMonth)
getPreferredExercises()
}

fun onMonthChanged(newMonth: YearMonth) {
Expand Down Expand Up @@ -120,4 +123,21 @@ class MypageViewModel

_selectedMonth.value = newMonth
}

/**
* 선호 운동 조회
*/
fun getPreferredExercises() {
viewModelScope.launch {
when (val result = getPreferredExerciseUseCase()) {
is BaseResult.Success -> {
_preferredExercise.value = result.data
}

is BaseResult.Error -> {
_toast.emit(true)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.project200.undabang.profile.mypage

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.project200.common.utils.PreferredExerciseDayFormatter
import com.project200.domain.model.PreferredExercise
import com.project200.undabang.feature.profile.databinding.ItemPreferredExerciseBinding

class PreferredExerciseRVAdapter(
private var items: List<PreferredExercise> = emptyList(),
private val formatter: PreferredExerciseDayFormatter,
) : RecyclerView.Adapter<PreferredExerciseRVAdapter.ViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,
): ViewHolder {
val binding =
ItemPreferredExerciseBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false,
)
return ViewHolder(binding)
}

override fun onBindViewHolder(
holder: ViewHolder,
position: Int,
) {
holder.bind(items[position])
}

override fun getItemCount(): Int = items.size

fun setItems(newItems: List<PreferredExercise>) {
items = newItems
notifyDataSetChanged()
}

inner class ViewHolder(private val binding: ItemPreferredExerciseBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(exercise: PreferredExercise) {
binding.exerciseNameTv.text = exercise.name
binding.skillTv.text = exercise.skillLevel
binding.exerciseDaysTv.text = formatter.formatDaysOfWeek(exercise.daysOfWeek)
Glide.with(binding.exerciseIv.context)
.load(exercise.imageUrl)
.into(binding.exerciseIv)
}
}
}
Loading