Skip to content

Commit

Permalink
Merge pull request #481 from woowacourse-teams/AN/feature/449-studyde…
Browse files Browse the repository at this point in the history
…tail-inflate

[스터디 상세보기 뷰] 스터디 시작 시 보여질 요일 선택 뷰를 구현합니다.
  • Loading branch information
no1msh authored Oct 16, 2023
2 parents b764d04 + 94653cd commit a0cc743
Show file tree
Hide file tree
Showing 14 changed files with 560 additions and 3 deletions.
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() }

private var canMultiSelect: Boolean = false

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()
}

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
}
}

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) {
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

0 comments on commit a0cc743

Please sign in to comment.