fix: enforce multi-choice maxSelections limit across all answer UIs, pass questionIds to startGame

This commit is contained in:
null 2026-06-18 02:41:33 -05:00
parent 4134c4570d
commit df961e8d94
8 changed files with 38 additions and 14 deletions

View File

@ -64,7 +64,8 @@ private fun parseAnswerConfig(raw: String, questionType: String) = try {
"single_choice", "multi_choice" -> {
val optionsArr = configObj?.optJSONArray("options")
val options = parseOptions(optionsArr)
ChoiceAnswerConfigImpl(type = questionType, config = ChoiceAnswerConfig(options = options))
val maxSel = if (questionType == "multi_choice") configObj?.optInt("maxSelections", 0) ?: 0 else 0
ChoiceAnswerConfigImpl(type = questionType, config = ChoiceAnswerConfig(options = options, maxSelections = maxSel))
}
"scale" -> ScaleAnswerConfigImpl(
config = ScaleAnswerConfig(

View File

@ -172,7 +172,6 @@ object QuestionJsonParser {
}
}
"multi_choice" -> {
// For now, treat multi_choice like single_choice (can be extended later)
val options = mutableListOf<ChoiceOption>()
val optionsArray = obj.optJSONArray("options")
if (optionsArray != null) {
@ -188,7 +187,10 @@ object QuestionJsonParser {
}
ChoiceAnswerConfigImpl(
type = "multi_choice",
config = ChoiceAnswerConfig(options = options.toList())
config = ChoiceAnswerConfig(
options = options.toList(),
maxSelections = answerConfigObj.optInt("max_selections", 0)
)
)
}
else -> null

View File

@ -8,7 +8,8 @@ data class WrittenAnswerConfig(
)
data class ChoiceAnswerConfig(
val options: List<ChoiceOption>
val options: List<ChoiceOption>,
val maxSelections: Int = 0
)
data class ChoiceOption(

View File

@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.closer.core.crash.CrashReporter
import app.closer.data.remote.FirestoreAnswerDataSource
import app.closer.domain.model.ChoiceAnswerConfigImpl
import app.closer.domain.model.LocalAnswer
import app.closer.domain.model.Question
import app.closer.domain.repository.AuthRepository
@ -120,10 +121,11 @@ class DailyQuestionViewModel @Inject constructor(
_uiState.update { state ->
val question = state.question ?: return@update state
val updated = if (question.type == "multi_choice") {
if (optionId in state.pendingSelectedOptionIds) {
state.pendingSelectedOptionIds - optionId
} else {
state.pendingSelectedOptionIds + optionId
val maxSel = (question.answerConfig as? ChoiceAnswerConfigImpl)?.config?.maxSelections ?: 0
when {
optionId in state.pendingSelectedOptionIds -> state.pendingSelectedOptionIds - optionId
maxSel == 0 || state.pendingSelectedOptionIds.size < maxSel -> state.pendingSelectedOptionIds + optionId
else -> state.pendingSelectedOptionIds
}
} else {
listOf(optionId)

View File

@ -3,6 +3,7 @@ package app.closer.ui.questions
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.closer.domain.model.ChoiceAnswerConfigImpl
import app.closer.domain.repository.LocalAnswerRepository
import app.closer.domain.repository.QuestionRepository
import dagger.hilt.android.lifecycle.HiltViewModel
@ -57,10 +58,11 @@ class QuestionDetailViewModel @Inject constructor(
_uiState.update { state ->
val question = state.question ?: return@update state
val updated = if (question.type == "multi_choice") {
if (optionId in state.pendingSelectedOptionIds) {
state.pendingSelectedOptionIds - optionId
} else {
state.pendingSelectedOptionIds + optionId
val maxSel = (question.answerConfig as? ChoiceAnswerConfigImpl)?.config?.maxSelections ?: 0
when {
optionId in state.pendingSelectedOptionIds -> state.pendingSelectedOptionIds - optionId
maxSel == 0 || state.pendingSelectedOptionIds.size < maxSel -> state.pendingSelectedOptionIds + optionId
else -> state.pendingSelectedOptionIds
}
} else {
listOf(optionId)

View File

@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.closer.data.local.QuestionDao
import app.closer.data.local.mapper.toQuestion
import app.closer.domain.model.ChoiceAnswerConfigImpl
import app.closer.domain.model.Question
import app.closer.domain.model.QuestionAnswer
import app.closer.domain.model.QuestionMessage
@ -124,6 +125,13 @@ class QuestionThreadViewModel @Inject constructor(
val current = state.pendingSelectedOptionIds
val updated = if (question.type == "single_choice") {
listOf(optionId)
} else if (question.type == "multi_choice") {
val maxSel = (question.answerConfig as? ChoiceAnswerConfigImpl)?.config?.maxSelections ?: 0
when {
optionId in current -> current - optionId
maxSel == 0 || current.size < maxSel -> current + optionId
else -> current
}
} else {
if (optionId in current) current - optionId else current + optionId
}

View File

@ -112,11 +112,13 @@ class SpinWheelViewModel @Inject constructor(
return@launch
}
val questionIds = sessionStore.activeSession?.questions?.map { it.id }
val startResult = runCatching {
gameSessionManager.startGame(
userId = userId,
gameType = "wheel",
categoryId = categoryId
categoryId = categoryId,
questionIds = questionIds
).getOrNull()
}.getOrNull()

View File

@ -61,9 +61,15 @@ class WheelSessionViewModel @Inject constructor(
fun selectOption(optionId: String) {
val question = _uiState.value.questions.getOrNull(_uiState.value.currentIndex) ?: return
if (question.type == "multi_choice") {
val maxSel = (question.answerConfig as? app.closer.domain.model.ChoiceAnswerConfigImpl)
?.config?.maxSelections ?: 0
_uiState.update {
val current = it.selectedOptionIds.toMutableList()
if (optionId in current) current.remove(optionId) else current.add(optionId)
if (optionId in current) {
current.remove(optionId)
} else if (maxSel == 0 || current.size < maxSel) {
current.add(optionId)
}
it.copy(selectedOptionIds = current)
}
} else {