diff --git a/app/src/main/java/app/closer/domain/model/SessionLength.kt b/app/src/main/java/app/closer/domain/model/SessionLength.kt new file mode 100644 index 00000000..4184778b --- /dev/null +++ b/app/src/main/java/app/closer/domain/model/SessionLength.kt @@ -0,0 +1,7 @@ +package app.closer.domain.model + +enum class SessionLength(val label: String, val count: Int) { + SHORT("5 · Quick", 5), + STANDARD("10 · Standard", 10), + LONG("15 · Long", 15) +} diff --git a/app/src/main/java/app/closer/ui/desiresync/DesireSyncScreen.kt b/app/src/main/java/app/closer/ui/desiresync/DesireSyncScreen.kt index cb8ed1b9..644d68b3 100644 --- a/app/src/main/java/app/closer/ui/desiresync/DesireSyncScreen.kt +++ b/app/src/main/java/app/closer/ui/desiresync/DesireSyncScreen.kt @@ -5,6 +5,7 @@ import app.closer.ui.theme.closerCardColor import android.util.Log import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -55,11 +56,15 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.closer.core.navigation.AppRoute import app.closer.data.remote.FirestoreDesireSyncDataSource +import android.content.Context +import android.provider.Settings import app.closer.domain.model.ChoiceAnswerConfigImpl import app.closer.domain.model.Question +import app.closer.domain.model.SessionLength import app.closer.domain.repository.QuestionRepository import app.closer.domain.repository.QuestionSessionRepository import app.closer.domain.usecase.GameSessionManager +import dagger.hilt.android.qualifiers.ApplicationContext import app.closer.ui.components.StatusGlyph import app.closer.ui.theme.CloserPalette import app.closer.ui.theme.closerBackgroundBrush @@ -80,7 +85,7 @@ data class DesireMatch( val question: Question ) -enum class DesireSyncPhase { LOADING, INTRO, ANSWER, WAITING, REVEAL, ERROR } +enum class DesireSyncPhase { LOADING, SETUP, INTRO, ANSWER, WAITING, REVEAL, ERROR } data class DesireSyncUiState( val phase: DesireSyncPhase = DesireSyncPhase.LOADING, @@ -90,6 +95,7 @@ data class DesireSyncUiState( val myAnswers: List = emptyList(), val partnerName: String = "Your partner", val matches: List = emptyList(), + val selectedLength: SessionLength = SessionLength.STANDARD, val error: String? = null, val navigateTo: String? = null ) @@ -106,6 +112,7 @@ private fun isBinaryQuestion(q: Question): Boolean { @HiltViewModel class DesireSyncViewModel @Inject constructor( + @ApplicationContext private val context: Context, private val repository: QuestionRepository, private val gameSessionManager: GameSessionManager, private val dataSource: FirestoreDesireSyncDataSource @@ -152,14 +159,24 @@ class DesireSyncViewModel @Inject constructor( // A different game is already in progress — respect the one-game lock. _uiState.update { it.copy(navigateTo = AppRoute.WAITING_FOR_PARTNER) } else -> - createSession(uid) + _uiState.update { it.copy(phase = DesireSyncPhase.SETUP) } } } } + fun setLength(len: SessionLength) { + _uiState.update { it.copy(selectedLength = len) } + } + + fun startGame() { + val uid = userId ?: return + _uiState.update { it.copy(phase = DesireSyncPhase.LOADING) } + viewModelScope.launch { createSession(uid) } + } + /** First partner: pick the neutral question set and open the shared session. */ private suspend fun createSession(uid: String) { - val questions = loadNeutralQuestions().shuffled().take(SESSION_SIZE) + val questions = loadNeutralQuestions().shuffled().take(_uiState.value.selectedLength.count) if (questions.isEmpty()) return fail("No questions available.") val startResult = runCatching { @@ -214,7 +231,10 @@ class DesireSyncViewModel @Inject constructor( if (s.pendingSelection != null || s.phase != DesireSyncPhase.ANSWER) return _uiState.update { it.copy(pendingSelection = optionId) } viewModelScope.launch { - delay(ADVANCE_DELAY_MS) + val reduceMotion = Settings.Global.getFloat( + context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f + ) == 0f + delay(if (reduceMotion) 0L else ADVANCE_DELAY_MS) val answers = _uiState.value.myAnswers + optionId val next = _uiState.value.currentIndex + 1 if (next >= _uiState.value.questions.size) { @@ -322,7 +342,6 @@ class DesireSyncViewModel @Inject constructor( } companion object { - private const val SESSION_SIZE = 10 private const val ADVANCE_DELAY_MS = 380L private const val TAG = "DesireSyncViewModel" } @@ -358,6 +377,12 @@ fun DesireSyncScreen( message = state.error ?: "Something went wrong.", onBack = viewModel::quit ) + DesireSyncPhase.SETUP -> DSSetupScreen( + selectedLength = state.selectedLength, + onLengthSelected = viewModel::setLength, + onStart = viewModel::startGame, + onBack = viewModel::quit + ) DesireSyncPhase.INTRO -> DSIntroScreen( total = state.questions.size, onReady = viewModel::startAnswering @@ -391,6 +416,72 @@ fun DesireSyncScreen( // ── Phase screens ───────────────────────────────────────────────────────────── +@Composable +private fun DSSetupScreen( + selectedLength: SessionLength, + onLengthSelected: (SessionLength) -> Unit, + onStart: () -> Unit, + onBack: () -> Unit +) { + Column( + modifier = Modifier + .fillMaxSize() + .safeDrawingPadding() + .navigationBarsPadding() + .padding(horizontal = 24.dp, vertical = 24.dp), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "How long?", + style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.SemiBold) + ) + TextButton(onClick = onBack) { + Text("Back", color = MaterialTheme.colorScheme.onSurfaceVariant) + } + } + Text( + text = "Choose a round length. Your partner will answer the same questions.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + SessionLength.values().forEach { len -> + Surface( + onClick = { onLengthSelected(len) }, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(12.dp), + color = if (len == selectedLength) CloserPalette.Romantic else Color.Transparent, + border = if (len != selectedLength) + BorderStroke(1.dp, CloserPalette.Romantic.copy(alpha = 0.4f)) + else null + ) { + Text( + text = len.label, + modifier = Modifier.padding(vertical = 10.dp), + style = MaterialTheme.typography.labelMedium, + color = if (len == selectedLength) Color.White else CloserPalette.Romantic, + textAlign = TextAlign.Center + ) + } + } + } + Spacer(Modifier.weight(1f)) + Button( + onClick = onStart, + modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp), + shape = RoundedCornerShape(18.dp), + colors = ButtonDefaults.buttonColors(containerColor = CloserPalette.Romantic) + ) { + Text("Create session") + } + } +} + @Composable private fun DSIntroScreen(total: Int, onReady: () -> Unit) { Column( diff --git a/app/src/main/java/app/closer/ui/howwell/HowWellScreen.kt b/app/src/main/java/app/closer/ui/howwell/HowWellScreen.kt index 1f52ef61..e9b6e041 100644 --- a/app/src/main/java/app/closer/ui/howwell/HowWellScreen.kt +++ b/app/src/main/java/app/closer/ui/howwell/HowWellScreen.kt @@ -3,6 +3,7 @@ package app.closer.ui.howwell import app.closer.domain.model.GameType import app.closer.ui.theme.closerCardColor import android.util.Log +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -57,12 +58,15 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.closer.core.navigation.AppRoute +import android.content.Context import app.closer.domain.model.AnswerConfig import app.closer.domain.model.ChoiceAnswerConfigImpl import app.closer.domain.model.ChoiceOption import app.closer.domain.model.Question import app.closer.domain.model.ScaleAnswerConfigImpl +import app.closer.domain.model.SessionLength import app.closer.domain.model.ThisOrThatAnswerConfigImpl +import dagger.hilt.android.qualifiers.ApplicationContext import app.closer.domain.repository.QuestionRepository import app.closer.domain.repository.QuestionSessionRepository import app.closer.data.remote.FirestoreHowWellDataSource @@ -122,7 +126,7 @@ data class HowWellResult( val isClose: Boolean ) -enum class HowWellPhase { LOADING, INTRO, ANSWER, WAITING, COMPLETE, ERROR } +enum class HowWellPhase { LOADING, SETUP, INTRO, ANSWER, WAITING, COMPLETE, ERROR } data class HowWellUiState( val phase: HowWellPhase = HowWellPhase.LOADING, @@ -135,6 +139,7 @@ data class HowWellUiState( val partnerName: String = "Your partner", val results: List = emptyList(), val score: Int = 0, + val selectedLength: SessionLength = SessionLength.STANDARD, val error: String? = null, val navigateTo: String? = null ) @@ -143,6 +148,7 @@ data class HowWellUiState( @HiltViewModel class HowWellViewModel @Inject constructor( + @ApplicationContext private val context: Context, private val repository: QuestionRepository, private val gameSessionManager: GameSessionManager, private val dataSource: FirestoreHowWellDataSource @@ -188,18 +194,28 @@ class HowWellViewModel @Inject constructor( // A different game is already in progress — respect the one-game lock. _uiState.update { it.copy(navigateTo = AppRoute.WAITING_FOR_PARTNER) } else -> - createSession(uid) + _uiState.update { it.copy(phase = HowWellPhase.SETUP) } } } } + fun setLength(len: SessionLength) { + _uiState.update { it.copy(selectedLength = len) } + } + + fun startGame() { + val uid = userId ?: return + _uiState.update { it.copy(phase = HowWellPhase.LOADING) } + viewModelScope.launch { createSession(uid) } + } + /** First partner becomes the subject: they answer about themselves. */ private suspend fun createSession(uid: String) { val questions = runCatching { repository.getQuestionsForPrediction() } .onFailure { Log.w(TAG, "Failed to load prediction questions", it) } .getOrElse { emptyList() } .shuffled() - .take(SESSION_SIZE) + .take(_uiState.value.selectedLength.count) if (questions.isEmpty()) return fail("No questions available.") val startResult = runCatching { @@ -371,7 +387,6 @@ class HowWellViewModel @Inject constructor( } companion object { - const val SESSION_SIZE = 10 private const val TAG = "HowWellViewModel" } } @@ -406,6 +421,12 @@ fun HowWellScreen( message = state.error ?: "Something went wrong.", onBack = viewModel::quit ) + HowWellPhase.SETUP -> HWSetupScreen( + selectedLength = state.selectedLength, + onLengthSelected = viewModel::setLength, + onStart = viewModel::startGame, + onBack = viewModel::quit + ) HowWellPhase.INTRO -> PlayerIntroScreen( amSubject = state.amSubject, partnerName = state.partnerName, @@ -449,6 +470,72 @@ fun HowWellScreen( // ── Phase screens ───────────────────────────────────────────────────────────── +@Composable +private fun HWSetupScreen( + selectedLength: SessionLength, + onLengthSelected: (SessionLength) -> Unit, + onStart: () -> Unit, + onBack: () -> Unit +) { + Column( + modifier = Modifier + .fillMaxSize() + .safeDrawingPadding() + .navigationBarsPadding() + .padding(horizontal = 24.dp, vertical = 24.dp), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "How long?", + style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.SemiBold) + ) + TextButton(onClick = onBack) { + Text("Back", color = MaterialTheme.colorScheme.onSurfaceVariant) + } + } + Text( + text = "Choose a round length. You'll answer about yourself and your partner will try to guess.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + SessionLength.values().forEach { len -> + Surface( + onClick = { onLengthSelected(len) }, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(12.dp), + color = if (len == selectedLength) CloserPalette.PurpleDeep else Color.Transparent, + border = if (len != selectedLength) + BorderStroke(1.dp, CloserPalette.PurpleDeep.copy(alpha = 0.4f)) + else null + ) { + Text( + text = len.label, + modifier = Modifier.padding(vertical = 10.dp), + style = MaterialTheme.typography.labelMedium, + color = if (len == selectedLength) Color.White else CloserPalette.PurpleDeep, + textAlign = TextAlign.Center + ) + } + } + } + Spacer(Modifier.weight(1f)) + Button( + onClick = onStart, + modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp), + shape = RoundedCornerShape(18.dp), + colors = ButtonDefaults.buttonColors(containerColor = CloserPalette.PurpleDeep) + ) { + Text("Create session") + } + } +} + @Composable private fun PlayerIntroScreen( amSubject: Boolean, diff --git a/app/src/main/java/app/closer/ui/thisorthat/ThisOrThatScreen.kt b/app/src/main/java/app/closer/ui/thisorthat/ThisOrThatScreen.kt index 6bf8e6ab..5d7e4615 100644 --- a/app/src/main/java/app/closer/ui/thisorthat/ThisOrThatScreen.kt +++ b/app/src/main/java/app/closer/ui/thisorthat/ThisOrThatScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Arrangement @@ -66,11 +67,15 @@ import app.closer.core.navigation.AppRoute import app.closer.data.remote.FirestoreThisOrThatDataSource import app.closer.domain.model.ChoiceOption import app.closer.domain.model.Question +import app.closer.domain.model.SessionLength import app.closer.domain.model.ThisOrThatAnswerConfig import app.closer.domain.model.ThisOrThatAnswerConfigImpl import app.closer.domain.repository.QuestionRepository import app.closer.domain.repository.QuestionSessionRepository import app.closer.domain.usecase.GameSessionManager +import android.content.Context +import android.provider.Settings +import dagger.hilt.android.qualifiers.ApplicationContext import app.closer.ui.theme.CloserPalette import app.closer.ui.theme.closerBackgroundBrush import dagger.hilt.android.lifecycle.HiltViewModel @@ -131,12 +136,14 @@ data class ThisOrThatUiState( val partnerName: String = "Your partner", val matchedCount: Int = 0, val revealCards: List = emptyList(), + val selectedLength: SessionLength = SessionLength.STANDARD, val error: String? = null, val navigateTo: String? = null ) @HiltViewModel class ThisOrThatViewModel @Inject constructor( + @ApplicationContext private val context: Context, private val repository: QuestionRepository, private val gameSessionManager: GameSessionManager, private val dataSource: FirestoreThisOrThatDataSource @@ -188,6 +195,10 @@ class ThisOrThatViewModel @Inject constructor( } } + fun setLength(len: SessionLength) { + _uiState.update { it.copy(selectedLength = len) } + } + fun chooseMood(mood: TotMood) { val uid = userId ?: return fail("You need to be signed in to play.") _uiState.update { it.copy(phase = TotPhase.LOADING, error = null) } @@ -202,10 +213,11 @@ class ThisOrThatViewModel @Inject constructor( val moodQuestions = mood.categoryIds ?.let { categoryIds -> allQuestions.filter { it.category in categoryIds } } ?: allQuestions - val pool = moodQuestions.takeIf { it.size >= SESSION_SIZE } ?: allQuestions + val count = _uiState.value.selectedLength.count + val pool = moodQuestions.takeIf { it.size >= count } ?: allQuestions val picked = pool .shuffled() - .take(SESSION_SIZE) + .take(count) if (picked.isEmpty()) return fail("No questions available.") val startResult = runCatching { @@ -268,7 +280,10 @@ class ThisOrThatViewModel @Inject constructor( if (s.pendingSelection != null || s.phase != TotPhase.PLAYING) return _uiState.update { it.copy(pendingSelection = optionId) } viewModelScope.launch { - delay(ADVANCE_DELAY_MS) + val reduceMotion = Settings.Global.getFloat( + context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f + ) == 0f + delay(if (reduceMotion) 0L else ADVANCE_DELAY_MS) val answers = _uiState.value.myAnswers + optionId val next = _uiState.value.currentIndex + 1 if (next >= _uiState.value.questions.size) { @@ -374,7 +389,6 @@ class ThisOrThatViewModel @Inject constructor( } companion object { - const val SESSION_SIZE = 10 private const val ADVANCE_DELAY_MS = 420L private const val TAG = "ThisOrThatViewModel" } @@ -416,6 +430,8 @@ fun ThisOrThatScreen( onAbandon = viewModel::abandon ) TotPhase.PICK_MOOD -> ThisOrThatMoodPicker( + selectedLength = state.selectedLength, + onLengthSelected = viewModel::setLength, onMoodSelected = viewModel::chooseMood, onBack = viewModel::quit ) @@ -453,6 +469,8 @@ fun ThisOrThatScreen( @Composable private fun ThisOrThatMoodPicker( + selectedLength: SessionLength, + onLengthSelected: (SessionLength) -> Unit, onMoodSelected: (TotMood) -> Unit, onBack: () -> Unit ) { @@ -489,6 +507,10 @@ private fun ThisOrThatMoodPicker( } } + item { + TotLengthChips(selected = selectedLength, onSelect = onLengthSelected) + } + items(TotMood.values().toList()) { mood -> Card( onClick = { onMoodSelected(mood) }, @@ -1131,6 +1153,33 @@ private fun ErrorState(message: String, onBack: () -> Unit) { } } +// ── Length picker ───────────────────────────────────────────────────────────── + +@Composable +private fun TotLengthChips(selected: SessionLength, onSelect: (SessionLength) -> Unit) { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + SessionLength.values().forEach { len -> + Surface( + onClick = { onSelect(len) }, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(12.dp), + color = if (len == selected) CloserPalette.PurpleDeep else Color.Transparent, + border = if (len != selected) + BorderStroke(1.dp, CloserPalette.PurpleDeep.copy(alpha = 0.4f)) + else null + ) { + Text( + text = len.label, + modifier = Modifier.padding(vertical = 10.dp), + style = MaterialTheme.typography.labelMedium, + color = if (len == selected) Color.White else CloserPalette.PurpleDeep, + textAlign = TextAlign.Center + ) + } + } + } +} + // ── History Replay ──────────────────────────────────────────────────────────── sealed interface TotReplayPhase { diff --git a/app/src/main/java/app/closer/ui/wheel/SpinWheelScreen.kt b/app/src/main/java/app/closer/ui/wheel/SpinWheelScreen.kt index 93641046..e539c338 100644 --- a/app/src/main/java/app/closer/ui/wheel/SpinWheelScreen.kt +++ b/app/src/main/java/app/closer/ui/wheel/SpinWheelScreen.kt @@ -30,6 +30,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.foundation.layout.Row +import app.closer.domain.model.SessionLength import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -69,7 +71,8 @@ fun SpinWheelScreen( SpinWheelContent( state = state, onSpin = viewModel::spin, - onStart = viewModel::startSession + onStart = viewModel::startSession, + onLengthSelected = viewModel::setLength ) } @@ -77,7 +80,8 @@ fun SpinWheelScreen( private fun SpinWheelContent( state: SpinWheelUiState, onSpin: () -> Unit, - onStart: () -> Unit + onStart: () -> Unit, + onLengthSelected: (SessionLength) -> Unit = {} ) { val infiniteTransition = rememberInfiniteTransition(label = "wheel") val rotation by infiniteTransition.animateFloat( @@ -149,6 +153,12 @@ private fun SpinWheelContent( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(14.dp) ) { + if (!state.isLoading && !state.isSpinning) { + WheelLengthChips( + selected = state.selectedLength, + onSelect = onLengthSelected + ) + } when { state.error != null -> Text( text = state.error, @@ -159,7 +169,7 @@ private fun SpinWheelContent( ) state.spunAndReady -> { Text( - text = "${SpinWheelViewModel.SESSION_SIZE} questions selected", + text = "${state.selectedLength.count} questions selected", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center, @@ -192,7 +202,7 @@ private fun SpinWheelContent( state.isLoading -> CircularProgressIndicator(color = CloserPalette.PurpleDeep) else -> { Text( - text = "Tap to select ${SpinWheelViewModel.SESSION_SIZE} questions at random", + text = "Tap to select ${state.selectedLength.count} questions at random", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center, @@ -311,6 +321,37 @@ private fun WheelSpinner( } } +@Composable +private fun WheelLengthChips( + selected: SessionLength, + onSelect: (SessionLength) -> Unit +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + SessionLength.values().forEach { len -> + Surface( + onClick = { onSelect(len) }, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(12.dp), + color = if (len == selected) CloserPalette.PurpleDeep else Color.Transparent, + border = if (len != selected) + BorderStroke(1.dp, CloserPalette.PurpleDeep.copy(alpha = 0.4f)) + else null + ) { + Text( + text = len.label, + modifier = Modifier.padding(vertical = 10.dp), + style = MaterialTheme.typography.labelMedium, + color = if (len == selected) Color.White else CloserPalette.PurpleDeep, + textAlign = TextAlign.Center + ) + } + } + } +} + @Preview @Composable fun SpinWheelScreenPreview() { diff --git a/app/src/main/java/app/closer/ui/wheel/SpinWheelViewModel.kt b/app/src/main/java/app/closer/ui/wheel/SpinWheelViewModel.kt index e9bfd4e6..d7c6e889 100644 --- a/app/src/main/java/app/closer/ui/wheel/SpinWheelViewModel.kt +++ b/app/src/main/java/app/closer/ui/wheel/SpinWheelViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.closer.core.navigation.AppRoute import app.closer.domain.model.GameType +import app.closer.domain.model.SessionLength import app.closer.domain.repository.QuestionRepository import app.closer.domain.usecase.GameSessionManager import dagger.hilt.android.lifecycle.HiltViewModel @@ -21,6 +22,7 @@ data class SpinWheelUiState( val categoryName: String = "", val isSpinning: Boolean = false, val spunAndReady: Boolean = false, + val selectedLength: SessionLength = SessionLength.STANDARD, val error: String? = null, val navigateTo: String? = null ) @@ -61,7 +63,7 @@ class SpinWheelViewModel @Inject constructor( viewModelScope.launch { _uiState.update { it.copy(isSpinning = true, error = null) } val questions = runCatching { - repository.getQuestionsByCategory(categoryId).shuffled().take(SESSION_SIZE) + repository.getQuestionsByCategory(categoryId).shuffled().take(_uiState.value.selectedLength.count) } .onFailure { Log.w(TAG, "Could not load wheel questions", it) } .getOrElse { emptyList() } @@ -139,12 +141,16 @@ class SpinWheelViewModel @Inject constructor( } } + fun setLength(len: SessionLength) { + // Resets spunAndReady so the user re-spins with the new count. + _uiState.update { it.copy(selectedLength = len, spunAndReady = false) } + } + fun onNavigated() { _uiState.update { it.copy(navigateTo = null) } } companion object { - const val SESSION_SIZE = 10 private const val TAG = "SpinWheelViewModel" } }