fix: enforce multi-choice maxSelections limit across all answer UIs, pass questionIds to startGame
This commit is contained in:
parent
4134c4570d
commit
df961e8d94
|
|
@ -64,7 +64,8 @@ private fun parseAnswerConfig(raw: String, questionType: String) = try {
|
||||||
"single_choice", "multi_choice" -> {
|
"single_choice", "multi_choice" -> {
|
||||||
val optionsArr = configObj?.optJSONArray("options")
|
val optionsArr = configObj?.optJSONArray("options")
|
||||||
val options = parseOptions(optionsArr)
|
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(
|
"scale" -> ScaleAnswerConfigImpl(
|
||||||
config = ScaleAnswerConfig(
|
config = ScaleAnswerConfig(
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,6 @@ object QuestionJsonParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"multi_choice" -> {
|
"multi_choice" -> {
|
||||||
// For now, treat multi_choice like single_choice (can be extended later)
|
|
||||||
val options = mutableListOf<ChoiceOption>()
|
val options = mutableListOf<ChoiceOption>()
|
||||||
val optionsArray = obj.optJSONArray("options")
|
val optionsArray = obj.optJSONArray("options")
|
||||||
if (optionsArray != null) {
|
if (optionsArray != null) {
|
||||||
|
|
@ -188,7 +187,10 @@ object QuestionJsonParser {
|
||||||
}
|
}
|
||||||
ChoiceAnswerConfigImpl(
|
ChoiceAnswerConfigImpl(
|
||||||
type = "multi_choice",
|
type = "multi_choice",
|
||||||
config = ChoiceAnswerConfig(options = options.toList())
|
config = ChoiceAnswerConfig(
|
||||||
|
options = options.toList(),
|
||||||
|
maxSelections = answerConfigObj.optInt("max_selections", 0)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ data class WrittenAnswerConfig(
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ChoiceAnswerConfig(
|
data class ChoiceAnswerConfig(
|
||||||
val options: List<ChoiceOption>
|
val options: List<ChoiceOption>,
|
||||||
|
val maxSelections: Int = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ChoiceOption(
|
data class ChoiceOption(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.closer.core.crash.CrashReporter
|
import app.closer.core.crash.CrashReporter
|
||||||
import app.closer.data.remote.FirestoreAnswerDataSource
|
import app.closer.data.remote.FirestoreAnswerDataSource
|
||||||
|
import app.closer.domain.model.ChoiceAnswerConfigImpl
|
||||||
import app.closer.domain.model.LocalAnswer
|
import app.closer.domain.model.LocalAnswer
|
||||||
import app.closer.domain.model.Question
|
import app.closer.domain.model.Question
|
||||||
import app.closer.domain.repository.AuthRepository
|
import app.closer.domain.repository.AuthRepository
|
||||||
|
|
@ -120,10 +121,11 @@ class DailyQuestionViewModel @Inject constructor(
|
||||||
_uiState.update { state ->
|
_uiState.update { state ->
|
||||||
val question = state.question ?: return@update state
|
val question = state.question ?: return@update state
|
||||||
val updated = if (question.type == "multi_choice") {
|
val updated = if (question.type == "multi_choice") {
|
||||||
if (optionId in state.pendingSelectedOptionIds) {
|
val maxSel = (question.answerConfig as? ChoiceAnswerConfigImpl)?.config?.maxSelections ?: 0
|
||||||
state.pendingSelectedOptionIds - optionId
|
when {
|
||||||
} else {
|
optionId in state.pendingSelectedOptionIds -> state.pendingSelectedOptionIds - optionId
|
||||||
state.pendingSelectedOptionIds + optionId
|
maxSel == 0 || state.pendingSelectedOptionIds.size < maxSel -> state.pendingSelectedOptionIds + optionId
|
||||||
|
else -> state.pendingSelectedOptionIds
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
listOf(optionId)
|
listOf(optionId)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package app.closer.ui.questions
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import app.closer.domain.model.ChoiceAnswerConfigImpl
|
||||||
import app.closer.domain.repository.LocalAnswerRepository
|
import app.closer.domain.repository.LocalAnswerRepository
|
||||||
import app.closer.domain.repository.QuestionRepository
|
import app.closer.domain.repository.QuestionRepository
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
|
@ -57,10 +58,11 @@ class QuestionDetailViewModel @Inject constructor(
|
||||||
_uiState.update { state ->
|
_uiState.update { state ->
|
||||||
val question = state.question ?: return@update state
|
val question = state.question ?: return@update state
|
||||||
val updated = if (question.type == "multi_choice") {
|
val updated = if (question.type == "multi_choice") {
|
||||||
if (optionId in state.pendingSelectedOptionIds) {
|
val maxSel = (question.answerConfig as? ChoiceAnswerConfigImpl)?.config?.maxSelections ?: 0
|
||||||
state.pendingSelectedOptionIds - optionId
|
when {
|
||||||
} else {
|
optionId in state.pendingSelectedOptionIds -> state.pendingSelectedOptionIds - optionId
|
||||||
state.pendingSelectedOptionIds + optionId
|
maxSel == 0 || state.pendingSelectedOptionIds.size < maxSel -> state.pendingSelectedOptionIds + optionId
|
||||||
|
else -> state.pendingSelectedOptionIds
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
listOf(optionId)
|
listOf(optionId)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.closer.data.local.QuestionDao
|
import app.closer.data.local.QuestionDao
|
||||||
import app.closer.data.local.mapper.toQuestion
|
import app.closer.data.local.mapper.toQuestion
|
||||||
|
import app.closer.domain.model.ChoiceAnswerConfigImpl
|
||||||
import app.closer.domain.model.Question
|
import app.closer.domain.model.Question
|
||||||
import app.closer.domain.model.QuestionAnswer
|
import app.closer.domain.model.QuestionAnswer
|
||||||
import app.closer.domain.model.QuestionMessage
|
import app.closer.domain.model.QuestionMessage
|
||||||
|
|
@ -124,6 +125,13 @@ class QuestionThreadViewModel @Inject constructor(
|
||||||
val current = state.pendingSelectedOptionIds
|
val current = state.pendingSelectedOptionIds
|
||||||
val updated = if (question.type == "single_choice") {
|
val updated = if (question.type == "single_choice") {
|
||||||
listOf(optionId)
|
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 {
|
} else {
|
||||||
if (optionId in current) current - optionId else current + optionId
|
if (optionId in current) current - optionId else current + optionId
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,11 +112,13 @@ class SpinWheelViewModel @Inject constructor(
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val questionIds = sessionStore.activeSession?.questions?.map { it.id }
|
||||||
val startResult = runCatching {
|
val startResult = runCatching {
|
||||||
gameSessionManager.startGame(
|
gameSessionManager.startGame(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
gameType = "wheel",
|
gameType = "wheel",
|
||||||
categoryId = categoryId
|
categoryId = categoryId,
|
||||||
|
questionIds = questionIds
|
||||||
).getOrNull()
|
).getOrNull()
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,15 @@ class WheelSessionViewModel @Inject constructor(
|
||||||
fun selectOption(optionId: String) {
|
fun selectOption(optionId: String) {
|
||||||
val question = _uiState.value.questions.getOrNull(_uiState.value.currentIndex) ?: return
|
val question = _uiState.value.questions.getOrNull(_uiState.value.currentIndex) ?: return
|
||||||
if (question.type == "multi_choice") {
|
if (question.type == "multi_choice") {
|
||||||
|
val maxSel = (question.answerConfig as? app.closer.domain.model.ChoiceAnswerConfigImpl)
|
||||||
|
?.config?.maxSelections ?: 0
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
val current = it.selectedOptionIds.toMutableList()
|
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)
|
it.copy(selectedOptionIds = current)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue