refactor: replace raw string game types with GameType constants, remove unused feature flag module
This commit is contained in:
parent
debc6aed02
commit
574fed27f7
|
|
@ -1,140 +0,0 @@
|
|||
package app.closer.core.feature
|
||||
|
||||
/**
|
||||
* Feature flag definition.
|
||||
* Each feature has a stable key string, billing status, and priority.
|
||||
*
|
||||
* @property key Unique identifier for this feature (used in config, analytics, Remote Config)
|
||||
* @property status FREE or PREMIUM
|
||||
* @property priority MVP or LATER
|
||||
* @property description Human-readable description
|
||||
*/
|
||||
sealed class FeatureFlag(
|
||||
val key: String,
|
||||
val status: FeatureStatus,
|
||||
val priority: FeaturePriority,
|
||||
val description: String
|
||||
) {
|
||||
data object DAILY_QUESTION : FeatureFlag(
|
||||
key = "DAILY_QUESTION",
|
||||
status = FeatureStatus.FREE,
|
||||
priority = FeaturePriority.MVP,
|
||||
description = "Answer one daily relationship question"
|
||||
)
|
||||
|
||||
data object ANSWER_HISTORY : FeatureFlag(
|
||||
key = "ANSWER_HISTORY",
|
||||
status = FeatureStatus.FREE,
|
||||
priority = FeaturePriority.MVP,
|
||||
description = "View limited recent answer history"
|
||||
)
|
||||
|
||||
data object BASIC_CATEGORIES : FeatureFlag(
|
||||
key = "BASIC_CATEGORIES",
|
||||
status = FeatureStatus.FREE,
|
||||
priority = FeaturePriority.MVP,
|
||||
description = "Basic question categories"
|
||||
)
|
||||
|
||||
data object BASIC_REMINDERS : FeatureFlag(
|
||||
key = "BASIC_REMINDERS",
|
||||
status = FeatureStatus.FREE,
|
||||
priority = FeaturePriority.MVP,
|
||||
description = "Basic push reminders"
|
||||
)
|
||||
|
||||
data object STREAK_TRACKING : FeatureFlag(
|
||||
key = "STREAK_TRACKING",
|
||||
status = FeatureStatus.FREE,
|
||||
priority = FeaturePriority.MVP,
|
||||
description = "Daily answer streak tracking"
|
||||
)
|
||||
|
||||
data object SPIN_WHEEL_LIMITED : FeatureFlag(
|
||||
key = "SPIN_WHEEL_LIMITED",
|
||||
status = FeatureStatus.FREE,
|
||||
priority = FeaturePriority.MVP,
|
||||
description = "Limited spin wheel sessions"
|
||||
)
|
||||
|
||||
data object PREMIUM_PACKS : FeatureFlag(
|
||||
key = "PREMIUM_PACKS",
|
||||
status = FeatureStatus.PREMIUM,
|
||||
priority = FeaturePriority.MVP,
|
||||
description = "Premium question packs"
|
||||
)
|
||||
|
||||
data object FULL_SPIN_WHEEL : FeatureFlag(
|
||||
key = "FULL_SPIN_WHEEL",
|
||||
status = FeatureStatus.PREMIUM,
|
||||
priority = FeaturePriority.MVP,
|
||||
description = "All spin wheel categories + saved sessions"
|
||||
)
|
||||
|
||||
data object UNLIMITED_QUESTIONS : FeatureFlag(
|
||||
key = "UNLIMITED_QUESTIONS",
|
||||
status = FeatureStatus.PREMIUM,
|
||||
priority = FeaturePriority.LATER,
|
||||
description = "Unlimited daily questions"
|
||||
)
|
||||
|
||||
data object FULL_HISTORY : FeatureFlag(
|
||||
key = "FULL_HISTORY",
|
||||
status = FeatureStatus.PREMIUM,
|
||||
priority = FeaturePriority.LATER,
|
||||
description = "Full answer history with search/filter"
|
||||
)
|
||||
|
||||
data object CUSTOM_QUESTIONS : FeatureFlag(
|
||||
key = "CUSTOM_QUESTIONS",
|
||||
status = FeatureStatus.PREMIUM,
|
||||
priority = FeaturePriority.LATER,
|
||||
description = "Create custom questions"
|
||||
)
|
||||
|
||||
data object RELATIONSHIP_QUIZZES : FeatureFlag(
|
||||
key = "RELATIONSHIP_QUIZZES",
|
||||
status = FeatureStatus.PREMIUM,
|
||||
priority = FeaturePriority.LATER,
|
||||
description = "Deeper relationship quizzes"
|
||||
)
|
||||
|
||||
data object PRIVATE_NOTES : FeatureFlag(
|
||||
key = "PRIVATE_NOTES",
|
||||
status = FeatureStatus.PREMIUM,
|
||||
priority = FeaturePriority.LATER,
|
||||
description = "Private notes per user"
|
||||
)
|
||||
|
||||
data object EXPORT_MEMORIES : FeatureFlag(
|
||||
key = "EXPORT_MEMORIES",
|
||||
status = FeatureStatus.PREMIUM,
|
||||
priority = FeaturePriority.LATER,
|
||||
description = "Exportable memories"
|
||||
)
|
||||
|
||||
data object ADVANCED_REMINDERS : FeatureFlag(
|
||||
key = "ADVANCED_REMINDERS",
|
||||
status = FeatureStatus.PREMIUM,
|
||||
priority = FeaturePriority.LATER,
|
||||
description = "Custom reminder times, quiet hours"
|
||||
)
|
||||
|
||||
data object EXTRA_CATEGORIES : FeatureFlag(
|
||||
key = "EXTRA_CATEGORIES",
|
||||
status = FeatureStatus.PREMIUM,
|
||||
priority = FeaturePriority.LATER,
|
||||
description = "Extra question categories"
|
||||
)
|
||||
|
||||
data object AI_QUESTIONS : FeatureFlag(
|
||||
key = "AI_QUESTIONS",
|
||||
status = FeatureStatus.PREMIUM,
|
||||
priority = FeaturePriority.LATER,
|
||||
description = "Future AI-assisted question suggestions"
|
||||
)
|
||||
|
||||
// Convenience helpers
|
||||
val isPremium: Boolean get() = status == FeatureStatus.PREMIUM
|
||||
val isMvp: Boolean get() = priority == FeaturePriority.MVP
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package app.closer.core.feature
|
||||
|
||||
/**
|
||||
* Feature implementation priority.
|
||||
* - MVP: Will be released in initial version (with or without paywall)
|
||||
* - LATER: Future enhancement, not in initial release scope
|
||||
*/
|
||||
enum class FeaturePriority {
|
||||
MVP,
|
||||
LATER
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package app.closer.core.feature
|
||||
|
||||
/**
|
||||
* Central registry for all feature flags.
|
||||
* Holds all defined features and provides query helpers.
|
||||
*
|
||||
* Usage:
|
||||
* ```
|
||||
* val features = FeatureRegistry.allFeatures()
|
||||
* val premiumFeatures = FeatureRegistry.featuresByStatus(FeatureStatus.PREMIUM)
|
||||
* val isPremium = FeatureRegistry.isPremiumFeature("CUSTOM_QUESTIONS")
|
||||
* ```
|
||||
*/
|
||||
object FeatureRegistry {
|
||||
|
||||
// Query methods
|
||||
fun allFeatures(): List<FeatureFlag> =
|
||||
listOf(
|
||||
FeatureFlag.DAILY_QUESTION,
|
||||
FeatureFlag.ANSWER_HISTORY,
|
||||
FeatureFlag.BASIC_CATEGORIES,
|
||||
FeatureFlag.BASIC_REMINDERS,
|
||||
FeatureFlag.STREAK_TRACKING,
|
||||
FeatureFlag.SPIN_WHEEL_LIMITED,
|
||||
FeatureFlag.PREMIUM_PACKS,
|
||||
FeatureFlag.FULL_SPIN_WHEEL,
|
||||
FeatureFlag.UNLIMITED_QUESTIONS,
|
||||
FeatureFlag.FULL_HISTORY,
|
||||
FeatureFlag.CUSTOM_QUESTIONS,
|
||||
FeatureFlag.RELATIONSHIP_QUIZZES,
|
||||
FeatureFlag.PRIVATE_NOTES,
|
||||
FeatureFlag.EXPORT_MEMORIES,
|
||||
FeatureFlag.ADVANCED_REMINDERS,
|
||||
FeatureFlag.EXTRA_CATEGORIES,
|
||||
FeatureFlag.AI_QUESTIONS
|
||||
)
|
||||
|
||||
fun getFeature(key: String): FeatureFlag? =
|
||||
allFeatures().find { it.key == key }
|
||||
|
||||
fun featuresByStatus(status: FeatureStatus): List<FeatureFlag> =
|
||||
allFeatures().filter { it.status == status }
|
||||
|
||||
fun featuresByPriority(priority: FeaturePriority): List<FeatureFlag> =
|
||||
allFeatures().filter { it.priority == priority }
|
||||
|
||||
fun isPremiumFeature(key: String): Boolean =
|
||||
getFeature(key)?.isPremium == true
|
||||
|
||||
fun isMvpFeature(key: String): Boolean =
|
||||
getFeature(key)?.isMvp == true
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package app.closer.core.feature
|
||||
|
||||
/**
|
||||
* Feature billing status.
|
||||
* - FREE: Available to all users
|
||||
* - PREMIUM: Requires active subscription/entitlement
|
||||
*/
|
||||
enum class FeatureStatus {
|
||||
FREE,
|
||||
PREMIUM
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package app.closer.data.repository
|
|||
|
||||
import app.closer.core.crash.CrashReporter
|
||||
import app.closer.data.remote.FirestoreCollections
|
||||
import app.closer.domain.model.GameType
|
||||
import app.closer.domain.model.QuestionSession
|
||||
import app.closer.domain.repository.QuestionSessionRepository
|
||||
import com.google.firebase.firestore.FirebaseFirestore
|
||||
|
|
@ -71,7 +72,7 @@ class QuestionSessionRepositoryImpl @Inject constructor(
|
|||
partnerCompletedAt = doc.getLong("partnerCompletedAt"),
|
||||
isPremium = doc.getBoolean("isPremium") ?: false,
|
||||
status = doc.getString("status") ?: "completed",
|
||||
gameType = doc.getString("gameType") ?: "wheel"
|
||||
gameType = doc.getString("gameType") ?: GameType.WHEEL
|
||||
)
|
||||
}
|
||||
.onFailure { crashReporter.recordException(it) }
|
||||
|
|
@ -104,7 +105,7 @@ class QuestionSessionRepositoryImpl @Inject constructor(
|
|||
partnerCompletedAt = doc.getLong("partnerCompletedAt"),
|
||||
isPremium = doc.getBoolean("isPremium") ?: false,
|
||||
status = doc.getString("status") ?: "active",
|
||||
gameType = doc.getString("gameType") ?: "wheel"
|
||||
gameType = doc.getString("gameType") ?: GameType.WHEEL
|
||||
)
|
||||
}
|
||||
.onFailure { crashReporter.recordException(it) }
|
||||
|
|
@ -138,7 +139,7 @@ class QuestionSessionRepositoryImpl @Inject constructor(
|
|||
partnerCompletedAt = doc.getLong("partnerCompletedAt"),
|
||||
isPremium = doc.getBoolean("isPremium") ?: false,
|
||||
status = doc.getString("status") ?: "active",
|
||||
gameType = doc.getString("gameType") ?: "wheel"
|
||||
gameType = doc.getString("gameType") ?: GameType.WHEEL
|
||||
)
|
||||
}
|
||||
.onFailure { crashReporter.recordException(it) }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package app.closer.domain.model
|
||||
|
||||
object GameType {
|
||||
const val WHEEL = "wheel"
|
||||
const val THIS_OR_THAT = "this_or_that"
|
||||
const val HOW_WELL = "how_well"
|
||||
const val DESIRE_SYNC = "desire_sync"
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package app.closer.domain.usecase
|
||||
|
||||
import app.closer.domain.model.Couple
|
||||
import app.closer.domain.model.GameType
|
||||
import app.closer.domain.model.QuestionSession
|
||||
import app.closer.domain.repository.AuthRepository
|
||||
import app.closer.domain.repository.CoupleRepository
|
||||
|
|
@ -155,10 +156,10 @@ class GameSessionManager @Inject constructor(
|
|||
sessionRepository.observeActiveSessionForCouple(coupleId)
|
||||
|
||||
private fun gameTypeLabel(gameType: String): String = when (gameType) {
|
||||
"wheel" -> "Wheel"
|
||||
"this_or_that" -> "This or That"
|
||||
"how_well" -> "How Well Do You Know Me"
|
||||
"desire_sync" -> "Desire Sync"
|
||||
GameType.WHEEL -> "Wheel"
|
||||
GameType.THIS_OR_THAT -> "This or That"
|
||||
GameType.HOW_WELL -> "How Well Do You Know Me"
|
||||
GameType.DESIRE_SYNC -> "Desire Sync"
|
||||
else -> gameType
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package app.closer.ui.desiresync
|
||||
|
||||
import app.closer.domain.model.GameType
|
||||
import app.closer.ui.theme.closerCardColor
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
|
|
@ -145,7 +146,7 @@ class DesireSyncViewModel @Inject constructor(
|
|||
|
||||
private fun startSession() {
|
||||
viewModelScope.launch {
|
||||
gameSessionManager.startGameForCurrentUser(gameType = "desire_sync")
|
||||
gameSessionManager.startGameForCurrentUser(gameType = GameType.DESIRE_SYNC)
|
||||
.onSuccess { gameHandle = it }
|
||||
.onFailure { Log.w(TAG, "Could not start session", it) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.closer.core.navigation.AppRoute
|
||||
import app.closer.domain.model.GameType
|
||||
import app.closer.domain.usecase.GameSessionManager
|
||||
import app.closer.ui.components.CategoryGlyph
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -42,7 +43,7 @@ import kotlinx.coroutines.launch
|
|||
|
||||
data class WaitingForPartnerUiState(
|
||||
val isLoading: Boolean = true,
|
||||
val gameType: String = "wheel",
|
||||
val gameType: String = GameType.WHEEL,
|
||||
val partnerName: String = "Partner",
|
||||
val navigateTo: String? = null
|
||||
)
|
||||
|
|
@ -160,17 +161,17 @@ fun WaitingForPartnerScreen(
|
|||
}
|
||||
|
||||
private fun gameTypeLabel(gameType: String): String = when (gameType) {
|
||||
"wheel" -> "Wheel"
|
||||
"this_or_that" -> "This or That"
|
||||
"how_well" -> "How Well Do You Know Me"
|
||||
"desire_sync" -> "Desire Sync"
|
||||
GameType.WHEEL -> "Wheel"
|
||||
GameType.THIS_OR_THAT -> "This or That"
|
||||
GameType.HOW_WELL -> "How Well Do You Know Me"
|
||||
GameType.DESIRE_SYNC -> "Desire Sync"
|
||||
else -> gameType
|
||||
}
|
||||
|
||||
private fun gameTypeGlyphKey(gameType: String): String = when (gameType) {
|
||||
"wheel" -> "play"
|
||||
"this_or_that" -> "question"
|
||||
"how_well" -> "predict"
|
||||
"desire_sync" -> "sex_and_desire"
|
||||
GameType.WHEEL -> "play"
|
||||
GameType.THIS_OR_THAT -> "question"
|
||||
GameType.HOW_WELL -> "predict"
|
||||
GameType.DESIRE_SYNC -> "sex_and_desire"
|
||||
else -> "play"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
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.Canvas
|
||||
|
|
@ -167,7 +168,7 @@ class HowWellViewModel @Inject constructor(
|
|||
|
||||
private fun startSession() {
|
||||
viewModelScope.launch {
|
||||
gameSessionManager.startGameForCurrentUser(gameType = "how_well")
|
||||
gameSessionManager.startGameForCurrentUser(gameType = GameType.HOW_WELL)
|
||||
.onSuccess { gameHandle = it }
|
||||
.onFailure { Log.w(TAG, "Could not start session", it) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.heightIn
|
|||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package app.closer.ui.thisorthat
|
||||
|
||||
import app.closer.domain.model.GameType
|
||||
import app.closer.ui.theme.closerCardColor
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
|
|
@ -140,7 +141,7 @@ class ThisOrThatViewModel @Inject constructor(
|
|||
|
||||
private fun startSession() {
|
||||
viewModelScope.launch {
|
||||
gameSessionManager.startGameForCurrentUser(gameType = "this_or_that")
|
||||
gameSessionManager.startGameForCurrentUser(gameType = GameType.THIS_OR_THAT)
|
||||
.onSuccess { gameHandle = it }
|
||||
.onFailure { Log.w(TAG, "Could not start session", it) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.closer.core.navigation.AppRoute
|
||||
import app.closer.domain.model.GameType
|
||||
import app.closer.domain.repository.QuestionRepository
|
||||
import app.closer.domain.usecase.GameSessionManager
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -116,7 +117,7 @@ class SpinWheelViewModel @Inject constructor(
|
|||
val startResult = runCatching {
|
||||
gameSessionManager.startGame(
|
||||
userId = userId,
|
||||
gameType = "wheel",
|
||||
gameType = GameType.WHEEL,
|
||||
categoryId = categoryId,
|
||||
questionIds = questionIds
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue