Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[스터디 상세보기 뷰] 스터디 시작 시 보여질 요일 선택 뷰를 구현합니다. #481

Merged
merged 12 commits into from
Oct 16, 2023
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
@@ -0,0 +1,22 @@
package com.created.team201.presentation.common.customview.dayofselector

import androidx.annotation.StringRes
import com.created.team201.R

enum class DayOfWeek(@StringRes val stringRes: Int) {
MONDAY(R.string.monday),
TUESDAY(R.string.tuesday),
WEDNESDAY(R.string.wednesday),
THURSDAY(R.string.thursday),
FRIDAY(R.string.friday),
SATURDAY(R.string.saturday),
SUNDAY(R.string.sunday),
;

companion object {
fun getValuesWithStartDay(startDay: DayOfWeek = MONDAY): List<DayOfWeek> {
val values = DayOfWeek.values()
return values.slice(startDay.ordinal until values.size) + values.slice(0 until startDay.ordinal)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.created.team201.presentation.common.customview.dayofselector

import android.content.Context
import android.content.res.TypedArray
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.children
import androidx.databinding.BindingAdapter
import com.created.team201.R
import com.created.team201.databinding.ViewDayOfWeekSelectorBinding

class DayOfWeekSelector @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : ConstraintLayout(context, attrs) {

private val binding: ViewDayOfWeekSelectorBinding by lazy {
ViewDayOfWeekSelectorBinding.inflate(LayoutInflater.from(context), this, true)
}

private val dayTextViews: Map<DayOfWeek, TextView> by lazy { initDayTextViews() }

private val textViewDays: Map<TextView, DayOfWeek> by lazy { initTextViewDays() }
Comment on lines +26 to +28
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 두개의 차이점이 무엇일까요..?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

똑같은 값을 초기화해주는 것 같은데 key로 찾기 value로 찾기로는 부족해서 두개를 따로 분리해주신걸까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실은 <key, key> 와 같은 형태의 자료구조가 필요했습니다.
map의 경우 value로는 key값을 찾지 못합니다.
day enum으로도 해당하는 textview를 찾을 수 있고, textView로도 해당하는 day enum을 찾을 수가 없었기에 지금과 같은 방법을 사용하였습니다.


private var canMultiSelect: Boolean = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canMultiSelect의 Default값은 false이고, 생성자로 받게 되는 attributeSet은 nullable 합니다.
view_day_of_week_selector.xml 의 코드를 슬쩍 봤는데 default value인 SingleSelect에 대한 Background drawable 설정을 안 해주더라구요.
attributeSet이 없을 경우 의도한대로 동작하지 않을 수도 있을 거 같아요
view_day_of_week_selector의 각각의 dayOfTextView의 background에 default background를 지정해주면 어떨까요?
그러면 이 값이 false일 때 setTypedArray()에서 초기화 해줄 필요 없을 거 같아요!
제가 코드를 잘못 이해했을 수도 있으니
한 번 확인 부탁드립니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

초기화 값으로 사용한 값이 default로 사용될 수 있다는 값으로 확실하게 인지 했어야하는데 놓친부분이라 수정하였습니다!
감사합니다!


private var dayOnClick: DayOnClickListener? = null

fun interface DayOnClickListener {
fun onClick(day: DayOfWeek)
}

init {
attrs?.let { setTypedArray(getAttrs(it)) }
setupDayTexts()
setupDayClickListeners()
}

private fun initDayTextViews(): Map<DayOfWeek, TextView> =
DayOfWeek.getValuesWithStartDay()
.zip(binding.clDayOfWeekBackground.children.filterIsInstance<TextView>().toList())
.toMap()

private fun initTextViewDays(): Map<TextView, DayOfWeek> =
dayTextViews.entries.associateBy({ it.value }, { it.key })

private fun getAttrs(attrs: AttributeSet): TypedArray {
return context.obtainStyledAttributes(attrs, R.styleable.DayOfWeekSelector)
}

private fun setTypedArray(typedArray: TypedArray) {
canMultiSelect =
typedArray.getBoolean(R.styleable.DayOfWeekSelector_canMultipleSelect, false)

if (canMultiSelect) {
dayTextViews.values.forEach {
it.background = getBackground(R.drawable.bg_day_of_week_selector_multi_select)
}
} else {
dayTextViews.values.forEach {
it.background = getBackground(R.drawable.bg_day_of_week_selector_single_select)
}
}

typedArray.recycle()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오.. 이건 왜 해주는건가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메서드 이름이 헷갈릴 수 있는데
실생활로 비유하자면 typeArray라는 바가지에 물을 담아두었던걸 비우고 새로운 물을 받는 행위
-> 이 행위 자체를 바가지(typedArray)를 재활용한다 라고 생각하면 좋을 것 같습니다.
참고

}

private fun getBackground(@DrawableRes id: Int): Drawable? = ResourcesCompat.getDrawable(
resources,
id,
null,
)

private fun setupDayClickListeners() {
dayTextViews.values.forEach { dayTextView ->
dayTextView.setOnClickListener { it ->
val clickedDay: DayOfWeek =
requireNotNull(textViewDays[it]) { "$it 에 해당하는 요일이 존재하지 않습니다." }
if (canMultiSelect) {
selectDayTextView(dayTextView)
dayOnClick?.onClick(clickedDay)
return@setOnClickListener
}

if (it.isSelected) return@setOnClickListener

selectDayTextView(dayTextView)

dayTextViews.values
.filterNot { it == dayTextView }
.forEach { it.isSelected = false }
dayOnClick?.onClick(clickedDay)
}
}
}

private fun setupDayTexts() {
val days: List<DayOfWeek> = DayOfWeek.getValuesWithStartDay()
dayTextViews.values.forEachIndexed { index, textView ->
textView.text = resources.getString(days[index].stringRes)
}
}

fun setSelectableDays(selectableDays: List<DayOfWeek>) {
dayTextViews.keys.forEach {
if (selectableDays.contains(it)) {
dayTextViews[it]?.isEnabled = true
return@forEach
}
dayTextViews[it]?.isEnabled = false
}
}
Comment on lines +109 to +117
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 이 함수는 어떤 역할을 수행하는 함수 인가요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 함수는 싱글 선택 모드일 때 선택할 수 있는 요일들을 리스트로 넘겨 지정하게해주는 로직입니다.

피그마에서 보여드렸듯 싱글 선택 모드는 선택 가능한 요일은 어두운 초록 동그라미, 선택 된 요일은 밝은 초록 동그라미인데요,
그 중 어두운 초록 동그라미가 무엇이 될지 선택하는 함수입니다.


fun selectDay(day: DayOfWeek) {
dayTextViews[day]?.isSelected = true
}

fun selectDays(days: List<DayOfWeek>) {
days.forEach { day ->
val textView: TextView =
requireNotNull(dayTextViews[day]) { "$day 에 해당하는 TextView가 존재하지 않습니다." }

textView.isSelected = !(textView.isSelected)
}
}

private fun selectDayTextView(day: TextView) {
day.isSelected = !day.isSelected
}

fun getSelectedDays(): List<DayOfWeek> {
val selectedDayTextViews: List<TextView> = dayTextViews.values.filter { it.isSelected }
return selectedDayTextViews.map { requireNotNull(textViewDays[it]) { "$it 에 해당하는 요일이 존재하지 않습니다." } }
}

fun setDayOnClickListener(dayOnClickListener: DayOnClickListener) {
dayOnClick = dayOnClickListener
}

fun getSelectedDaysSize(): Int {
return dayTextViews.values.count { it.isSelected }
}

companion object {

@JvmStatic
@BindingAdapter("selectableDays")
fun setSelectableDays(
dayOfWeekSelector: DayOfWeekSelector,
selectableDays: List<DayOfWeek>,
) {
dayOfWeekSelector.setSelectableDays(selectableDays)
}

@JvmStatic
@BindingAdapter("dayOnClick")
fun setDayOnClickListener(
dayOfWeekSelector: DayOfWeekSelector,
dayOnClickListener: DayOnClickListener,
) {
dayOfWeekSelector.setDayOnClickListener(dayOnClickListener)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.created.team201.presentation.report.model.ReportCategory
import com.created.team201.presentation.studyDetail.StudyDetailState.Guest
import com.created.team201.presentation.studyDetail.StudyDetailState.Master
import com.created.team201.presentation.studyDetail.adapter.StudyParticipantsAdapter
import com.created.team201.presentation.studyDetail.bottomSheet.StudyStartBottomSheetFragment
import com.created.team201.presentation.studyDetail.model.PeriodFormat
import com.created.team201.presentation.studyDetail.model.StudyDetailUIModel
import dagger.hilt.android.AndroidEntryPoint
Expand Down Expand Up @@ -154,7 +155,11 @@ class StudyDetailActivity :
}

private fun onMasterClickMainButton() {
studyDetailViewModel.startStudy(studyId)
val studyStartBottomSheetFragment = StudyStartBottomSheetFragment.newInstance(studyId)
studyStartBottomSheetFragment.show(
supportFragmentManager,
studyStartBottomSheetFragment.tag,
)
}

private fun onNothingClickMainButton() {
Expand All @@ -165,7 +170,7 @@ class StudyDetailActivity :
removeAllFragment()
LoginBottomSheetFragment().show(
supportFragmentManager,
LoginBottomSheetFragment.TAG_LOGIN_BOTTOM_SHEET
LoginBottomSheetFragment.TAG_LOGIN_BOTTOM_SHEET,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.created.team201.presentation.studyDetail.bottomSheet

import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.fragment.app.activityViewModels
import com.created.team201.R
import com.created.team201.databinding.FragmentStudyStartBottomSheetBinding
import com.created.team201.presentation.common.BindingBottomSheetFragment
import com.created.team201.presentation.studyDetail.StudyDetailViewModel

class StudyStartBottomSheetFragment :
BindingBottomSheetFragment<FragmentStudyStartBottomSheetBinding>(
R.layout.fragment_study_start_bottom_sheet,
) {
private val studyDetailViewModel: StudyDetailViewModel by activityViewModels()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

initBinding()
setupDayOnClickListener()
}

private fun initBinding() {
binding.viewModel = studyDetailViewModel
binding.onCancelClickListener = { dismiss() }
binding.onStartClickListener = ::onStartButtonClick
binding.lifecycleOwner = viewLifecycleOwner
}

private fun setupDayOnClickListener() {
binding.dowsStudyStartBottomSheetDayOfWeekSelector.setDayOnClickListener {
val selectedDaysSize: Int =
binding.dowsStudyStartBottomSheetDayOfWeekSelector.getSelectedDaysSize()

if (selectedDaysSize > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

마법숫자

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 0은 0 자체로 주는 뜻이 있다고 생각합니다.

현재 로직에서도 0의 특성을 그대로 살려 로직을 작성하였습니다.
선택한 요일의 갯수가 0보다 큰 수라면 -> 버튼을 활성화 한다.

0 보다 크다 -> 자연수이다.

따라서 상수화를 하지 않았던 것인데요. 혹시 어떻게 생각하시나요?

binding.tvStudyStartBottomSheetBtnStart.isEnabled = true
return@setDayOnClickListener
}
binding.tvStudyStartBottomSheetBtnStart.isEnabled = false
}
}

private fun onStartButtonClick() {
// val studyId = arguments?.getLong(KEY_STUDY_ID) ?: INVALID_STUDY_ID
// validateStudyId(studyId)
// studyDetailViewModel.startStudy(studyId) ToDo: 서버 연결시 수정
Toast.makeText(
context,
binding.dowsStudyStartBottomSheetDayOfWeekSelector.getSelectedDays().toString(),
Toast.LENGTH_SHORT,
).show()
dismiss()
}

private fun validateStudyId(studyId: Long) {
if (studyId == INVALID_STUDY_ID) {
Toast.makeText(
context,
getString(R.string.study_start_bottom_sheet_dialog_fragment_not_valid_study),
Toast.LENGTH_SHORT,
).show()
dismiss()
}
}

companion object {
private const val KEY_STUDY_ID: String = "KEY_STUDY_ID"
private const val INVALID_STUDY_ID: Long = 0L

fun newInstance(studyId: Long): StudyStartBottomSheetFragment =
StudyStartBottomSheetFragment().apply {
arguments = Bundle().apply {
putLong(KEY_STUDY_ID, studyId)
}
}
}
}
8 changes: 8 additions & 0 deletions android/app/src/main/res/drawable/bg_day_of_week_selector.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<corners android:radius="10dp" />
</shape>
</item>
</selector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:state_selected="true">
<shape android:angle="0" android:shape="oval" android:useLevel="false">

<solid android:color="@color/green10_CC3AD353" />
</shape>
</item>

</selector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:state_enabled="true" android:state_selected="true">
<shape android:angle="0" android:shape="oval" android:useLevel="false">

<solid android:color="@color/green10_CC3AD353" />
</shape>
</item>

<item android:state_enabled="true" android:state_selected="false">
<shape android:angle="0" android:shape="oval" android:useLevel="false">

<solid android:color="@color/green03_016D32" />
</shape>
</item>

</selector>
9 changes: 9 additions & 0 deletions android/app/src/main/res/drawable/ic_close.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:pathData="M17.414,16L24,9.414L22.586,8L16,14.586L9.414,8L8,9.414L14.586,16L8,22.586L9.414,24L16,17.414L22.586,24L24,22.586L17.414,16Z"
android:fillColor="#ffffff"/>
</vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@color/green09_799E82" />
<item android:color="#ffffff"/>
</selector>
Loading