Skip to content

Commit

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

[스터디 상세보기] 버그 리포팅 기반 개선
  • Loading branch information
no1msh authored Aug 9, 2023
2 parents eba7e10 + 264ea9b commit 3210f4c
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import android.os.Bundle
import android.view.MenuItem
import android.widget.Toast
import androidx.activity.viewModels
import androidx.annotation.StringRes
import com.created.team201.R
import com.created.team201.databinding.ActivityStudyDetailBinding
import com.created.team201.presentation.common.BindingActivity
import com.created.team201.presentation.profile.ProfileActivity
import com.created.team201.presentation.studyDetail.adapter.StudyParticipantsAdapter
import com.created.team201.presentation.studyDetail.model.PeriodFormat
import com.created.team201.presentation.studyDetail.model.StudyDetailUIModel
import com.created.team201.presentation.studyManagement.StudyManagementActivity

class StudyDetailActivity :
Expand All @@ -32,6 +34,8 @@ class StudyDetailActivity :
initStudyDetailInformation()
observeStudyDetailParticipants()
observeStartStudy()
observeCanStartStudy()
observeParticipantsCount()
}

private fun initViewModel() {
Expand All @@ -44,12 +48,13 @@ class StudyDetailActivity :
setSupportActionBar(binding.tbStudyDetailAppBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowTitleEnabled(false)
supportActionBar?.setHomeActionContentDescription(R.string.toolbar_back_text)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_back)
}

private fun validateStudyId() {
if (studyId == NON_EXISTENCE_STUDY_ID) {
Toast.makeText(this, "스터디를 찾을 수 없습니다.", Toast.LENGTH_SHORT).show()
showToast(R.string.study_detail_notify_invalid_study)
finish()
}
}
Expand All @@ -60,7 +65,12 @@ class StudyDetailActivity :
}

private fun initStudyDetailInformation() {
studyDetailViewModel.fetchStudyDetail(studyId)
studyDetailViewModel.fetchStudyDetail(studyId) {
if (studyDetailViewModel.study.value == StudyDetailUIModel.INVALID_STUDY_DETAIL) {
showToast(R.string.study_detail_notify_invalid_study)
}
finish()
}
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
Expand All @@ -76,10 +86,11 @@ class StudyDetailActivity :
}
}

fun convertPeriodOfCountFormat(periodOfCount: String?): String {
fun convertPeriodOfCountFormat(periodOfCount: String): String {
if (periodOfCount == "") return ""
val stringRes =
PeriodFormat.valueOf(periodOfCount?.last() ?: DEFAULT_PERIOD_SYMBOL).res
return getString(stringRes, periodOfCount?.dropLast(STRING_LAST_INDEX)?.toInt())
PeriodFormat.valueOf(periodOfCount.last()).res
return getString(stringRes, periodOfCount.dropLast(STRING_LAST_INDEX).toInt())
}

fun initMainButtonOnClick(isMaster: Boolean) {
Expand All @@ -96,12 +107,8 @@ class StudyDetailActivity :
}

override fun onAcceptApplicantClick(memberId: Long) {
if (studyDetailViewModel.isFullMember.value == true) {
Toast.makeText(
this,
getString(R.string.study_detail_do_not_accept_member_anymore),
Toast.LENGTH_SHORT,
).show()
if (studyDetailViewModel.isFullMember.value) {
showToast(R.string.study_detail_do_not_accept_member_anymore)
return
}
studyDetailViewModel.acceptApplicant(studyId, memberId)
Expand All @@ -111,6 +118,19 @@ class StudyDetailActivity :
startActivity(ProfileActivity.getIntent(this, memberId))
}

private fun observeParticipantsCount() {
studyDetailViewModel.studyMemberCount.observe(this) {
if (studyDetailViewModel.state.value is StudyDetailState.Master) {
binding.btnStudyDetailMain.text =
getString(
R.string.study_detail_button_start_study,
studyDetailViewModel.studyMemberCount.value,
studyDetailViewModel.study.value.peopleCount,
)
}
}
}

private fun observeStartStudy() {
studyDetailViewModel.isStartStudy.observe(this) { isStartStudy ->
if (isStartStudy) {
Expand All @@ -127,11 +147,19 @@ class StudyDetailActivity :
}
}

private fun observeCanStartStudy() {
studyDetailViewModel.canStudyStart.observe(this) { cantStartStudy ->
binding.btnStudyDetailMain.isEnabled = cantStartStudy
}
}

private fun showToast(@StringRes stringRes: Int) =
Toast.makeText(this, getString(stringRes), Toast.LENGTH_SHORT).show()

companion object {
private const val FIRST_PAGE = 1
private const val ROLE_INDEX_STUDY_MASTER = 0
private const val NON_EXISTENCE_STUDY_ID = 0L
private const val DEFAULT_PERIOD_SYMBOL = 'd'
private const val STRING_LAST_INDEX = 1
private const val KEY_STUDY_ID = "KEY_STUDY_ID"
fun getIntent(context: Context, studyId: Long): Intent =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ sealed class StudyDetailState(
@DrawableRes
val subButtonSrc: Int,
) {
object Master : StudyDetailState(
data class Master(private val canStartStudy: Boolean) : StudyDetailState(
appBarTitle = R.string.study_detail_app_bar_study_master_title,
mainButtonText = R.string.study_detail_button_start_study,
mainButtonTextColor = R.color.white,
subButtonSrc = R.drawable.ic_edit_20,
mainButtonIsEnabled = true,
mainButtonIsEnabled = canStartStudy,
)

object Member :
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.created.team201.presentation.studyDetail

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
Expand All @@ -14,35 +13,52 @@ import com.created.team201.data.remote.NetworkServiceModule
import com.created.team201.data.repository.StudyDetailRepositoryImpl
import com.created.team201.presentation.studyDetail.model.StudyDetailUIModel
import com.created.team201.presentation.studyDetail.model.StudyMemberUIModel
import com.created.team201.util.NonNullLiveData
import com.created.team201.util.NonNullMutableLiveData
import kotlinx.coroutines.launch

class StudyDetailViewModel private constructor(
private val studyDetailRepository: StudyDetailRepository,
) : ViewModel() {

private val _study: MutableLiveData<StudyDetailUIModel> = MutableLiveData()
val study: LiveData<StudyDetailUIModel> get() = _study
private val _studyParticipants: MutableLiveData<List<StudyMemberUIModel>> = MutableLiveData()
val studyParticipants: LiveData<List<StudyMemberUIModel>> get() = _studyParticipants
private val _state: MutableLiveData<StudyDetailState> =
MutableLiveData(StudyDetailState.Nothing)
private val _study: NonNullMutableLiveData<StudyDetailUIModel> =
NonNullMutableLiveData(StudyDetailUIModel.INVALID_STUDY_DETAIL)
val study: NonNullLiveData<StudyDetailUIModel> get() = _study

private val _studyParticipants: NonNullMutableLiveData<List<StudyMemberUIModel>> =
NonNullMutableLiveData(listOf())
val studyParticipants: NonNullLiveData<List<StudyMemberUIModel>> get() = _studyParticipants

private val _state: NonNullMutableLiveData<StudyDetailState> =
NonNullMutableLiveData(StudyDetailState.Nothing)
val state: LiveData<StudyDetailState> get() = _state
private val _isStartStudy: MutableLiveData<Boolean> = MutableLiveData(false)
val isStartStudy: LiveData<Boolean> get() = _isStartStudy
private val _isFullMember: MutableLiveData<Boolean> = MutableLiveData(false)
val isFullMember: LiveData<Boolean> get() = _isFullMember

fun fetchStudyDetail(studyId: Long) {
private val _isStartStudy: NonNullMutableLiveData<Boolean> = NonNullMutableLiveData(false)
val isStartStudy: NonNullLiveData<Boolean> get() = _isStartStudy

private val _isFullMember: NonNullMutableLiveData<Boolean> = NonNullMutableLiveData(false)
val isFullMember: NonNullLiveData<Boolean> get() = _isFullMember

private val _canStudyStart: NonNullMutableLiveData<Boolean> = NonNullMutableLiveData(false)
val canStudyStart: NonNullLiveData<Boolean> get() = _canStudyStart

private val _studyMemberCount: NonNullMutableLiveData<Int> = NonNullMutableLiveData(0)
val studyMemberCount: NonNullLiveData<Int> get() = _studyMemberCount

fun fetchStudyDetail(studyId: Long, notifyInvalidStudy: () -> Unit) {
viewModelScope.launch {
runCatching {
val studyDetail = studyDetailRepository.getStudyDetail(studyId).toUIModel()
studyDetail
studyDetailRepository.getStudyDetail(studyId).toUIModel()
}.onSuccess {
_study.value = it
_studyParticipants.value = it.studyMembers
_isFullMember.value = it.peopleCount == _studyParticipants.value!!.size
_state.value = it.role.toStudyDetailState()
_isFullMember.value = it.peopleCount == _studyParticipants.value.size
_state.value = it.role.toStudyDetailState(it.canStartStudy)
_studyMemberCount.value = it.memberCount
_canStudyStart.value = it.canStartStudy
if (it.role == Role.MASTER) fetchApplicants(studyId)
}.onFailure {
notifyInvalidStudy()
}
}
}
Expand All @@ -63,10 +79,10 @@ class StudyDetailViewModel private constructor(
studyDetailRepository.getStudyApplicants(studyId)
}.onSuccess { members ->
_studyParticipants.value =
_studyParticipants.value?.plus(
_studyParticipants.value.plus(
members.map {
it.toUIModel(
study.value?.studyMasterId ?: 0L,
study.value.studyMasterId,
true,
)
},
Expand All @@ -90,11 +106,13 @@ class StudyDetailViewModel private constructor(
runCatching {
studyDetailRepository.acceptApplicant(studyId, memberId)
}.onFailure { // 204 No Content가 onFailure로 가는 현상이 있습니다.
val studyParticipants = _studyParticipants.value ?: listOf()
val studyParticipants = _studyParticipants.value
val acceptedMember =
studyParticipants.find { it.id == memberId } ?: StudyMemberUIModel.DUMMY
studyParticipants.find { it.id == memberId } ?: return@launch
_studyParticipants.value =
studyParticipants.minus(acceptedMember) + acceptedMember.copy(isApplicant = false)
_canStudyStart.value = StudyDetail.canStartStudy(studyParticipants.size)
_studyMemberCount.value = _studyMemberCount.value.plus(1)
}
}
}
Expand All @@ -109,7 +127,8 @@ class StudyDetailViewModel private constructor(
startDate = this.startAt,
period = this.totalRoundCount.toString(),
cycle = this.periodOfRound,
applicantCount = this.members.count(),
memberCount = this.members.size,
canStartStudy = StudyDetail.canStartStudy(this.numberOfCurrentMembers),
studyMembers = this.members.map { it.toUIModel(this.studyMasterId, isApplicant = false) },
)

Expand All @@ -124,8 +143,8 @@ class StudyDetailViewModel private constructor(
tier = this.tier,
)

private fun Role.toStudyDetailState(): StudyDetailState = when (this) {
Role.MASTER -> StudyDetailState.Master
private fun Role.toStudyDetailState(canStartStudy: Boolean): StudyDetailState = when (this) {
Role.MASTER -> StudyDetailState.Master(canStartStudy)
Role.MEMBER -> StudyDetailState.Member
Role.APPLICANT -> StudyDetailState.Applicant
Role.NOTHING -> StudyDetailState.Nothing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ data class StudyDetailUIModel(
val startDate: String,
val period: String,
val cycle: String,
val applicantCount: Int,
val memberCount: Int,
val canStartStudy: Boolean,
val studyMembers: List<StudyMemberUIModel>,
)
) {
companion object {
val INVALID_STUDY_DETAIL = StudyDetailUIModel(
studyMasterId = 0,
isMaster = false,
title = "",
introduction = "",
peopleCount = 0,
role = Role.NOTHING,
startDate = "",
period = "",
cycle = "",
memberCount = 0,
canStartStudy = false,
studyMembers = listOf(),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,4 @@ data class StudyMemberUIModel(
val name: String,
val successRate: Int,
val tier: Int,
) {
companion object {
val DUMMY = StudyMemberUIModel(
id = 0L,
isMaster = true,
isApplicant = false,
tier = 3,
name = "bandal",
successRate = 90,
profileImageUrl = "https://opgg-com-image.akamaized.net/attach/images/20200321020018.373875.jpg",
)
}
}
)
12 changes: 6 additions & 6 deletions android/app/src/main/res/layout/activity_study_detail.xml
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
android:orientation="vertical"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toTopOf="@id/btn_study_detail_main_not_study_master"
app:layout_constraintBottom_toTopOf="@id/btn_study_detail_main"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_study_detail_study_people_title"
Expand All @@ -190,15 +190,15 @@
android:padding="16dp"
android:src="@{context.getDrawable(viewModel.state.subButtonSrc)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btn_study_detail_main_not_study_master"
app:layout_constraintEnd_toStartOf="@id/btn_study_detail_main"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
tools:src="@drawable/ic_dm" />

<TextView
android:id="@+id/btn_study_detail_main_not_study_master"
android:id="@+id/btn_study_detail_main"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginEnd="16dp"
Expand All @@ -208,15 +208,15 @@
android:enabled="@{viewModel.state.mainButtonIsEnabled}"
android:gravity="center"
android:onClick="@{() -> activity.initMainButtonOnClick(viewModel.study.isMaster)}"
android:text="@{context.getString(viewModel.state.mainButtonText, viewModel.study.applicantCount, viewModel.study.peopleCount)}"
android:text="@{context.getString(viewModel.state.mainButtonText, viewModel.study.memberCount, viewModel.study.peopleCount)}"
android:textAppearance="@style/button_sb18"
android:textColor="@{context.getColor(viewModel.state.mainButtonTextColor)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_weight="4"
app:layout_constraintStart_toEndOf="@+id/btn_study_detail_sub"
tools:text="참여하기(3/6)" />
app:layout_constraintStart_toEndOf="@id/btn_study_detail_sub"
tools:text="@string/study_detail_study_accept_waiting" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
5 changes: 3 additions & 2 deletions android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,13 @@
<string name="study_detail_study_capacity">참여하기(%d/%d)</string>
<string name="study_detail_button_start_study">시작하기(%d/%d)</string>
<string name="study_detail_dm_button_content_description">스터디장에게 문의하기</string>
<string name="study_detail_study_accept_waiting">스터디장의 수락을 기다리고있어요.</string>
<string name="study_detail_study_start_waiting">스터디 시작을 기다리고있어요.</string>
<string name="study_detail_study_accept_waiting">수락을 기다리고 있어요</string>
<string name="study_detail_study_start_waiting">시작을 기다리고 있어요</string>
<string name="study_detail_do_not_accept_member_anymore">더이상 스터디 멤버를 받을 수 없습니다.</string>
<string name="study_detail_period_of_count_day">%d일</string>
<string name="study_detail_period_of_count_week">%d주</string>
<string name="study_detail_period_format">%s회차</string>
<string name="study_detail_notify_invalid_study">스터디를 찾을 수 없습니다.</string>
<string name="item_study_detail_study_success_rate">스터디 성공률 %d%%</string>

<!--스터디 관리하기 뷰-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,11 @@ data class StudyDetail(
val introduction: String,
val members: List<Member>,
val rounds: List<Round>,
)
) {
companion object {
private const val START_MEMBER_CONDITION = 2

fun canStartStudy(numberOfCurrentMembers: Int): Boolean =
numberOfCurrentMembers >= START_MEMBER_CONDITION
}
}

0 comments on commit 3210f4c

Please sign in to comment.