From 838f6212af6e7131ef00df7b4514404ea0f3e513 Mon Sep 17 00:00:00 2001 From: joaomanaia Date: Thu, 15 Jun 2023 11:16:30 +0100 Subject: [PATCH 1/4] Start moving home recent categories to own components and repository --- .../MultiChoiceQuestionDataStoreCommon.kt | 15 -- .../RecentCategoryDataStoreCommon.kt | 18 +++ .../newquiz/core/di/DataStoreModule.kt | 42 +++--- .../ui/components/RequireInternetComponent.kt | 2 +- .../core/ui/home/HomeCategoriesItems.kt | 97 ++++++++++++ .../newquiz/core/ui/home/HomeLazyColumn.kt | 30 ++++ .../newquiz/data/di/RepositoryModule.kt | 5 + .../category/MultiChoiceQuestionCategories.kt | 58 +++---- .../data/local/wordle/WordleCategories.kt | 56 +++++++ .../home/RecentCategoriesRepositoryImpl.kt | 109 ++++++++++++++ .../MultiChoiceQuestionRepositoryImpl.kt | 38 +---- .../home/RecentCategoriesRepository.kt | 27 ++++ .../MultiChoiceQuestionRepository.kt | 9 +- .../newquiz/model/BaseCategory.kt | 11 ++ .../multi_choice_quiz/MultiChoiceCategory.kt | 15 +- .../newquiz/model/wordle/WordleCategory.kt | 14 ++ .../MultiChoiceQuizScreenViewModel.kt | 6 +- .../list/MultiChoiceQuizListScreen.kt | 142 ++---------------- .../list/MultiChoiceQuizListScreenUiState.kt | 4 +- .../MultiChoiceQuizListScreenViewModel.kt | 18 ++- .../SettingsViewModel.kt | 10 +- .../newquiz/wordle/list/WordleListScreen.kt | 64 +------- 22 files changed, 468 insertions(+), 322 deletions(-) delete mode 100644 core/src/main/java/com/infinitepower/newquiz/core/common/dataStore/MultiChoiceQuestionDataStoreCommon.kt create mode 100644 core/src/main/java/com/infinitepower/newquiz/core/common/dataStore/RecentCategoryDataStoreCommon.kt create mode 100644 core/src/main/java/com/infinitepower/newquiz/core/ui/home/HomeCategoriesItems.kt create mode 100644 core/src/main/java/com/infinitepower/newquiz/core/ui/home/HomeLazyColumn.kt create mode 100644 data/src/main/java/com/infinitepower/newquiz/data/local/wordle/WordleCategories.kt create mode 100644 data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt create mode 100644 domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt create mode 100644 model/src/main/java/com/infinitepower/newquiz/model/BaseCategory.kt create mode 100644 model/src/main/java/com/infinitepower/newquiz/model/wordle/WordleCategory.kt diff --git a/core/src/main/java/com/infinitepower/newquiz/core/common/dataStore/MultiChoiceQuestionDataStoreCommon.kt b/core/src/main/java/com/infinitepower/newquiz/core/common/dataStore/MultiChoiceQuestionDataStoreCommon.kt deleted file mode 100644 index c948e2a5..00000000 --- a/core/src/main/java/com/infinitepower/newquiz/core/common/dataStore/MultiChoiceQuestionDataStoreCommon.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.infinitepower.newquiz.core.common.dataStore - -import android.content.Context -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.stringPreferencesKey -import androidx.datastore.preferences.core.stringSetPreferencesKey -import androidx.datastore.preferences.preferencesDataStore -import com.infinitepower.newquiz.core.dataStore.manager.PreferenceRequest - -val Context.multiChoiceCategoriesDataStore: DataStore by preferencesDataStore(name = "multiChoiceCategories") - -object MultiChoiceQuestionDataStoreCommon { - object RecentCategories : PreferenceRequest>(stringSetPreferencesKey("recent_categories"), emptySet()) -} \ No newline at end of file diff --git a/core/src/main/java/com/infinitepower/newquiz/core/common/dataStore/RecentCategoryDataStoreCommon.kt b/core/src/main/java/com/infinitepower/newquiz/core/common/dataStore/RecentCategoryDataStoreCommon.kt new file mode 100644 index 00000000..fb544fd3 --- /dev/null +++ b/core/src/main/java/com/infinitepower/newquiz/core/common/dataStore/RecentCategoryDataStoreCommon.kt @@ -0,0 +1,18 @@ +package com.infinitepower.newquiz.core.common.dataStore + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.stringSetPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import com.infinitepower.newquiz.core.dataStore.manager.PreferenceRequest + +val Context.recentCategoriesDataStore: DataStore by preferencesDataStore(name = "recent_categories") + +object RecentCategoryDataStoreCommon { + object MultiChoice : PreferenceRequest>(stringSetPreferencesKey("multi_choice"), emptySet()) + + object Wordle : PreferenceRequest>(stringSetPreferencesKey("wordle"), emptySet()) + + object ComparisonQuiz : PreferenceRequest>(stringSetPreferencesKey("comparison_quiz"), emptySet()) +} diff --git a/core/src/main/java/com/infinitepower/newquiz/core/di/DataStoreModule.kt b/core/src/main/java/com/infinitepower/newquiz/core/di/DataStoreModule.kt index 5ebf2149..479f66eb 100644 --- a/core/src/main/java/com/infinitepower/newquiz/core/di/DataStoreModule.kt +++ b/core/src/main/java/com/infinitepower/newquiz/core/di/DataStoreModule.kt @@ -4,7 +4,7 @@ import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import com.infinitepower.newquiz.core.common.dataStore.comparisonQuizDataStore -import com.infinitepower.newquiz.core.common.dataStore.multiChoiceCategoriesDataStore +import com.infinitepower.newquiz.core.common.dataStore.recentCategoriesDataStore import com.infinitepower.newquiz.core.common.dataStore.settingsDataStore import com.infinitepower.newquiz.core.dataStore.manager.DataStoreManager import com.infinitepower.newquiz.core.dataStore.manager.DataStoreManagerImpl @@ -34,36 +34,36 @@ object DataStoreModule { @SettingsDataStore dataStore: DataStore ): DataStoreManager = DataStoreManagerImpl(dataStore) - // Multi choice question data store + // Comparison quiz data store @Provides @Singleton - @MultiChoiceQuestionDataStore - fun provideMultiChoiceQuestionDatastore( + @ComparisonQuizDataStore + fun provideComparisonQuizDatastore( @ApplicationContext context: Context - ): DataStore = context.multiChoiceCategoriesDataStore + ): DataStore = context.comparisonQuizDataStore @Provides @Singleton - @MultiChoiceQuestionDataStoreManager - fun provideMultiChoiceQuestionStoreManager( - @MultiChoiceQuestionDataStore dataStore: DataStore + @ComparisonQuizDataStoreManager + fun provideComparisonQuizStoreManager( + @ComparisonQuizDataStore dataStore: DataStore ): DataStoreManager = DataStoreManagerImpl(dataStore) - // Comparison quiz data store + // Recent categories data store @Provides @Singleton - @ComparisonQuizDataStore - fun provideComparisonQuizDatastore( + @RecentCategoriesDataStore + fun provideRecentCategoriesDatastore( @ApplicationContext context: Context - ): DataStore = context.comparisonQuizDataStore + ): DataStore = context.recentCategoriesDataStore @Provides @Singleton - @ComparisonQuizDataStoreManager - fun provideComparisonQuizStoreManager( - @ComparisonQuizDataStore dataStore: DataStore + @RecentCategoriesDataStoreManager + fun provideRecentCategoriesStoreManager( + @RecentCategoriesDataStore dataStore: DataStore ): DataStoreManager = DataStoreManagerImpl(dataStore) } @@ -76,22 +76,22 @@ annotation class SettingsDataStore @Retention(AnnotationRetention.RUNTIME) annotation class SettingsDataStoreManager -// Multi choice question data store +// Comparison quiz data store @Qualifier @Retention(AnnotationRetention.RUNTIME) -annotation class MultiChoiceQuestionDataStore +annotation class ComparisonQuizDataStore @Qualifier @Retention(AnnotationRetention.RUNTIME) -annotation class MultiChoiceQuestionDataStoreManager +annotation class ComparisonQuizDataStoreManager -// Comparison quiz data store +// Recent categories data store @Qualifier @Retention(AnnotationRetention.RUNTIME) -annotation class ComparisonQuizDataStore +annotation class RecentCategoriesDataStore @Qualifier @Retention(AnnotationRetention.RUNTIME) -annotation class ComparisonQuizDataStoreManager \ No newline at end of file +annotation class RecentCategoriesDataStoreManager diff --git a/core/src/main/java/com/infinitepower/newquiz/core/ui/components/RequireInternetComponent.kt b/core/src/main/java/com/infinitepower/newquiz/core/ui/components/RequireInternetComponent.kt index 76c484e5..60f77338 100644 --- a/core/src/main/java/com/infinitepower/newquiz/core/ui/components/RequireInternetComponent.kt +++ b/core/src/main/java/com/infinitepower/newquiz/core/ui/components/RequireInternetComponent.kt @@ -32,7 +32,7 @@ fun rememberIsInternetAvailable(): Boolean { } @Suppress("DEPRECATION") -private fun Context.isInternetAvailable(): Boolean { +fun Context.isInternetAvailable(): Boolean { val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { diff --git a/core/src/main/java/com/infinitepower/newquiz/core/ui/home/HomeCategoriesItems.kt b/core/src/main/java/com/infinitepower/newquiz/core/ui/home/HomeCategoriesItems.kt new file mode 100644 index 00000000..8419e0d9 --- /dev/null +++ b/core/src/main/java/com/infinitepower/newquiz/core/ui/home/HomeCategoriesItems.kt @@ -0,0 +1,97 @@ +package com.infinitepower.newquiz.core.ui.home + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ExpandLess +import androidx.compose.material.icons.rounded.ExpandMore +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.infinitepower.newquiz.core.R +import com.infinitepower.newquiz.core.ui.components.category.CategoryComponent +import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory + +fun LazyListScope.homeCategoriesItems( + seeAllCategories: Boolean, + recentCategories: List, + otherCategories: List, + isInternetAvailable: Boolean, + onCategoryClick: (MultiChoiceCategory) -> Unit, + onSeeAllCategoriesClick: () -> Unit, +) { + items( + items = recentCategories, + key = { category -> "recent_category_${category.id}" } + ) { category -> + CategoryComponent( + modifier = Modifier + .fillParentMaxWidth() + .height(120.dp), + title = category.name.asString(), + imageUrl = category.image, + onClick = { onCategoryClick(category) }, + enabled = isInternetAvailable || !category.requireInternetConnection + ) + } + + item { + val seeAllText = if (seeAllCategories) { + stringResource(id = R.string.see_less_categories) + } else { + stringResource(id = R.string.see_all_categories) + } + + val seeAllIcon = if (seeAllCategories) { + Icons.Rounded.ExpandLess + } else { + Icons.Rounded.ExpandMore + } + + Box( + modifier = Modifier.fillParentMaxWidth(), + contentAlignment = Alignment.Center + ) { + TextButton(onClick = onSeeAllCategoriesClick) { + Icon( + imageVector = seeAllIcon, + contentDescription = seeAllText, + modifier = Modifier.size(ButtonDefaults.IconSize) + ) + Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing)) + Text( + text = seeAllText, + style = MaterialTheme.typography.bodyMedium + ) + } + } + } + + if (seeAllCategories) { + items( + items = otherCategories, + key = { category -> category.id } + ) { category -> + CategoryComponent( + modifier = Modifier + .fillParentMaxWidth() + .height(120.dp), + title = category.name.asString(), + imageUrl = category.image, + onClick = { onCategoryClick(category) }, + enabled = isInternetAvailable || !category.requireInternetConnection + ) + } + } +} diff --git a/core/src/main/java/com/infinitepower/newquiz/core/ui/home/HomeLazyColumn.kt b/core/src/main/java/com/infinitepower/newquiz/core/ui/home/HomeLazyColumn.kt new file mode 100644 index 00000000..156fe485 --- /dev/null +++ b/core/src/main/java/com/infinitepower/newquiz/core/ui/home/HomeLazyColumn.kt @@ -0,0 +1,30 @@ +package com.infinitepower.newquiz.core.ui.home + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.infinitepower.newquiz.core.theme.spacing + +@Composable +fun HomeLazyColumn( + modifier: Modifier = Modifier, + content: LazyListScope.() -> Unit +) { + val spaceMedium = MaterialTheme.spacing.medium + + LazyColumn( + modifier = modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(spaceMedium), + contentPadding = PaddingValues( + start = spaceMedium, + end = spaceMedium, + bottom = MaterialTheme.spacing.large, + ), + content = content + ) +} \ No newline at end of file diff --git a/data/src/main/java/com/infinitepower/newquiz/data/di/RepositoryModule.kt b/data/src/main/java/com/infinitepower/newquiz/data/di/RepositoryModule.kt index 62084741..a22dfe85 100644 --- a/data/src/main/java/com/infinitepower/newquiz/data/di/RepositoryModule.kt +++ b/data/src/main/java/com/infinitepower/newquiz/data/di/RepositoryModule.kt @@ -3,6 +3,7 @@ package com.infinitepower.newquiz.data.di import com.infinitepower.newquiz.data.repository.comparison_quiz.ComparisonQuizRepositoryImpl import com.infinitepower.newquiz.data.repository.daily_challenge.DailyChallengeRepositoryImpl +import com.infinitepower.newquiz.data.repository.home.RecentCategoriesRepositoryImpl import com.infinitepower.newquiz.data.repository.math_quiz.MathQuizCoreRepositoryImpl import com.infinitepower.newquiz.data.repository.maze_quiz.MazeQuizRepositoryImpl import com.infinitepower.newquiz.data.repository.multi_choice_quiz.CountryCapitalFlagsQuizRepositoryImpl @@ -17,6 +18,7 @@ import com.infinitepower.newquiz.data.repository.remote_config.FirebaseRemoteCon import com.infinitepower.newquiz.data.repository.wordle.WordleRepositoryImpl import com.infinitepower.newquiz.domain.repository.comparison_quiz.ComparisonQuizRepository import com.infinitepower.newquiz.domain.repository.daily_challenge.DailyChallengeRepository +import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import com.infinitepower.newquiz.domain.repository.math_quiz.MathQuizCoreRepository import com.infinitepower.newquiz.domain.repository.maze.MazeQuizRepository import com.infinitepower.newquiz.domain.repository.multi_choice_quiz.CountryCapitalFlagsQuizRepository @@ -78,4 +80,7 @@ abstract class RepositoryModule { @Binds abstract fun bindRemoteConfigApi(impl: FirebaseRemoteConfigApiImpl): RemoteConfigApi + + @Binds + abstract fun bindRecentCategoriesRepository(impl: RecentCategoriesRepositoryImpl): RecentCategoriesRepository } \ No newline at end of file diff --git a/data/src/main/java/com/infinitepower/newquiz/data/local/multi_choice_quiz/category/MultiChoiceQuestionCategories.kt b/data/src/main/java/com/infinitepower/newquiz/data/local/multi_choice_quiz/category/MultiChoiceQuestionCategories.kt index 22fafda3..9069e4d3 100644 --- a/data/src/main/java/com/infinitepower/newquiz/data/local/multi_choice_quiz/category/MultiChoiceQuestionCategories.kt +++ b/data/src/main/java/com/infinitepower/newquiz/data/local/multi_choice_quiz/category/MultiChoiceQuestionCategories.kt @@ -8,176 +8,176 @@ import com.infinitepower.newquiz.core.R as CoreR val multiChoiceQuestionCategories = listOf( MultiChoiceCategory( key = MultiChoiceBaseCategory.Logo.key, - id = 1001, + id = "1001", name = UiText.StringResource(CoreR.string.logo_quiz), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Flogo_quiz_illustration.jpg?alt=media&token=cd9e54a2-a5d1-45f1-a285-cc490cc44cad" ), MultiChoiceCategory( key = MultiChoiceBaseCategory.Flag.key, - id = 1002, + id = "1002", name = UiText.StringResource(CoreR.string.flag_quiz), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fflags_illustration.png?alt=media&token=ec6b2820-1d26-4352-9c54-201bd387ae94" ), MultiChoiceCategory( key = MultiChoiceBaseCategory.CountryCapitalFlags.key, - id = 1003, + id = "1003", name = UiText.StringResource(CoreR.string.country_capital_flags), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fflags_illustration.png?alt=media&token=ec6b2820-1d26-4352-9c54-201bd387ae94" ), MultiChoiceCategory( key = MultiChoiceBaseCategory.GuessMathSolution.key, - id = 1004, + id = "1004", name = UiText.StringResource(CoreR.string.guess_solution), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumber_illustration.jpg?alt=media&token=68faf243-2b0e-4a13-aa9c-223743e263fd", requireInternetConnection = false ), MultiChoiceCategory( key = MultiChoiceBaseCategory.NumberTrivia.key, - id = 1005, + id = "1005", name = UiText.StringResource(CoreR.string.number_trivia), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumber_12_in_beach.jpg?alt=media&token=9b888c81-c51c-49ac-a376-0b3bde45db36" ), MultiChoiceCategory( key = "General knowledge", - id = 9, + id = "9", name = UiText.StringResource(CoreR.string.general_knowledge), image = CoreR.drawable.general_knowledge ), MultiChoiceCategory( key = "Books", - id = 10, + id = "10", name = UiText.StringResource(CoreR.string.entertainment_books), image = CoreR.drawable.books ), MultiChoiceCategory( key = "Film", - id = 11, + id = "11", name = UiText.StringResource(CoreR.string.entertainment_film), image = CoreR.drawable.films ), MultiChoiceCategory( key = "Music", - id = 12, + id = "12", name = UiText.StringResource(CoreR.string.entertainment_music), image = CoreR.drawable.music ), MultiChoiceCategory( key = "General Musicals & Theatres", - id = 13, + id = "13", name = UiText.StringResource(CoreR.string.entertainment_musicals_and_theatres), image = CoreR.drawable.musicals_and_theatres ), MultiChoiceCategory( key = "Television", - id = 14, + id = "14", name = UiText.StringResource(CoreR.string.entertainment_television), image = CoreR.drawable.entertainment_television ), MultiChoiceCategory( key = "Video Games", - id = 15, + id = "15", name = UiText.StringResource(CoreR.string.entertainment_video_games), image = CoreR.drawable.entertainment_video_games ), MultiChoiceCategory( key = "Board Games", - id = 16, + id = "16", name = UiText.StringResource(CoreR.string.entertainment_board_games), image = CoreR.drawable.entertainment_board_games ), MultiChoiceCategory( key = "Science & Nature", - id = 17, + id = "17", name = UiText.StringResource(CoreR.string.science_and_nature), image = CoreR.drawable.science_and_nature ), MultiChoiceCategory( key = "Computers", - id = 18, + id = "18", name = UiText.StringResource(CoreR.string.science_computers), image = CoreR.drawable.science_computers ), MultiChoiceCategory( key = "Mathematics", - id = 19, + id = "19", name = UiText.StringResource(CoreR.string.science_mathematics), image = CoreR.drawable.science_mathematics ), MultiChoiceCategory( key = "Mythology", - id = 20, + id = "20", name = UiText.StringResource(CoreR.string.mythology), image = CoreR.drawable.mythology ), MultiChoiceCategory( key = "Sports", - id = 21, + id = "21", name = UiText.StringResource(CoreR.string.sports), image = CoreR.drawable.sports ), MultiChoiceCategory( key = "Geography", - id = 22, + id = "22", name = UiText.StringResource(CoreR.string.geography), image = CoreR.drawable.geography ), MultiChoiceCategory( key = "History", - id = 23, + id = "23", name = UiText.StringResource(CoreR.string.history), image = CoreR.drawable.history ), MultiChoiceCategory( key = "Politics", - id = 24, + id = "24", name = UiText.StringResource(CoreR.string.politics), image = CoreR.drawable.politics ), MultiChoiceCategory( key = "Art", - id = 25, + id = "25", name = UiText.StringResource(CoreR.string.art), image = CoreR.drawable.art ), MultiChoiceCategory( key = "Celebrities", - id = 26, + id = "26", name = UiText.StringResource(CoreR.string.celebrities), image = CoreR.drawable.celebrities ), MultiChoiceCategory( key = "Animals", - id = 27, + id = "27", name = UiText.StringResource(CoreR.string.animals), image = CoreR.drawable.animals ), MultiChoiceCategory( key = "Vehicles", - id = 28, + id = "28", name = UiText.StringResource(CoreR.string.vehicles), image = CoreR.drawable.vehicles ), MultiChoiceCategory( key = "Comics", - id = 29, + id = "29", name = UiText.StringResource(CoreR.string.entertainment_comics), image = CoreR.drawable.entertainment_comics ), MultiChoiceCategory( key = "Gadgets", - id = 30, + id = "30", name = UiText.StringResource(CoreR.string.science_gadgets), image = CoreR.drawable.science_gadgets ), MultiChoiceCategory( key = "Japanese Anime & Manga", - id = 31, + id = "31", name = UiText.StringResource(CoreR.string.entertainment_japanese_anime_and_manga), image = CoreR.drawable.entertainment_japanese_anime_and_manga ), MultiChoiceCategory( key = "Cartoon & Animations", - id = 32, + id = "32", name = UiText.StringResource(CoreR.string.entertainment_cartoon_and_animations), image = CoreR.drawable.entertainment_cartoon_and_animations ) diff --git a/data/src/main/java/com/infinitepower/newquiz/data/local/wordle/WordleCategories.kt b/data/src/main/java/com/infinitepower/newquiz/data/local/wordle/WordleCategories.kt new file mode 100644 index 00000000..d752f86b --- /dev/null +++ b/data/src/main/java/com/infinitepower/newquiz/data/local/wordle/WordleCategories.kt @@ -0,0 +1,56 @@ +package com.infinitepower.newquiz.data.local.wordle + +import com.infinitepower.newquiz.model.UiText +import com.infinitepower.newquiz.model.wordle.WordleCategory +import com.infinitepower.newquiz.model.wordle.WordleQuizType +import kotlin.random.Random +import com.infinitepower.newquiz.core.R as CoreR + +object WordleCategories { + /** + * Returns a random [WordleCategory] from the list of [allCategories]. + * If [isInternetAvailable] is false, it will only return categories that don't require internet connection. + * + * @param allCategories The list of all categories to get the random category. + * @param isInternetAvailable Whether the internet is available or not. + * @param random The random instance to use. + * @see [WordleCategory] + */ + fun random( + allCategories: List, + isInternetAvailable: Boolean, + random: Random = Random + ): WordleCategory = if (isInternetAvailable) { + allCategories.random(random) + } else { + allCategories.filter { !it.requireInternetConnection }.random(random) + } + + val allCategories = listOf( + WordleCategory( + id = "guess_the_word", + name = UiText.StringResource(CoreR.string.guess_the_word), + image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fwordle_illustration.jpg?alt=media&token=69019438-4904-4656-8b1c-18678c537d6b", + wordleQuizType = WordleQuizType.TEXT + ), + WordleCategory( + id = "guess_the_number", + name = UiText.StringResource(CoreR.string.guess_the_number), + image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumbers_12345_illustration.jpg?alt=media&token=f170e7ca-02a3-4dae-87f0-63b0f1205bc5", + wordleQuizType = WordleQuizType.NUMBER + ), + WordleCategory( + id = "guess_math_formula", + name = UiText.StringResource(CoreR.string.guess_math_formula), + image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumber_illustration.jpg?alt=media&token=68faf243-2b0e-4a13-aa9c-223743e263fd", + wordleQuizType = WordleQuizType.MATH_FORMULA + ), + WordleCategory( + id = "number_trivia", + name = UiText.StringResource(CoreR.string.number_trivia), + image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumber_12_in_beach.jpg?alt=media&token=9b888c81-c51c-49ac-a376-0b3bde45db36", + wordleQuizType = WordleQuizType.NUMBER_TRIVIA, + requireInternetConnection = true + ) + ) +} diff --git a/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt b/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt new file mode 100644 index 00000000..c356dca1 --- /dev/null +++ b/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt @@ -0,0 +1,109 @@ +package com.infinitepower.newquiz.data.repository.home + +import com.infinitepower.newquiz.core.common.dataStore.RecentCategoryDataStoreCommon +import com.infinitepower.newquiz.core.dataStore.manager.DataStoreManager +import com.infinitepower.newquiz.core.dataStore.manager.PreferenceRequest +import com.infinitepower.newquiz.core.di.RecentCategoriesDataStoreManager +import com.infinitepower.newquiz.data.local.multi_choice_quiz.category.multiChoiceQuestionCategories +import com.infinitepower.newquiz.domain.repository.home.HomeCategories +import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository +import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceBaseCategory +import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RecentCategoriesRepositoryImpl @Inject constructor( + @RecentCategoriesDataStoreManager private val recentCategoriesDataStoreManager: DataStoreManager +) : RecentCategoriesRepository { + override fun getMultiChoiceCategories(): Flow> = recentCategoriesDataStoreManager + .getPreferenceFlow(RecentCategoryDataStoreCommon.MultiChoice) + .map { recentCategoriesIds -> + val allCategories = getAllCategories(isInternetAvailable = true) + + val recentCategories = getRecentCategories( + recentCategories = recentCategoriesIds.mapNotNull { id -> + allCategories.find { it.key == id } + }, + allCategories = allCategories, + isInternetAvailable = true + ) + + val otherCategories = allCategories - recentCategories.toSet() + + HomeCategories( + recentCategories = recentCategories, + otherCategories = otherCategories + ) + } + + private fun getAllCategories( + isInternetAvailable: Boolean + ): List { + // If there is no internet, we make the categories that don't require internet connection + // in the top of the list + return if (isInternetAvailable) { + multiChoiceQuestionCategories + } else { + multiChoiceQuestionCategories.sortedBy { it.requireInternetConnection } + } + } + + private fun getRecentCategories( + recentCategories: List, + allCategories: List, + isInternetAvailable: Boolean + ): List { + // When there are recent categories, we return them + return recentCategories.ifEmpty { + // If there are no recent categories, we take 3 random ones, + // So we don't show all categories initially + allCategories + // If there is no internet, we only show the categories that don't require internet connection + .filter { !it.requireInternetConnection || isInternetAvailable } + .shuffled() + .take(3) + } + } + + override suspend fun addMultiChoiceCategory(category: MultiChoiceBaseCategory) { + addCategory(category.key, RecentCategoryDataStoreCommon.MultiChoice) + } + + override suspend fun cleanMultiChoiceCategories() { + recentCategoriesDataStoreManager.editPreference( + key = RecentCategoryDataStoreCommon.MultiChoice.key, + newValue = emptySet() + ) + } + + override suspend fun cleanAll() { + cleanMultiChoiceCategories() + } + + private suspend fun addCategory( + id: String, + preferenceRequest: PreferenceRequest> + ) { + val recentCategories = recentCategoriesDataStoreManager.getPreference(preferenceRequest) + + val newCategoriesIds = recentCategories + .toMutableSet() + .apply { + // If the category to add is in the recent it's not necessary + // to add the category, so return + if (id in this) return + + if (size >= 3) remove(last()) + + add(id) + }.toSet() + + recentCategoriesDataStoreManager.editPreference( + key = preferenceRequest.key, + newValue = newCategoriesIds + ) + } +} \ No newline at end of file diff --git a/data/src/main/java/com/infinitepower/newquiz/data/repository/multi_choice_quiz/MultiChoiceQuestionRepositoryImpl.kt b/data/src/main/java/com/infinitepower/newquiz/data/repository/multi_choice_quiz/MultiChoiceQuestionRepositoryImpl.kt index 4da9aa5c..46ac0fd2 100644 --- a/data/src/main/java/com/infinitepower/newquiz/data/repository/multi_choice_quiz/MultiChoiceQuestionRepositoryImpl.kt +++ b/data/src/main/java/com/infinitepower/newquiz/data/repository/multi_choice_quiz/MultiChoiceQuestionRepositoryImpl.kt @@ -1,12 +1,8 @@ package com.infinitepower.newquiz.data.repository.multi_choice_quiz -import com.infinitepower.newquiz.core.common.dataStore.MultiChoiceQuestionDataStoreCommon -import com.infinitepower.newquiz.core.dataStore.manager.DataStoreManager -import com.infinitepower.newquiz.core.di.MultiChoiceQuestionDataStoreManager import com.infinitepower.newquiz.data.local.multi_choice_quiz.category.multiChoiceQuestionCategories import com.infinitepower.newquiz.domain.repository.multi_choice_quiz.MultiChoiceQuestionRepository import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceBaseCategory -import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceQuestion import com.infinitepower.newquiz.model.multi_choice_quiz.opentdb.OpenTDBQuestionResponse import com.infinitepower.newquiz.model.multi_choice_quiz.toQuestion @@ -21,10 +17,7 @@ import io.ktor.http.HttpMethod import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import javax.inject.Inject import javax.inject.Singleton @@ -34,8 +27,7 @@ private const val OPENTDB_API_URL = "https://opentdb.com/api.php" @Singleton class MultiChoiceQuestionRepositoryImpl @Inject constructor( - private val client: HttpClient, - @MultiChoiceQuestionDataStoreManager private val settingsDataStoreManager: DataStoreManager + private val client: HttpClient ) : MultiChoiceQuestionRepository { override suspend fun getRandomQuestions( amount: Int, @@ -78,32 +70,4 @@ class MultiChoiceQuestionRepositoryImpl @Inject constructor( val textResponse = response.bodyAsText() return Json.decodeFromString(textResponse) } - - override fun getRecentCategories(): Flow> = - settingsDataStoreManager - .getPreferenceFlow(MultiChoiceQuestionDataStoreCommon.RecentCategories) - .map { recentCategories -> - multiChoiceQuestionCategories.filter { it.key in recentCategories } - } - - override suspend fun addCategoryToRecent(category: MultiChoiceBaseCategory) { - val recentCategories = settingsDataStoreManager.getPreference(MultiChoiceQuestionDataStoreCommon.RecentCategories) - - val newCategoriesIds = recentCategories - .toMutableSet() - .apply { - // If the category to add is in the recent it's not necessary - // to add the category, so return - if (category.key in this) return - - if (size >= 3) remove(last()) - - add(category.key) - }.toSet() - - settingsDataStoreManager.editPreference( - key = MultiChoiceQuestionDataStoreCommon.RecentCategories.key, - newValue = newCategoriesIds - ) - } } \ No newline at end of file diff --git a/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt b/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt new file mode 100644 index 00000000..df0900a7 --- /dev/null +++ b/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt @@ -0,0 +1,27 @@ +package com.infinitepower.newquiz.domain.repository.home + +import androidx.annotation.Keep +import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceBaseCategory +import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory +import kotlinx.coroutines.flow.Flow + +@Keep +data class HomeCategories ( + val recentCategories: List, + val otherCategories: List +) + +fun emptyHomeCategories() = HomeCategories( + recentCategories = emptyList(), + otherCategories = emptyList() +) + +interface RecentCategoriesRepository { + fun getMultiChoiceCategories(): Flow> + + suspend fun addMultiChoiceCategory(category: MultiChoiceBaseCategory) + + suspend fun cleanMultiChoiceCategories() + + suspend fun cleanAll() +} \ No newline at end of file diff --git a/domain/src/main/java/com/infinitepower/newquiz/domain/repository/multi_choice_quiz/MultiChoiceQuestionRepository.kt b/domain/src/main/java/com/infinitepower/newquiz/domain/repository/multi_choice_quiz/MultiChoiceQuestionRepository.kt index 8ad5badb..81bf39ce 100644 --- a/domain/src/main/java/com/infinitepower/newquiz/domain/repository/multi_choice_quiz/MultiChoiceQuestionRepository.kt +++ b/domain/src/main/java/com/infinitepower/newquiz/domain/repository/multi_choice_quiz/MultiChoiceQuestionRepository.kt @@ -1,12 +1,5 @@ package com.infinitepower.newquiz.domain.repository.multi_choice_quiz import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceBaseCategory -import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory -import kotlinx.coroutines.flow.Flow -interface MultiChoiceQuestionRepository : MultiChoiceQuestionBaseRepository { - - fun getRecentCategories(): Flow> - - suspend fun addCategoryToRecent(category: MultiChoiceBaseCategory) -} \ No newline at end of file +interface MultiChoiceQuestionRepository : MultiChoiceQuestionBaseRepository diff --git a/model/src/main/java/com/infinitepower/newquiz/model/BaseCategory.kt b/model/src/main/java/com/infinitepower/newquiz/model/BaseCategory.kt new file mode 100644 index 00000000..94ad3bd5 --- /dev/null +++ b/model/src/main/java/com/infinitepower/newquiz/model/BaseCategory.kt @@ -0,0 +1,11 @@ +package com.infinitepower.newquiz.model + +interface BaseCategory { + val id: String + + val name: UiText + + val image: Any + + val requireInternetConnection: Boolean +} diff --git a/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceCategory.kt b/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceCategory.kt index ee407cc6..0bd660cb 100644 --- a/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceCategory.kt +++ b/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceCategory.kt @@ -1,15 +1,18 @@ package com.infinitepower.newquiz.model.multi_choice_quiz import androidx.annotation.Keep +import com.infinitepower.newquiz.model.BaseCategory import com.infinitepower.newquiz.model.UiText @Keep data class MultiChoiceCategory( - val key: String, - val id: Int, - val name: UiText, - val image: Any, - val requireInternetConnection: Boolean = true -) + override val id: String, + override val name: UiText, + override val image: Any, + override val requireInternetConnection: Boolean = true, + + // The key to use in the MultiChoiceBaseCategory + val key: String +) : BaseCategory fun MultiChoiceCategory.toBaseCategory() = MultiChoiceBaseCategory.fromKey(key) diff --git a/model/src/main/java/com/infinitepower/newquiz/model/wordle/WordleCategory.kt b/model/src/main/java/com/infinitepower/newquiz/model/wordle/WordleCategory.kt new file mode 100644 index 00000000..29058f82 --- /dev/null +++ b/model/src/main/java/com/infinitepower/newquiz/model/wordle/WordleCategory.kt @@ -0,0 +1,14 @@ +package com.infinitepower.newquiz.model.wordle + +import androidx.annotation.Keep +import com.infinitepower.newquiz.model.BaseCategory +import com.infinitepower.newquiz.model.UiText + +@Keep +data class WordleCategory( + override val id: String, + override val name: UiText, + override val image: Any, + override val requireInternetConnection: Boolean = false, + val wordleQuizType: WordleQuizType +) : BaseCategory diff --git a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/MultiChoiceQuizScreenViewModel.kt b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/MultiChoiceQuizScreenViewModel.kt index 2ad45ebf..adf2f7c9 100644 --- a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/MultiChoiceQuizScreenViewModel.kt +++ b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/MultiChoiceQuizScreenViewModel.kt @@ -17,7 +17,7 @@ import com.infinitepower.newquiz.core.dataStore.manager.DataStoreManager import com.infinitepower.newquiz.core.di.SettingsDataStoreManager import com.infinitepower.newquiz.data.worker.UpdateGlobalEventDataWorker import com.infinitepower.newquiz.data.worker.maze.EndGameMazeQuizWorker -import com.infinitepower.newquiz.domain.repository.multi_choice_quiz.MultiChoiceQuestionRepository +import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import com.infinitepower.newquiz.domain.repository.multi_choice_quiz.saved_questions.SavedMultiChoiceQuestionsRepository import com.infinitepower.newquiz.domain.repository.user.auth.AuthUserRepository import com.infinitepower.newquiz.domain.use_case.question.GetRandomMultiChoiceQuestionUseCase @@ -48,7 +48,7 @@ class QuizScreenViewModel @Inject constructor( private val getRandomQuestionUseCase: GetRandomMultiChoiceQuestionUseCase, @SettingsDataStoreManager private val settingsDataStoreManager: DataStoreManager, private val savedQuestionsRepository: SavedMultiChoiceQuestionsRepository, - private val multiChoiceQuestionsRepository: MultiChoiceQuestionRepository, + private val recentCategoriesRepository: RecentCategoriesRepository, private val savedStateHandle: SavedStateHandle, private val multiChoiceQuizLoggingAnalytics: MultiChoiceQuizLoggingAnalytics, private val translationUtil: TranslatorUtil, @@ -165,7 +165,7 @@ class QuizScreenViewModel @Inject constructor( val difficulty = savedStateHandle.get(MultiChoiceQuizScreenNavArg::difficulty.name) if (category.hasCategory) { - multiChoiceQuestionsRepository.addCategoryToRecent(category) + recentCategoriesRepository.addMultiChoiceCategory(category) } getRandomQuestionUseCase(questionSize, category, difficulty).collect { res -> diff --git a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreen.kt b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreen.kt index 8e1eb8ac..246a9918 100644 --- a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreen.kt +++ b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreen.kt @@ -1,56 +1,37 @@ package com.infinitepower.newquiz.multi_choice_quiz.list -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.ExpandLess -import androidx.compose.material.icons.rounded.ExpandMore import androidx.compose.material.icons.rounded.Save -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.airbnb.lottie.compose.LottieCompositionSpec import com.infinitepower.newquiz.core.common.annotation.compose.AllPreviewsNightLight import com.infinitepower.newquiz.core.theme.NewQuizTheme -import com.infinitepower.newquiz.core.theme.spacing -import com.infinitepower.newquiz.core.ui.components.category.CategoryComponent import com.infinitepower.newquiz.core.ui.components.rememberIsInternetAvailable +import com.infinitepower.newquiz.core.ui.home.HomeLazyColumn +import com.infinitepower.newquiz.core.ui.home.homeCategoriesItems import com.infinitepower.newquiz.core.ui.home_card.components.HomeGroupTitle import com.infinitepower.newquiz.core.ui.home_card.components.HomeLargeCard import com.infinitepower.newquiz.core.ui.home_card.components.HomeMediumCard import com.infinitepower.newquiz.core.ui.home_card.model.CardIcon import com.infinitepower.newquiz.core.ui.home_card.model.HomeCardItem -import com.infinitepower.newquiz.data.local.multi_choice_quiz.category.multiChoiceQuestionCategories import com.infinitepower.newquiz.model.multi_choice_quiz.toBaseCategory import com.infinitepower.newquiz.model.question.QuestionDifficulty import com.infinitepower.newquiz.multi_choice_quiz.components.difficulty.SelectableDifficultyRow import com.infinitepower.newquiz.multi_choice_quiz.destinations.MultiChoiceQuizScreenDestination import com.infinitepower.newquiz.multi_choice_quiz.destinations.SavedMultiChoiceQuestionsScreenDestination -import com.infinitepower.newquiz.multi_choice_quiz.list.util.MultiChoiceQuizListUtils import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator @@ -77,8 +58,6 @@ private fun MultiChoiceQuizListScreenImpl( uiState: MultiChoiceQuizListScreenUiState, navigator: DestinationsNavigator ) { - val spaceMedium = MaterialTheme.spacing.medium - val isInternetAvailable = rememberIsInternetAvailable() val questionsAvailableText = pluralStringResource( @@ -94,44 +73,7 @@ private fun MultiChoiceQuizListScreenImpl( var seeAllCategories by remember { mutableStateOf(false) } - val seeAllText = if (seeAllCategories) { - stringResource(id = CoreR.string.see_less_categories) - } else { - stringResource(id = CoreR.string.see_all_categories) - } - - val seeAllIcon = if (seeAllCategories) { - Icons.Rounded.ExpandLess - } else { - Icons.Rounded.ExpandMore - } - - val allCategories = remember(isInternetAvailable) { - MultiChoiceQuizListUtils.getAllCategories(isInternetAvailable = isInternetAvailable) - } - - val recentCategories = remember(uiState.recentCategories, isInternetAvailable) { - MultiChoiceQuizListUtils.getRecentCategories( - recentCategories = uiState.recentCategories, - allCategories = multiChoiceQuestionCategories, - isInternetAvailable = isInternetAvailable - ) - } - - // The other categories are the ones that are not recent - val otherCategories = remember(allCategories, recentCategories) { - allCategories - recentCategories.toSet() - } - - LazyColumn( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(spaceMedium), - contentPadding = PaddingValues( - start = spaceMedium, - end = spaceMedium, - bottom = MaterialTheme.spacing.large, - ) - ) { + HomeLazyColumn { item { HomeGroupTitle(title = stringResource(id = CoreR.string.random_quiz)) } @@ -172,73 +114,21 @@ private fun MultiChoiceQuizListScreenImpl( ) } - items( - items = recentCategories, - key = { category -> "recent_category_${category.id}" } - ) { category -> - CategoryComponent( - modifier = Modifier - .fillParentMaxWidth() - .height(120.dp), - title = category.name.asString(), - imageUrl = category.image, - onClick = { - navigator.navigate( - MultiChoiceQuizScreenDestination( - category = category.toBaseCategory(), - difficulty = selectedDifficulty?.id - ) + homeCategoriesItems( + seeAllCategories = seeAllCategories, + recentCategories = uiState.homeCategories.recentCategories, + otherCategories = uiState.homeCategories.otherCategories, + isInternetAvailable = isInternetAvailable, + onCategoryClick = { category -> + navigator.navigate( + MultiChoiceQuizScreenDestination( + category = category.toBaseCategory(), + difficulty = selectedDifficulty?.id ) - }, - enabled = isInternetAvailable || !category.requireInternetConnection - ) - } - - item { - Box( - modifier = Modifier.fillParentMaxWidth(), - contentAlignment = Alignment.Center - ) { - TextButton( - onClick = { seeAllCategories = !seeAllCategories } - ) { - Icon( - imageVector = seeAllIcon, - contentDescription = seeAllText, - modifier = Modifier.size(ButtonDefaults.IconSize) - ) - Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing)) - Text( - text = seeAllText, - style = MaterialTheme.typography.bodyMedium - ) - } - } - } - - if (seeAllCategories) { - items( - items = otherCategories, - key = { category -> category.id } - ) { category -> - CategoryComponent( - modifier = Modifier - .fillParentMaxWidth() - .height(120.dp), - title = category.name.asString(), - imageUrl = category.image, - onClick = { - navigator.navigate( - MultiChoiceQuizScreenDestination( - category = category.toBaseCategory(), - difficulty = selectedDifficulty?.id - ) - ) - }, - enabled = isInternetAvailable || !category.requireInternetConnection ) - } - } + }, + onSeeAllCategoriesClick = { seeAllCategories = !seeAllCategories } + ) item { HomeGroupTitle(title = stringResource(id = CoreR.string.saved_questions)) diff --git a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreenUiState.kt b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreenUiState.kt index 35ffacc7..644af559 100644 --- a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreenUiState.kt +++ b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreenUiState.kt @@ -1,10 +1,12 @@ package com.infinitepower.newquiz.multi_choice_quiz.list import androidx.annotation.Keep +import com.infinitepower.newquiz.domain.repository.home.HomeCategories +import com.infinitepower.newquiz.domain.repository.home.emptyHomeCategories import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory @Keep data class MultiChoiceQuizListScreenUiState( val savedQuestionsSize: Int = 0, - val recentCategories: List = emptyList() + val homeCategories: HomeCategories = emptyHomeCategories() ) \ No newline at end of file diff --git a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreenViewModel.kt b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreenViewModel.kt index e1745c7c..f1882e09 100644 --- a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreenViewModel.kt +++ b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreenViewModel.kt @@ -2,16 +2,20 @@ package com.infinitepower.newquiz.multi_choice_quiz.list import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.infinitepower.newquiz.domain.repository.multi_choice_quiz.MultiChoiceQuestionRepository +import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import com.infinitepower.newquiz.domain.repository.multi_choice_quiz.saved_questions.SavedMultiChoiceQuestionsRepository import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update import javax.inject.Inject @HiltViewModel class MultiChoiceQuizListScreenViewModel @Inject constructor( private val savedQuestionsRepository: SavedMultiChoiceQuestionsRepository, - private val multiChoiceQuestionsRepository: MultiChoiceQuestionRepository + private val recentCategoriesRepository: RecentCategoriesRepository ) : ViewModel() { private val _uiState = MutableStateFlow(MultiChoiceQuizListScreenUiState()) val uiState = _uiState.asStateFlow() @@ -25,11 +29,11 @@ class MultiChoiceQuizListScreenViewModel @Inject constructor( } }.launchIn(viewModelScope) - multiChoiceQuestionsRepository - .getRecentCategories() - .onEach { recentCategories -> + recentCategoriesRepository + .getMultiChoiceCategories() + .onEach { homeCategories -> _uiState.update { currentState -> - currentState.copy(recentCategories = recentCategories) + currentState.copy(homeCategories = homeCategories) } }.launchIn(viewModelScope) } diff --git a/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/SettingsViewModel.kt b/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/SettingsViewModel.kt index af1cebc7..45cf9ca8 100644 --- a/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/SettingsViewModel.kt +++ b/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/SettingsViewModel.kt @@ -3,12 +3,11 @@ package com.infinitepower.newquiz.settings_presentation import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.infinitepower.newquiz.core.common.dataStore.MultiChoiceQuestionDataStoreCommon import com.infinitepower.newquiz.core.common.dataStore.SettingsCommon import com.infinitepower.newquiz.core.dataStore.manager.DataStoreManager -import com.infinitepower.newquiz.core.di.MultiChoiceQuestionDataStoreManager import com.infinitepower.newquiz.core.di.SettingsDataStoreManager import com.infinitepower.newquiz.core.util.analytics.AnalyticsUtils +import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import com.infinitepower.newquiz.domain.repository.user.auth.AuthUserRepository import com.infinitepower.newquiz.model.DataAnalyticsConsentState import com.infinitepower.newquiz.settings_presentation.data.SettingsScreenPageData @@ -30,8 +29,8 @@ class SettingsViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val translatorUtil: TranslatorUtil, private val authUserRepository: AuthUserRepository, - @MultiChoiceQuestionDataStoreManager private val multiChoiceSettingsDataStoreManager: DataStoreManager, @SettingsDataStoreManager private val settingsDataStoreManager: DataStoreManager, + private val recentCategoriesRepository: RecentCategoriesRepository ) : ViewModel() { private val _uiState = MutableStateFlow(SettingsUiState()) val uiState = _uiState.asStateFlow() @@ -92,10 +91,7 @@ class SettingsViewModel @Inject constructor( } private fun cleanMultiChoiceRecentCategoriesItems() = viewModelScope.launch(Dispatchers.IO) { - multiChoiceSettingsDataStoreManager.editPreference( - key = MultiChoiceQuestionDataStoreCommon.RecentCategories.key, - newValue = emptySet() - ) + recentCategoriesRepository.cleanMultiChoiceCategories() } private fun enableLoggingAnalytics(enabled: Boolean) = viewModelScope.launch(Dispatchers.IO) { diff --git a/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreen.kt b/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreen.kt index 10ccb755..90bebc42 100644 --- a/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreen.kt +++ b/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreen.kt @@ -1,6 +1,5 @@ package com.infinitepower.newquiz.wordle.list -import androidx.annotation.Keep import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize @@ -27,23 +26,13 @@ import com.infinitepower.newquiz.core.ui.home_card.components.HomeGroupTitle import com.infinitepower.newquiz.core.ui.home_card.components.HomeLargeCard import com.infinitepower.newquiz.core.ui.home_card.model.CardIcon import com.infinitepower.newquiz.core.ui.home_card.model.HomeCardItem -import com.infinitepower.newquiz.model.UiText +import com.infinitepower.newquiz.data.local.wordle.WordleCategories import com.infinitepower.newquiz.model.wordle.WordleQuizType import com.infinitepower.newquiz.wordle.destinations.WordleScreenDestination import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import kotlin.random.Random import com.infinitepower.newquiz.core.R as CoreR -@Keep -data class WordleCategory( - val id: Int, - val name: UiText, - val image: Any, - val wordleQuizType: WordleQuizType, - val requireInternetConnection: Boolean = false -) - @Composable @Destination @OptIn(ExperimentalMaterial3Api::class) @@ -66,7 +55,7 @@ private fun WordleListScreenImpl( val isInternetAvailable = rememberIsInternetAvailable() - val categories = remember { getWordleCategories() } + val categories = remember { WordleCategories.allCategories } LazyColumn( modifier = Modifier.fillMaxSize(), @@ -89,7 +78,7 @@ private fun WordleListScreenImpl( icon = CardIcon.Icon(Icons.Rounded.QuestionMark), backgroundPrimary = true, onClick = { - val randomCategory = getRandomWordleCategory(categories, isInternetAvailable) + val randomCategory = WordleCategories.random(categories, isInternetAvailable) navigateToWordleQuiz(randomCategory.wordleQuizType) } ) @@ -120,53 +109,6 @@ private fun WordleListScreenImpl( } } -/** - * Returns a random [WordleCategory] from the list of [allCategories]. - * If [isInternetAvailable] is false, it will only return categories that don't require internet connection. - * - * @param allCategories The list of all categories to get the random category. - * @param isInternetAvailable Whether the internet is available or not. - * @param random The random instance to use. - * @see [WordleCategory] - */ -fun getRandomWordleCategory( - allCategories: List, - isInternetAvailable: Boolean, - random: Random = Random -): WordleCategory = if (isInternetAvailable) { - allCategories.random(random) -} else { - allCategories.filter { !it.requireInternetConnection }.random(random) -} - -fun getWordleCategories() = listOf( - WordleCategory( - id = 1, - name = UiText.StringResource(CoreR.string.guess_the_word), - image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fwordle_illustration.jpg?alt=media&token=69019438-4904-4656-8b1c-18678c537d6b", - wordleQuizType = WordleQuizType.TEXT - ), - WordleCategory( - id = 2, - name = UiText.StringResource(CoreR.string.guess_the_number), - image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumbers_12345_illustration.jpg?alt=media&token=f170e7ca-02a3-4dae-87f0-63b0f1205bc5", - wordleQuizType = WordleQuizType.NUMBER - ), - WordleCategory( - id = 3, - name = UiText.StringResource(CoreR.string.guess_math_formula), - image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumber_illustration.jpg?alt=media&token=68faf243-2b0e-4a13-aa9c-223743e263fd", - wordleQuizType = WordleQuizType.MATH_FORMULA - ), - WordleCategory( - id = 4, - name = UiText.StringResource(CoreR.string.number_trivia), - image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumber_12_in_beach.jpg?alt=media&token=9b888c81-c51c-49ac-a376-0b3bde45db36", - wordleQuizType = WordleQuizType.NUMBER_TRIVIA, - requireInternetConnection = true - ) -) - @Composable @AllPreviewsNightLight @OptIn(ExperimentalMaterial3Api::class) From c8684becbad924ddcd30f6b2d59ffb7d8a047532 Mon Sep 17 00:00:00 2001 From: joaomanaia Date: Thu, 15 Jun 2023 16:54:27 +0100 Subject: [PATCH 2/4] Updated wordle home list categories --- .../MultiChoiceQuizLoggingAnalyticsImpl.kt | 2 +- .../core/ui/home/HomeCategoriesItems.kt | 10 +- .../DailyChallengeScreenNavigator.kt | 4 +- .../category/MultiChoiceQuestionCategories.kt | 39 +------- .../data/local/wordle/WordleCategories.kt | 6 -- .../util/DailyChallengeTypeTitleUtil.kt | 2 +- .../home/RecentCategoriesRepositoryImpl.kt | 90 ++++++++++++------ .../MultiChoiceQuestionRepositoryImpl.kt | 4 +- .../data/local/wordle/WordleCategoriesTest.kt | 32 +++++++ .../home/RecentCategoriesRepository.kt | 5 + .../newquiz/model/global_event/GameEvent.kt | 6 +- .../MultiChoiceBaseCategory.kt | 44 ++++----- .../multi_choice_quiz/MultiChoiceCategory.kt | 7 +- .../MultiChoiceQuestionEntity.kt | 2 +- .../newquiz/model/wordle/WordleCategory.kt | 6 +- .../MultiChoiceQuizScreenViewModel.kt | 2 +- .../list/util/MultiChoiceQuizListUtils.kt | 55 ----------- .../list/util/MultiChoiceQuizListUtilsTest.kt | 95 ------------------- .../newquiz/wordle/di/TestRepositoryModule.kt | 5 + .../newquiz/wordle/list/WordleListScreen.kt | 44 +++++---- .../wordle/list/WordleListScreenViewModel.kt | 30 ++++++ .../newquiz/wordle/list/WordleListUiState.kt | 11 +++ .../wordle/util/worker/WordleEndGameWorker.kt | 10 +- .../wordle/list/WordleListScreenTest.kt | 32 ------- 24 files changed, 221 insertions(+), 322 deletions(-) create mode 100644 data/src/test/java/com/infinitepower/newquiz/data/local/wordle/WordleCategoriesTest.kt delete mode 100644 multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/util/MultiChoiceQuizListUtils.kt delete mode 100644 multi-choice-quiz/src/test/java/com/infinitepower/newquiz/multi_choice_quiz/list/util/MultiChoiceQuizListUtilsTest.kt create mode 100644 wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreenViewModel.kt create mode 100644 wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListUiState.kt delete mode 100644 wordle/src/test/java/com/infinitepower/newquiz/wordle/list/WordleListScreenTest.kt diff --git a/core/src/main/java/com/infinitepower/newquiz/core/analytics/logging/multi_choice_quiz/MultiChoiceQuizLoggingAnalyticsImpl.kt b/core/src/main/java/com/infinitepower/newquiz/core/analytics/logging/multi_choice_quiz/MultiChoiceQuizLoggingAnalyticsImpl.kt index 1a4ca095..13a716ba 100644 --- a/core/src/main/java/com/infinitepower/newquiz/core/analytics/logging/multi_choice_quiz/MultiChoiceQuizLoggingAnalyticsImpl.kt +++ b/core/src/main/java/com/infinitepower/newquiz/core/analytics/logging/multi_choice_quiz/MultiChoiceQuizLoggingAnalyticsImpl.kt @@ -53,7 +53,7 @@ class MultiChoiceQuizLoggingAnalyticsImpl @Inject constructor( override fun logCategoryClicked(category: MultiChoiceBaseCategory) { firebaseAnalytics.logEvent(EVENT_CATEGORY_CLICKED) { - param(PARAM_ID, category.key) + param(PARAM_ID, category.id) } } diff --git a/core/src/main/java/com/infinitepower/newquiz/core/ui/home/HomeCategoriesItems.kt b/core/src/main/java/com/infinitepower/newquiz/core/ui/home/HomeCategoriesItems.kt index 8419e0d9..2bf5234d 100644 --- a/core/src/main/java/com/infinitepower/newquiz/core/ui/home/HomeCategoriesItems.kt +++ b/core/src/main/java/com/infinitepower/newquiz/core/ui/home/HomeCategoriesItems.kt @@ -21,14 +21,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.infinitepower.newquiz.core.R import com.infinitepower.newquiz.core.ui.components.category.CategoryComponent -import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory +import com.infinitepower.newquiz.model.BaseCategory -fun LazyListScope.homeCategoriesItems( +fun LazyListScope.homeCategoriesItems( seeAllCategories: Boolean, - recentCategories: List, - otherCategories: List, + recentCategories: List, + otherCategories: List, isInternetAvailable: Boolean, - onCategoryClick: (MultiChoiceCategory) -> Unit, + onCategoryClick: (T) -> Unit, onSeeAllCategoriesClick: () -> Unit, ) { items( diff --git a/daily_challenge/src/main/java/com/infinitepower/newquiz/daily_challenge/DailyChallengeScreenNavigator.kt b/daily_challenge/src/main/java/com/infinitepower/newquiz/daily_challenge/DailyChallengeScreenNavigator.kt index aaafb755..c4db0cc8 100644 --- a/daily_challenge/src/main/java/com/infinitepower/newquiz/daily_challenge/DailyChallengeScreenNavigator.kt +++ b/daily_challenge/src/main/java/com/infinitepower/newquiz/daily_challenge/DailyChallengeScreenNavigator.kt @@ -22,8 +22,8 @@ interface DailyChallengeScreenNavigator { // Navigate to multi choice quiz with category is GameEvent.MultiChoice.PlayQuizWithCategory -> { - val categoryKey = event.categoryKey - val category = MultiChoiceBaseCategory.fromKey(categoryKey) + val categoryKey = event.categoryId + val category = MultiChoiceBaseCategory.fromId(categoryKey) navigateToMultiChoiceQuiz(category) } diff --git a/data/src/main/java/com/infinitepower/newquiz/data/local/multi_choice_quiz/category/MultiChoiceQuestionCategories.kt b/data/src/main/java/com/infinitepower/newquiz/data/local/multi_choice_quiz/category/MultiChoiceQuestionCategories.kt index 9069e4d3..31a81bef 100644 --- a/data/src/main/java/com/infinitepower/newquiz/data/local/multi_choice_quiz/category/MultiChoiceQuestionCategories.kt +++ b/data/src/main/java/com/infinitepower/newquiz/data/local/multi_choice_quiz/category/MultiChoiceQuestionCategories.kt @@ -7,176 +7,147 @@ import com.infinitepower.newquiz.core.R as CoreR val multiChoiceQuestionCategories = listOf( MultiChoiceCategory( - key = MultiChoiceBaseCategory.Logo.key, - id = "1001", + id = MultiChoiceBaseCategory.Logo.id, name = UiText.StringResource(CoreR.string.logo_quiz), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Flogo_quiz_illustration.jpg?alt=media&token=cd9e54a2-a5d1-45f1-a285-cc490cc44cad" ), MultiChoiceCategory( - key = MultiChoiceBaseCategory.Flag.key, - id = "1002", + id = MultiChoiceBaseCategory.Flag.id, name = UiText.StringResource(CoreR.string.flag_quiz), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fflags_illustration.png?alt=media&token=ec6b2820-1d26-4352-9c54-201bd387ae94" ), MultiChoiceCategory( - key = MultiChoiceBaseCategory.CountryCapitalFlags.key, - id = "1003", + id = MultiChoiceBaseCategory.CountryCapitalFlags.id, name = UiText.StringResource(CoreR.string.country_capital_flags), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fflags_illustration.png?alt=media&token=ec6b2820-1d26-4352-9c54-201bd387ae94" ), MultiChoiceCategory( - key = MultiChoiceBaseCategory.GuessMathSolution.key, - id = "1004", + id = MultiChoiceBaseCategory.GuessMathSolution.id, name = UiText.StringResource(CoreR.string.guess_solution), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumber_illustration.jpg?alt=media&token=68faf243-2b0e-4a13-aa9c-223743e263fd", requireInternetConnection = false ), MultiChoiceCategory( - key = MultiChoiceBaseCategory.NumberTrivia.key, - id = "1005", + id = MultiChoiceBaseCategory.NumberTrivia.id, name = UiText.StringResource(CoreR.string.number_trivia), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumber_12_in_beach.jpg?alt=media&token=9b888c81-c51c-49ac-a376-0b3bde45db36" ), MultiChoiceCategory( - key = "General knowledge", id = "9", name = UiText.StringResource(CoreR.string.general_knowledge), image = CoreR.drawable.general_knowledge ), MultiChoiceCategory( - key = "Books", id = "10", name = UiText.StringResource(CoreR.string.entertainment_books), image = CoreR.drawable.books ), MultiChoiceCategory( - key = "Film", id = "11", name = UiText.StringResource(CoreR.string.entertainment_film), image = CoreR.drawable.films ), MultiChoiceCategory( - key = "Music", id = "12", name = UiText.StringResource(CoreR.string.entertainment_music), image = CoreR.drawable.music ), MultiChoiceCategory( - key = "General Musicals & Theatres", id = "13", name = UiText.StringResource(CoreR.string.entertainment_musicals_and_theatres), image = CoreR.drawable.musicals_and_theatres ), MultiChoiceCategory( - key = "Television", id = "14", name = UiText.StringResource(CoreR.string.entertainment_television), image = CoreR.drawable.entertainment_television ), MultiChoiceCategory( - key = "Video Games", id = "15", name = UiText.StringResource(CoreR.string.entertainment_video_games), image = CoreR.drawable.entertainment_video_games ), MultiChoiceCategory( - key = "Board Games", id = "16", name = UiText.StringResource(CoreR.string.entertainment_board_games), image = CoreR.drawable.entertainment_board_games ), MultiChoiceCategory( - key = "Science & Nature", id = "17", name = UiText.StringResource(CoreR.string.science_and_nature), image = CoreR.drawable.science_and_nature ), MultiChoiceCategory( - key = "Computers", id = "18", name = UiText.StringResource(CoreR.string.science_computers), image = CoreR.drawable.science_computers ), MultiChoiceCategory( - key = "Mathematics", id = "19", name = UiText.StringResource(CoreR.string.science_mathematics), image = CoreR.drawable.science_mathematics ), MultiChoiceCategory( - key = "Mythology", id = "20", name = UiText.StringResource(CoreR.string.mythology), image = CoreR.drawable.mythology ), MultiChoiceCategory( - key = "Sports", id = "21", name = UiText.StringResource(CoreR.string.sports), image = CoreR.drawable.sports ), MultiChoiceCategory( - key = "Geography", id = "22", name = UiText.StringResource(CoreR.string.geography), image = CoreR.drawable.geography ), MultiChoiceCategory( - key = "History", id = "23", name = UiText.StringResource(CoreR.string.history), image = CoreR.drawable.history ), MultiChoiceCategory( - key = "Politics", id = "24", name = UiText.StringResource(CoreR.string.politics), image = CoreR.drawable.politics ), MultiChoiceCategory( - key = "Art", id = "25", name = UiText.StringResource(CoreR.string.art), image = CoreR.drawable.art ), MultiChoiceCategory( - key = "Celebrities", id = "26", name = UiText.StringResource(CoreR.string.celebrities), image = CoreR.drawable.celebrities ), MultiChoiceCategory( - key = "Animals", id = "27", name = UiText.StringResource(CoreR.string.animals), image = CoreR.drawable.animals ), MultiChoiceCategory( - key = "Vehicles", id = "28", name = UiText.StringResource(CoreR.string.vehicles), image = CoreR.drawable.vehicles ), MultiChoiceCategory( - key = "Comics", id = "29", name = UiText.StringResource(CoreR.string.entertainment_comics), image = CoreR.drawable.entertainment_comics ), MultiChoiceCategory( - key = "Gadgets", id = "30", name = UiText.StringResource(CoreR.string.science_gadgets), image = CoreR.drawable.science_gadgets ), MultiChoiceCategory( - key = "Japanese Anime & Manga", id = "31", name = UiText.StringResource(CoreR.string.entertainment_japanese_anime_and_manga), image = CoreR.drawable.entertainment_japanese_anime_and_manga ), MultiChoiceCategory( - key = "Cartoon & Animations", id = "32", name = UiText.StringResource(CoreR.string.entertainment_cartoon_and_animations), image = CoreR.drawable.entertainment_cartoon_and_animations diff --git a/data/src/main/java/com/infinitepower/newquiz/data/local/wordle/WordleCategories.kt b/data/src/main/java/com/infinitepower/newquiz/data/local/wordle/WordleCategories.kt index d752f86b..6ff08a71 100644 --- a/data/src/main/java/com/infinitepower/newquiz/data/local/wordle/WordleCategories.kt +++ b/data/src/main/java/com/infinitepower/newquiz/data/local/wordle/WordleCategories.kt @@ -11,13 +11,11 @@ object WordleCategories { * Returns a random [WordleCategory] from the list of [allCategories]. * If [isInternetAvailable] is false, it will only return categories that don't require internet connection. * - * @param allCategories The list of all categories to get the random category. * @param isInternetAvailable Whether the internet is available or not. * @param random The random instance to use. * @see [WordleCategory] */ fun random( - allCategories: List, isInternetAvailable: Boolean, random: Random = Random ): WordleCategory = if (isInternetAvailable) { @@ -28,25 +26,21 @@ object WordleCategories { val allCategories = listOf( WordleCategory( - id = "guess_the_word", name = UiText.StringResource(CoreR.string.guess_the_word), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fwordle_illustration.jpg?alt=media&token=69019438-4904-4656-8b1c-18678c537d6b", wordleQuizType = WordleQuizType.TEXT ), WordleCategory( - id = "guess_the_number", name = UiText.StringResource(CoreR.string.guess_the_number), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumbers_12345_illustration.jpg?alt=media&token=f170e7ca-02a3-4dae-87f0-63b0f1205bc5", wordleQuizType = WordleQuizType.NUMBER ), WordleCategory( - id = "guess_math_formula", name = UiText.StringResource(CoreR.string.guess_math_formula), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumber_illustration.jpg?alt=media&token=68faf243-2b0e-4a13-aa9c-223743e263fd", wordleQuizType = WordleQuizType.MATH_FORMULA ), WordleCategory( - id = "number_trivia", name = UiText.StringResource(CoreR.string.number_trivia), image = "https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fnumber_12_in_beach.jpg?alt=media&token=9b888c81-c51c-49ac-a376-0b3bde45db36", wordleQuizType = WordleQuizType.NUMBER_TRIVIA, diff --git a/data/src/main/java/com/infinitepower/newquiz/data/repository/daily_challenge/util/DailyChallengeTypeTitleUtil.kt b/data/src/main/java/com/infinitepower/newquiz/data/repository/daily_challenge/util/DailyChallengeTypeTitleUtil.kt index 6cd88b6e..5455a8ef 100644 --- a/data/src/main/java/com/infinitepower/newquiz/data/repository/daily_challenge/util/DailyChallengeTypeTitleUtil.kt +++ b/data/src/main/java/com/infinitepower/newquiz/data/repository/daily_challenge/util/DailyChallengeTypeTitleUtil.kt @@ -35,7 +35,7 @@ fun GameEvent.getTitle( maxValue ) is GameEvent.MultiChoice.PlayQuizWithCategory -> { - val category = multiChoiceQuestionCategories.first { it.key == this.categoryKey } + val category = multiChoiceQuestionCategories.first { it.id == this.categoryId } UiText.PluralStringResource( resId = CoreR.plurals.play_multi_choice_quiz_game_in_category, diff --git a/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt b/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt index c356dca1..70340e32 100644 --- a/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt +++ b/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt @@ -5,10 +5,13 @@ import com.infinitepower.newquiz.core.dataStore.manager.DataStoreManager import com.infinitepower.newquiz.core.dataStore.manager.PreferenceRequest import com.infinitepower.newquiz.core.di.RecentCategoriesDataStoreManager import com.infinitepower.newquiz.data.local.multi_choice_quiz.category.multiChoiceQuestionCategories +import com.infinitepower.newquiz.data.local.wordle.WordleCategories import com.infinitepower.newquiz.domain.repository.home.HomeCategories import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository +import com.infinitepower.newquiz.model.BaseCategory import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceBaseCategory import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory +import com.infinitepower.newquiz.model.wordle.WordleCategory import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -18,44 +21,69 @@ import javax.inject.Singleton class RecentCategoriesRepositoryImpl @Inject constructor( @RecentCategoriesDataStoreManager private val recentCategoriesDataStoreManager: DataStoreManager ) : RecentCategoriesRepository { - override fun getMultiChoiceCategories(): Flow> = recentCategoriesDataStoreManager - .getPreferenceFlow(RecentCategoryDataStoreCommon.MultiChoice) - .map { recentCategoriesIds -> - val allCategories = getAllCategories(isInternetAvailable = true) - - val recentCategories = getRecentCategories( - recentCategories = recentCategoriesIds.mapNotNull { id -> - allCategories.find { it.key == id } - }, - allCategories = allCategories, - isInternetAvailable = true - ) + override fun getMultiChoiceCategories(): Flow> = getHomeCategories( + allCategories = multiChoiceQuestionCategories, + request = RecentCategoryDataStoreCommon.MultiChoice, + isInternetAvailable = true + ) - val otherCategories = allCategories - recentCategories.toSet() + override fun getWordleCategories(): Flow> = getHomeCategories( + allCategories = WordleCategories.allCategories, + request = RecentCategoryDataStoreCommon.Wordle, + isInternetAvailable = true + ) - HomeCategories( - recentCategories = recentCategories, - otherCategories = otherCategories + private fun getHomeCategories( + allCategories: List, + request: PreferenceRequest>, + isInternetAvailable: Boolean + ): Flow> = recentCategoriesDataStoreManager + .getPreferenceFlow(request) + .map { recentCategoriesIds -> + getHomeBaseCategories( + savedRecentCategoriesIds = recentCategoriesIds, + allCategories = allCategories, + isInternetAvailable = isInternetAvailable ) } - private fun getAllCategories( + private fun getHomeBaseCategories( + savedRecentCategoriesIds: Set, + allCategories: List, isInternetAvailable: Boolean - ): List { - // If there is no internet, we make the categories that don't require internet connection - // in the top of the list - return if (isInternetAvailable) { - multiChoiceQuestionCategories - } else { - multiChoiceQuestionCategories.sortedBy { it.requireInternetConnection } + ): HomeCategories { + val savedRecentCategories = savedRecentCategoriesIds.mapNotNull { id -> + allCategories.find { it.id == id } } + + val recentCategories = getRecentCategories( + recentCategories = savedRecentCategories, + allCategories = allCategories, + isInternetAvailable = isInternetAvailable + ) + + val otherCategories = allCategories - recentCategories.toSet() + + return HomeCategories( + recentCategories = recentCategories, + otherCategories = otherCategories.sortByInternetConnection(isInternetAvailable) + ) } - private fun getRecentCategories( - recentCategories: List, - allCategories: List, + /** + * If there is internet available, we return all the categories normally, + * but if there is no internet, we make the categories that don't require internet connection + * in the top of the list. + */ + private fun List.sortByInternetConnection( isInternetAvailable: Boolean - ): List { + ): List = if (isInternetAvailable) this else sortedBy { it.requireInternetConnection } + + private fun getRecentCategories( + recentCategories: List, + allCategories: List, + isInternetAvailable: Boolean + ): List { // When there are recent categories, we return them return recentCategories.ifEmpty { // If there are no recent categories, we take 3 random ones, @@ -69,7 +97,11 @@ class RecentCategoriesRepositoryImpl @Inject constructor( } override suspend fun addMultiChoiceCategory(category: MultiChoiceBaseCategory) { - addCategory(category.key, RecentCategoryDataStoreCommon.MultiChoice) + addCategory(category.id, RecentCategoryDataStoreCommon.MultiChoice) + } + + override suspend fun addWordleCategory(categoryId: String) { + addCategory(categoryId, RecentCategoryDataStoreCommon.Wordle) } override suspend fun cleanMultiChoiceCategories() { diff --git a/data/src/main/java/com/infinitepower/newquiz/data/repository/multi_choice_quiz/MultiChoiceQuestionRepositoryImpl.kt b/data/src/main/java/com/infinitepower/newquiz/data/repository/multi_choice_quiz/MultiChoiceQuestionRepositoryImpl.kt index 46ac0fd2..ab36911b 100644 --- a/data/src/main/java/com/infinitepower/newquiz/data/repository/multi_choice_quiz/MultiChoiceQuestionRepositoryImpl.kt +++ b/data/src/main/java/com/infinitepower/newquiz/data/repository/multi_choice_quiz/MultiChoiceQuestionRepositoryImpl.kt @@ -1,6 +1,5 @@ package com.infinitepower.newquiz.data.repository.multi_choice_quiz -import com.infinitepower.newquiz.data.local.multi_choice_quiz.category.multiChoiceQuestionCategories import com.infinitepower.newquiz.domain.repository.multi_choice_quiz.MultiChoiceQuestionRepository import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceBaseCategory import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceQuestion @@ -58,8 +57,7 @@ class MultiChoiceQuestionRepositoryImpl @Inject constructor( parameter("encode", "base64") parameter("amount", amount) if (category.hasCategory) { - val categoryDB = multiChoiceQuestionCategories.find { it.key == category.categoryKey } - if (categoryDB != null) parameter("category", categoryDB.id) + parameter("category", category.categoryId) } if (difficulty != null) parameter("difficulty", difficulty) diff --git a/data/src/test/java/com/infinitepower/newquiz/data/local/wordle/WordleCategoriesTest.kt b/data/src/test/java/com/infinitepower/newquiz/data/local/wordle/WordleCategoriesTest.kt new file mode 100644 index 00000000..219b0f8a --- /dev/null +++ b/data/src/test/java/com/infinitepower/newquiz/data/local/wordle/WordleCategoriesTest.kt @@ -0,0 +1,32 @@ +package com.infinitepower.newquiz.data.local.wordle + +import com.google.common.truth.Truth +import org.junit.jupiter.api.Test + +internal class WordleCategoriesTest { + @Test + fun `test getRandomWordleCategory when not internet connection`() { + val allCategories = WordleCategories.allCategories + val isInternetAvailable = false + + val categoriesWithoutInternet = allCategories.filter { !it.requireInternetConnection } + + val randomCategory = WordleCategories.random(isInternetAvailable) + + Truth.assertThat(randomCategory).isIn(categoriesWithoutInternet) + Truth.assertThat(randomCategory.requireInternetConnection).isEqualTo(isInternetAvailable) + } + + @Test + fun `test getRandomWordleCategory when internet connection`() { + val allCategories = WordleCategories.allCategories + val isInternetAvailable = true + + val randomCategory = WordleCategories.random(isInternetAvailable) + + Truth.assertThat(randomCategory).isIn(allCategories) + + // If internet is available, it can return any category + Truth.assertThat(randomCategory.requireInternetConnection).isAnyOf(true, false) + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt b/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt index df0900a7..cf7868f1 100644 --- a/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt +++ b/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt @@ -3,6 +3,7 @@ package com.infinitepower.newquiz.domain.repository.home import androidx.annotation.Keep import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceBaseCategory import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory +import com.infinitepower.newquiz.model.wordle.WordleCategory import kotlinx.coroutines.flow.Flow @Keep @@ -19,8 +20,12 @@ fun emptyHomeCategories() = HomeCategories( interface RecentCategoriesRepository { fun getMultiChoiceCategories(): Flow> + fun getWordleCategories(): Flow> + suspend fun addMultiChoiceCategory(category: MultiChoiceBaseCategory) + suspend fun addWordleCategory(categoryId: String) + suspend fun cleanMultiChoiceCategories() suspend fun cleanAll() diff --git a/model/src/main/java/com/infinitepower/newquiz/model/global_event/GameEvent.kt b/model/src/main/java/com/infinitepower/newquiz/model/global_event/GameEvent.kt index 3737fb41..287a38bf 100644 --- a/model/src/main/java/com/infinitepower/newquiz/model/global_event/GameEvent.kt +++ b/model/src/main/java/com/infinitepower/newquiz/model/global_event/GameEvent.kt @@ -71,7 +71,7 @@ sealed class GameEvent( random: Random = Random ): List { val multiChoicePlayQuizWithCategory = MultiChoice.PlayQuizWithCategory( - categoryKey = multiChoiceCategories.random(random).key + categoryId = multiChoiceCategories.random(random).id ) val wordleWithType = Wordle.PlayWordWithCategory( @@ -118,9 +118,9 @@ sealed class GameEvent( @Keep data class PlayQuizWithCategory( - val categoryKey: String + val categoryId: String ) : MultiChoice( - key = "$KEY_PREFIX$categoryKey", + key = "$KEY_PREFIX$categoryId", taskValueRange = 1u..5u ) { companion object { diff --git a/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceBaseCategory.kt b/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceBaseCategory.kt index 5e292bfd..2c6c0ef3 100644 --- a/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceBaseCategory.kt +++ b/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceBaseCategory.kt @@ -10,23 +10,23 @@ import kotlinx.serialization.encoding.Encoder @Serializable(with = MultiChoiceBaseCategorySerializer::class) sealed class MultiChoiceBaseCategory( - val key: String, + val id: String, ) : java.io.Serializable { companion object { - fun fromKey(key: String) = when (key) { - Logo.key -> Logo - Flag.key -> Flag - GuessMathSolution.key -> GuessMathSolution - NumberTrivia.key -> NumberTrivia - CountryCapitalFlags.key -> CountryCapitalFlags - else -> Normal(key) + fun fromId(id: String) = when (id) { + Logo.id -> Logo + Flag.id -> Flag + GuessMathSolution.id -> GuessMathSolution + NumberTrivia.id -> NumberTrivia + CountryCapitalFlags.id -> CountryCapitalFlags + else -> Normal(id) } } - override fun toString(): String = key + override fun toString(): String = id val hasCategory: Boolean - get() = key.isNotBlank() && key != "random" + get() = id.isNotBlank() && id != "random" /** * Random multi choice category using [Normal] class @@ -35,39 +35,39 @@ sealed class MultiChoiceBaseCategory( /** * Normal multi choice type with category - * @param categoryKey category to the quiz + * @param categoryId category to the quiz */ open class Normal( - val categoryKey: String - ) : MultiChoiceBaseCategory(key = categoryKey) { + val categoryId: String + ) : MultiChoiceBaseCategory(id = categoryId) { /** Sets multi choice type as no category */ constructor() : this("random") - override fun toString(): String = categoryKey + override fun toString(): String = categoryId override fun equals(other: Any?): Boolean { if (other !is Normal) return false - return this.categoryKey == other.categoryKey + return this.categoryId == other.categoryId } - override fun hashCode(): Int = categoryKey.hashCode() + override fun hashCode(): Int = categoryId.hashCode() } /** Logo multi choice quiz category */ - object Logo : MultiChoiceBaseCategory(key = "logo") + object Logo : MultiChoiceBaseCategory(id = "logo") /** Flag multi choice quiz category */ - object Flag : MultiChoiceBaseCategory(key = "flag") + object Flag : MultiChoiceBaseCategory(id = "flag") /** Number trivia multi choice quiz category */ - object CountryCapitalFlags : MultiChoiceBaseCategory(key = "country_capital_flags") + object CountryCapitalFlags : MultiChoiceBaseCategory(id = "country_capital_flags") /** Guess math solution multi choice quiz category */ - object GuessMathSolution : MultiChoiceBaseCategory(key = "guess_math_solution") + object GuessMathSolution : MultiChoiceBaseCategory(id = "guess_math_solution") /** Number trivia multi choice quiz category */ - object NumberTrivia : MultiChoiceBaseCategory(key = "number_trivia") + object NumberTrivia : MultiChoiceBaseCategory(id = "number_trivia") } object MultiChoiceBaseCategorySerializer : KSerializer { @@ -76,7 +76,7 @@ object MultiChoiceBaseCategorySerializer : KSerializer } override fun deserialize(decoder: Decoder): MultiChoiceBaseCategory { - return MultiChoiceBaseCategory.fromKey(decoder.decodeString()) + return MultiChoiceBaseCategory.fromId(decoder.decodeString()) } override val descriptor: SerialDescriptor diff --git a/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceCategory.kt b/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceCategory.kt index 0bd660cb..10694cff 100644 --- a/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceCategory.kt +++ b/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceCategory.kt @@ -9,10 +9,7 @@ data class MultiChoiceCategory( override val id: String, override val name: UiText, override val image: Any, - override val requireInternetConnection: Boolean = true, - - // The key to use in the MultiChoiceBaseCategory - val key: String + override val requireInternetConnection: Boolean = true ) : BaseCategory -fun MultiChoiceCategory.toBaseCategory() = MultiChoiceBaseCategory.fromKey(key) +fun MultiChoiceCategory.toBaseCategory() = MultiChoiceBaseCategory.fromId(id) diff --git a/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceQuestionEntity.kt b/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceQuestionEntity.kt index f7794984..3d1cad7e 100644 --- a/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceQuestionEntity.kt +++ b/model/src/main/java/com/infinitepower/newquiz/model/multi_choice_quiz/MultiChoiceQuestionEntity.kt @@ -68,7 +68,7 @@ fun MultiChoiceQuestionEntity.toQuestion(): MultiChoiceQuestion = MultiChoiceQue imageUrl = imageUrl, answers = answers, lang = QuestionLanguage.EN, - category = MultiChoiceBaseCategory.fromKey(category), + category = MultiChoiceBaseCategory.fromId(category), correctAns = correctAns, type = MultiChoiceQuestionType.MULTIPLE, difficulty = QuestionDifficulty.from(difficulty) diff --git a/model/src/main/java/com/infinitepower/newquiz/model/wordle/WordleCategory.kt b/model/src/main/java/com/infinitepower/newquiz/model/wordle/WordleCategory.kt index 29058f82..28ff0bec 100644 --- a/model/src/main/java/com/infinitepower/newquiz/model/wordle/WordleCategory.kt +++ b/model/src/main/java/com/infinitepower/newquiz/model/wordle/WordleCategory.kt @@ -6,9 +6,9 @@ import com.infinitepower.newquiz.model.UiText @Keep data class WordleCategory( - override val id: String, + val wordleQuizType: WordleQuizType, + override val id: String = wordleQuizType.name, override val name: UiText, override val image: Any, - override val requireInternetConnection: Boolean = false, - val wordleQuizType: WordleQuizType + override val requireInternetConnection: Boolean = false ) : BaseCategory diff --git a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/MultiChoiceQuizScreenViewModel.kt b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/MultiChoiceQuizScreenViewModel.kt index adf2f7c9..6fb8cfbb 100644 --- a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/MultiChoiceQuizScreenViewModel.kt +++ b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/MultiChoiceQuizScreenViewModel.kt @@ -213,7 +213,7 @@ class QuizScreenViewModel @Inject constructor( viewModelScope.launch(Dispatchers.IO) { // Update global event data, for daily challenge and achievements val event = if (category.hasCategory) { - GameEvent.MultiChoice.PlayQuizWithCategory(category.key) + GameEvent.MultiChoice.PlayQuizWithCategory(category.id) } else { GameEvent.MultiChoice.PlayRandomQuiz } diff --git a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/util/MultiChoiceQuizListUtils.kt b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/util/MultiChoiceQuizListUtils.kt deleted file mode 100644 index 8c01142a..00000000 --- a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/util/MultiChoiceQuizListUtils.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.infinitepower.newquiz.multi_choice_quiz.list.util - -import com.infinitepower.newquiz.data.local.multi_choice_quiz.category.multiChoiceQuestionCategories -import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory - -object MultiChoiceQuizListUtils { - /** - * This function returns all the categories of the multi-choice questions. - * If there is no internet connection, it sorts the categories that don't require internet connection - * in the top of the list. - * - * @param isInternetAvailable Whether the internet is available or not - * @return All the categories of the multi-choice questions. - */ - fun getAllCategories( - isInternetAvailable: Boolean - ): List { - // If there is no internet, we make the categories that don't require internet connection - // in the top of the list - return if (isInternetAvailable) { - multiChoiceQuestionCategories - } else { - multiChoiceQuestionCategories.sortedBy { it.requireInternetConnection } - } - } - - /** - * This function returns the recent categories if there are any, otherwise it returns 3 random categories - * from the list of all categories (if there is no internet connection, it only returns the categories that don't - * require internet connection). - * - * @param recentCategories The list of recent categories - * @param allCategories The list of all categories - * @param isInternetAvailable Whether the internet is available or not - * @return The list of recent categories if there are any, otherwise it returns 3 random categories - * from the list of all categories (if there is no internet connection, it only returns the categories that don't - * require internet connection). - */ - fun getRecentCategories( - recentCategories: List, - allCategories: List, - isInternetAvailable: Boolean - ): List { - // When there are recent categories, we return them - return recentCategories.ifEmpty { - // If there are no recent categories, we take 3 random ones, - // So we don't show all categories initially - allCategories - // If there is no internet, we only show the categories that don't require internet connection - .filter { !it.requireInternetConnection || isInternetAvailable } - .shuffled() - .take(3) - } - } -} diff --git a/multi-choice-quiz/src/test/java/com/infinitepower/newquiz/multi_choice_quiz/list/util/MultiChoiceQuizListUtilsTest.kt b/multi-choice-quiz/src/test/java/com/infinitepower/newquiz/multi_choice_quiz/list/util/MultiChoiceQuizListUtilsTest.kt deleted file mode 100644 index bae3a608..00000000 --- a/multi-choice-quiz/src/test/java/com/infinitepower/newquiz/multi_choice_quiz/list/util/MultiChoiceQuizListUtilsTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.infinitepower.newquiz.multi_choice_quiz.list.util - -import com.google.common.collect.Ordering -import com.google.common.truth.Truth.assertThat -import com.infinitepower.newquiz.data.local.multi_choice_quiz.category.multiChoiceQuestionCategories -import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory -import org.junit.jupiter.api.Test - -internal class MultiChoiceQuizListUtilsTest { - @Test - fun `test getAllCategories when internet is available`() { - val isInternetAvailable = true - val categories = MultiChoiceQuizListUtils.getAllCategories(isInternetAvailable) - - assertThat(categories).isNotEmpty() - - // Assert that the categories that require internet connection are also present - assertThat(categories).containsExactlyElementsIn(multiChoiceQuestionCategories) - } - - @Test - fun `test getAllCategories when internet is not available`() { - val isInternetAvailable = false - val categories = MultiChoiceQuizListUtils.getAllCategories(isInternetAvailable) - - assertThat(categories).isNotEmpty() - - val categoriesSorted = multiChoiceQuestionCategories.sortedBy { it.requireInternetConnection } - - assertThat(categories).containsExactlyElementsIn(multiChoiceQuestionCategories) - assertThat(categories).isInOrder(Ordering.explicit(categoriesSorted)) - } - - @Test - fun `test getRecentCategories when recent categories are not empty`() { - val recentCategories = multiChoiceQuestionCategories.take(3) - val allCategories = multiChoiceQuestionCategories - val isInternetAvailable = true - - val categories = MultiChoiceQuizListUtils.getRecentCategories( - recentCategories = recentCategories, - allCategories = allCategories, - isInternetAvailable = isInternetAvailable - ) - - assertThat(categories).isNotEmpty() - - // When there are recent categories, we return them - assertThat(categories).containsExactlyElementsIn(recentCategories) - } - - @Test - fun `test getRecentCategories when recent categories are empty and internet is available`() { - val initialRecentCategories = emptyList() - val allCategories = multiChoiceQuestionCategories - val isInternetAvailable = true - - val categories = MultiChoiceQuizListUtils.getRecentCategories( - recentCategories = initialRecentCategories, - allCategories = allCategories, - isInternetAvailable = isInternetAvailable - ) - - assertThat(categories).isNotEmpty() - assertThat(categories).containsAnyIn(allCategories) - - // Assert that the returned list of categories have at most 3 elements - assertThat(categories.size).isAtMost(3) - - // Assert that the returned list of categories satisfy the filtering condition - assertThat(categories).containsNoneIn(allCategories.filter { it.requireInternetConnection && !isInternetAvailable }) - } - - @Test - fun `test getRecentCategories when recent categories are empty and internet is not available`() { - val initialRecentCategories = emptyList() - val allCategories = multiChoiceQuestionCategories - val isInternetAvailable = false - - val categories = MultiChoiceQuizListUtils.getRecentCategories( - recentCategories = initialRecentCategories, - allCategories = allCategories, - isInternetAvailable = isInternetAvailable - ) - - assertThat(categories).isNotEmpty() - assertThat(categories).isNotEqualTo(allCategories) - - // Assert that the returned list of categories have at most 3 elements - assertThat(categories.size).isAtMost(3) - - // Assert that the returned list of categories satisfy the filtering condition - assertThat(categories).containsNoneIn(allCategories.filter { it.requireInternetConnection }) - } -} diff --git a/wordle/src/androidTest/java/com/infinitepower/newquiz/wordle/di/TestRepositoryModule.kt b/wordle/src/androidTest/java/com/infinitepower/newquiz/wordle/di/TestRepositoryModule.kt index 832bbf80..7b961963 100644 --- a/wordle/src/androidTest/java/com/infinitepower/newquiz/wordle/di/TestRepositoryModule.kt +++ b/wordle/src/androidTest/java/com/infinitepower/newquiz/wordle/di/TestRepositoryModule.kt @@ -3,6 +3,7 @@ package com.infinitepower.newquiz.wordle.di import com.infinitepower.newquiz.data.di.RepositoryModule import com.infinitepower.newquiz.data.repository.comparison_quiz.ComparisonQuizRepositoryImpl import com.infinitepower.newquiz.data.repository.daily_challenge.DailyChallengeRepositoryImpl +import com.infinitepower.newquiz.data.repository.home.RecentCategoriesRepositoryImpl import com.infinitepower.newquiz.data.repository.math_quiz.MathQuizCoreRepositoryImpl import com.infinitepower.newquiz.data.repository.maze_quiz.MazeQuizRepositoryImpl import com.infinitepower.newquiz.data.repository.multi_choice_quiz.CountryCapitalFlagsQuizRepositoryImpl @@ -15,6 +16,7 @@ import com.infinitepower.newquiz.data.repository.numbers.NumberTriviaQuestionApi import com.infinitepower.newquiz.data.repository.numbers.NumberTriviaQuestionRepositoryImpl import com.infinitepower.newquiz.domain.repository.comparison_quiz.ComparisonQuizRepository import com.infinitepower.newquiz.domain.repository.daily_challenge.DailyChallengeRepository +import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import com.infinitepower.newquiz.domain.repository.math_quiz.MathQuizCoreRepository import com.infinitepower.newquiz.domain.repository.maze.MazeQuizRepository import com.infinitepower.newquiz.domain.repository.multi_choice_quiz.CountryCapitalFlagsQuizRepository @@ -81,4 +83,7 @@ abstract class TestRepositoryModule { @Binds abstract fun bindRemoteConfigApi(impl: TestFirebaseRemoteConfigApiImpl): RemoteConfigApi + + @Binds + abstract fun bindRecentCategoriesRepository(impl: RecentCategoriesRepositoryImpl): RecentCategoriesRepository } \ No newline at end of file diff --git a/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreen.kt b/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreen.kt index 90bebc42..e7194eab 100644 --- a/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreen.kt +++ b/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreen.kt @@ -3,9 +3,7 @@ package com.infinitepower.newquiz.wordle.list import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.QuestionMark import androidx.compose.material3.ExperimentalMaterial3Api @@ -13,15 +11,19 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.infinitepower.newquiz.core.common.annotation.compose.AllPreviewsNightLight import com.infinitepower.newquiz.core.theme.NewQuizTheme import com.infinitepower.newquiz.core.theme.spacing -import com.infinitepower.newquiz.core.ui.components.category.CategoryComponent import com.infinitepower.newquiz.core.ui.components.rememberIsInternetAvailable +import com.infinitepower.newquiz.core.ui.home.homeCategoriesItems import com.infinitepower.newquiz.core.ui.home_card.components.HomeGroupTitle import com.infinitepower.newquiz.core.ui.home_card.components.HomeLargeCard import com.infinitepower.newquiz.core.ui.home_card.model.CardIcon @@ -37,9 +39,13 @@ import com.infinitepower.newquiz.core.R as CoreR @Destination @OptIn(ExperimentalMaterial3Api::class) fun WordleListScreen( - navigator: DestinationsNavigator + navigator: DestinationsNavigator, + homeViewModel: WordleListScreenViewModel = hiltViewModel() ) { + val uiState by homeViewModel.uiState.collectAsStateWithLifecycle() + WordleListScreenImpl( + uiState = uiState, navigateToWordleQuiz = { wordleQuizType -> navigator.navigate(WordleScreenDestination(quizType = wordleQuizType)) } @@ -49,13 +55,14 @@ fun WordleListScreen( @Composable @ExperimentalMaterial3Api private fun WordleListScreenImpl( + uiState: WordleListUiState, navigateToWordleQuiz: (wordleQuizType: WordleQuizType) -> Unit ) { val spaceMedium = MaterialTheme.spacing.medium val isInternetAvailable = rememberIsInternetAvailable() - val categories = remember { WordleCategories.allCategories } + var seeAllCategories by remember { mutableStateOf(false) } LazyColumn( modifier = Modifier.fillMaxSize(), @@ -78,7 +85,7 @@ private fun WordleListScreenImpl( icon = CardIcon.Icon(Icons.Rounded.QuestionMark), backgroundPrimary = true, onClick = { - val randomCategory = WordleCategories.random(categories, isInternetAvailable) + val randomCategory = WordleCategories.random(isInternetAvailable) navigateToWordleQuiz(randomCategory.wordleQuizType) } ) @@ -92,20 +99,14 @@ private fun WordleListScreenImpl( ) } - items( - items = categories, - key = { category -> category.id } - ) { category -> - CategoryComponent( - modifier = Modifier - .fillParentMaxWidth() - .height(120.dp), - title = category.name.asString(), - imageUrl = category.image, - onClick = { navigateToWordleQuiz(category.wordleQuizType) }, - enabled = isInternetAvailable || !category.requireInternetConnection - ) - } + homeCategoriesItems( + seeAllCategories = seeAllCategories, + recentCategories = uiState.homeCategories.recentCategories, + otherCategories = uiState.homeCategories.otherCategories, + isInternetAvailable = isInternetAvailable, + onCategoryClick = { category -> navigateToWordleQuiz(category.wordleQuizType) }, + onSeeAllCategoriesClick = { seeAllCategories = !seeAllCategories } + ) } } @@ -116,6 +117,7 @@ private fun WordleListScreenPreview() { NewQuizTheme { Surface { WordleListScreenImpl( + uiState = WordleListUiState(), navigateToWordleQuiz = {} ) } diff --git a/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreenViewModel.kt b/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreenViewModel.kt new file mode 100644 index 00000000..1ca9a4d6 --- /dev/null +++ b/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreenViewModel.kt @@ -0,0 +1,30 @@ +package com.infinitepower.newquiz.wordle.list + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update +import javax.inject.Inject + +@HiltViewModel +class WordleListScreenViewModel @Inject constructor( + private val recentCategoriesRepository: RecentCategoriesRepository +) : ViewModel() { + private val _uiState = MutableStateFlow(WordleListUiState()) + val uiState = _uiState.asStateFlow() + + init { + recentCategoriesRepository + .getWordleCategories() + .onEach { homeCategories -> + _uiState.update { currentState -> + currentState.copy(homeCategories = homeCategories) + } + }.launchIn(viewModelScope) + } +} \ No newline at end of file diff --git a/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListUiState.kt b/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListUiState.kt new file mode 100644 index 00000000..15874e30 --- /dev/null +++ b/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListUiState.kt @@ -0,0 +1,11 @@ +package com.infinitepower.newquiz.wordle.list + +import androidx.annotation.Keep +import com.infinitepower.newquiz.domain.repository.home.HomeCategories +import com.infinitepower.newquiz.domain.repository.home.emptyHomeCategories +import com.infinitepower.newquiz.model.wordle.WordleCategory + +@Keep +data class WordleListUiState( + val homeCategories: HomeCategories = emptyHomeCategories() +) diff --git a/wordle/src/main/java/com/infinitepower/newquiz/wordle/util/worker/WordleEndGameWorker.kt b/wordle/src/main/java/com/infinitepower/newquiz/wordle/util/worker/WordleEndGameWorker.kt index 8689d471..6888feab 100644 --- a/wordle/src/main/java/com/infinitepower/newquiz/wordle/util/worker/WordleEndGameWorker.kt +++ b/wordle/src/main/java/com/infinitepower/newquiz/wordle/util/worker/WordleEndGameWorker.kt @@ -9,6 +9,7 @@ import com.infinitepower.newquiz.core.analytics.logging.maze.MazeLoggingAnalytic import com.infinitepower.newquiz.core.analytics.logging.wordle.WordleLoggingAnalytics import com.infinitepower.newquiz.core.util.kotlin.toULong import com.infinitepower.newquiz.data.worker.UpdateGlobalEventDataWorker +import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import com.infinitepower.newquiz.model.global_event.GameEvent import com.infinitepower.newquiz.model.wordle.WordleQuizType import com.infinitepower.newquiz.online_services.core.OnlineServicesCore @@ -26,7 +27,8 @@ class WordleEndGameWorker @AssistedInject constructor( private val wordleXpRepository: WordleXpRepository, private val wordleLoggingAnalytics: WordleLoggingAnalytics, private val mazeLoggingAnalytics: MazeLoggingAnalytics, - private val workManager: WorkManager + private val workManager: WorkManager, + private val recentCategoriesRepository: RecentCategoriesRepository ) : CoroutineWorker(appContext, workerParams) { companion object { @@ -43,9 +45,11 @@ class WordleEndGameWorker @AssistedInject constructor( val rowLimit = inputData.getInt(INPUT_ROW_LIMIT, 0) val currentRowPosition = inputData.getInt(INPUT_CURRENT_ROW_POSITION, 0) val isLastRowCorrect = inputData.getBoolean(INPUT_IS_LAST_ROW_CORRECT, false) - val quizType = inputData.getString(INPUT_QUIZ_TYPE) ?: WordleQuizType.TEXT.name + val quizTypeName = inputData.getString(INPUT_QUIZ_TYPE) ?: WordleQuizType.TEXT.name val mazeItemId = inputData.getString(INPUT_MAZE_TEM_ID) + recentCategoriesRepository.addWordleCategory(quizTypeName) + if (isLastRowCorrect) { UpdateGlobalEventDataWorker.enqueueWork( workManager = workManager, @@ -58,7 +62,7 @@ class WordleEndGameWorker @AssistedInject constructor( maxRows = rowLimit, lastRow = currentRowPosition, lastRowCorrect = isLastRowCorrect, - quizType = quizType, + quizType = quizTypeName, mazeItemId = mazeItemId?.toIntOrNull() ) diff --git a/wordle/src/test/java/com/infinitepower/newquiz/wordle/list/WordleListScreenTest.kt b/wordle/src/test/java/com/infinitepower/newquiz/wordle/list/WordleListScreenTest.kt deleted file mode 100644 index cbb4f657..00000000 --- a/wordle/src/test/java/com/infinitepower/newquiz/wordle/list/WordleListScreenTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.infinitepower.newquiz.wordle.list - -import com.google.common.truth.Truth.assertThat -import org.junit.jupiter.api.Test - -internal class WordleListScreenTest { - @Test - fun `test getRandomWordleCategory when not internet connection`() { - val allCategories = getWordleCategories() - val isInternetAvailable = false - - val categoriesWithoutInternet = allCategories.filter { !it.requireInternetConnection } - - val randomCategory = getRandomWordleCategory(allCategories, isInternetAvailable) - - assertThat(randomCategory).isIn(categoriesWithoutInternet) - assertThat(randomCategory.requireInternetConnection).isEqualTo(isInternetAvailable) - } - - @Test - fun `test getRandomWordleCategory when internet connection`() { - val allCategories = getWordleCategories() - val isInternetAvailable = true - - val randomCategory = getRandomWordleCategory(allCategories, isInternetAvailable) - - assertThat(randomCategory).isIn(allCategories) - - // If internet is available, it can return any category - assertThat(randomCategory.requireInternetConnection).isAnyOf(true, false) - } -} From f137624430a6c9e778073204e9831e9272abdf4e Mon Sep 17 00:00:00 2001 From: joaomanaia Date: Fri, 16 Jun 2023 11:40:08 +0100 Subject: [PATCH 3/4] Updated comparison quiz home list categories --- .../navigation/CommonNavGraphNavigator.kt | 2 +- .../main/res/xml/remote_config_defaults.xml | 2 +- buildSrc/src/main/java/ProjectConfig.kt | 4 +- .../list/ComparisonQuizListScreen.kt | 72 +++++++++---------- .../list/ComparisonQuizListScreenUiState.kt | 4 +- .../list/ComparisonQuizListScreenViewModel.kt | 17 +++-- .../ui/ComparisonQuizScreen.kt | 10 +-- .../ui/ComparisonQuizViewModel.kt | 13 +++- .../ComparisonQuizRepositoryImpl.kt | 5 +- .../util/DailyChallengeTypeTitleUtil.kt | 4 +- .../home/RecentCategoriesRepositoryImpl.kt | 34 ++++++--- .../home/RecentCategoriesRepository.kt | 13 ++-- .../comparison_quiz/ComparisonQuizCategory.kt | 27 +++++-- .../ComparisonQuizCategoryEntity.kt | 31 ++++++++ .../ComparisonQuizCategoryTest.kt | 9 +-- .../SettingsScreenUiEvent.kt | 5 +- .../SettingsViewModel.kt | 9 +-- .../components/PreferencesScreen.kt | 6 +- .../data/SettingsScreenPageData.kt | 28 ++++---- .../newquiz/wordle/list/WordleListScreen.kt | 18 +---- 20 files changed, 191 insertions(+), 122 deletions(-) create mode 100644 model/src/main/java/com/infinitepower/newquiz/model/comparison_quiz/ComparisonQuizCategoryEntity.kt diff --git a/app/src/main/java/com/infinitepower/newquiz/core/navigation/CommonNavGraphNavigator.kt b/app/src/main/java/com/infinitepower/newquiz/core/navigation/CommonNavGraphNavigator.kt index 51ecbd5c..00ae99f9 100644 --- a/app/src/main/java/com/infinitepower/newquiz/core/navigation/CommonNavGraphNavigator.kt +++ b/app/src/main/java/com/infinitepower/newquiz/core/navigation/CommonNavGraphNavigator.kt @@ -33,7 +33,7 @@ class CommonNavGraphNavigator( category: ComparisonQuizCategory, mode: ComparisonMode ) { - navController.navigate(ComparisonQuizScreenDestination(category, mode)) + navController.navigate(ComparisonQuizScreenDestination(category.toEntity(), mode)) } override fun navigateToMultiChoiceQuiz(initialQuestions: ArrayList) { diff --git a/app/src/main/res/xml/remote_config_defaults.xml b/app/src/main/res/xml/remote_config_defaults.xml index 8c058299..89c2629b 100644 --- a/app/src/main/res/xml/remote_config_defaults.xml +++ b/app/src/main/res/xml/remote_config_defaults.xml @@ -14,7 +14,7 @@ comparison_quiz_categories - [{"id":"country-population","title":"Country Population","description":"Compare population of each country","imageUrl":"https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fworld_population_illustration.png?alt=media&token=db98d82c-7bbb-41bc-94a8-16771e0699aa","questionDescription":{"greater":"Which country has more population?","less":"Which country has less population?"},"formatType":"number","dataSourceAttribution":{"text":"Data from RESTCountries"}},{"id":"country-area","title":"Country Area","description":"Compare the area of each country (km²)","imageUrl":"https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fflags_illustration.png?alt=media&token=ec6b2820-1d26-4352-9c54-201bd387ae94","questionDescription":{"greater":"Which country has more area?","less":"Which country has less area?"},"formatType":"number","helperValueSuffix":"km²","dataSourceAttribution":{"text":"Data from RESTCountries"}},{"id":"movie-popularity","title":"Movie Popularity","description":"Compare the populatiry of movies.","imageUrl":"https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fmovie_popularity_illustration.png?alt=media&token=9e54b03f-391a-4cd9-9a04-e0a8f29d0b9f","questionDescription":{"greater":"Which movie is more popular?","less":"Which movie is less popular?"},"formatType":"number","dataSourceAttribution":{"text":"Powered by TMDB"}},{"id":"movie-release-date","title":"Movie Release Date","description":"Compare the release date of movies.","imageUrl":"https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fmovie_popularity_illustration.png?alt=media&token=9e54b03f-391a-4cd9-9a04-e0a8f29d0b9f","questionDescription":{"greater":"Which movie is more recent?","less":"Which movie is less recent?"},"formatType":"date","dataSourceAttribution":{"text":"Powered by TMDB"}}] + [{"id":"country-population","name":"Country Population","description":"Compare population of each country","image":"https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fworld_population_illustration.png?alt=media&token=db98d82c-7bbb-41bc-94a8-16771e0699aa","questionDescription":{"greater":"Which country has more population?","less":"Which country has less population?"},"formatType":"number","dataSourceAttribution":{"text":"Data from RESTCountries"}},{"id":"country-area","name":"Country Area","description":"Compare the area of each country (km²)","image":"https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fflags_illustration.png?alt=media&token=ec6b2820-1d26-4352-9c54-201bd387ae94","questionDescription":{"greater":"Which country has more area?","less":"Which country has less area?"},"formatType":"number","helperValueSuffix":"km²","dataSourceAttribution":{"text":"Data from RESTCountries"}},{"id":"movie-popularity","name":"Movie Popularity","description":"Compare the populatiry of movies.","image":"https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fmovie_popularity_illustration.png?alt=media&token=9e54b03f-391a-4cd9-9a04-e0a8f29d0b9f","questionDescription":{"greater":"Which movie is more popular?","less":"Which movie is less popular?"},"formatType":"number","dataSourceAttribution":{"text":"Powered by TMDB"}},{"id":"movie-release-date","name":"Movie Release Date","description":"Compare the release date of movies.","image":"https://firebasestorage.googleapis.com/v0/b/newquiz-app.appspot.com/o/Illustrations%2Fmovie_popularity_illustration.png?alt=media&token=9e54b03f-391a-4cd9-9a04-e0a8f29d0b9f","questionDescription":{"greater":"Which movie is more recent?","less":"Which movie is less recent?"},"formatType":"date","dataSourceAttribution":{"text":"Powered by TMDB"}}] comparison_quiz_skip_cost diff --git a/buildSrc/src/main/java/ProjectConfig.kt b/buildSrc/src/main/java/ProjectConfig.kt index aa704cad..ecca091f 100644 --- a/buildSrc/src/main/java/ProjectConfig.kt +++ b/buildSrc/src/main/java/ProjectConfig.kt @@ -11,9 +11,9 @@ object ProjectConfig { const val targetSdk = 33 - const val versionCode = 13 + const val versionCode = 14 - const val versionName = "1.5.3" + const val versionName = "1.6.0" val javaVersionCompatibility = JavaVersion.VERSION_17 diff --git a/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreen.kt b/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreen.kt index 6618b533..f2f4b7fb 100644 --- a/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreen.kt +++ b/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreen.kt @@ -1,21 +1,18 @@ package com.infinitepower.newquiz.comparison_quiz.list -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.infinitepower.newquiz.comparison_quiz.destinations.ComparisonQuizScreenDestination @@ -23,11 +20,14 @@ import com.infinitepower.newquiz.comparison_quiz.list.components.ComparisonModeC import com.infinitepower.newquiz.core.common.annotation.compose.AllPreviewsNightLight import com.infinitepower.newquiz.core.theme.NewQuizTheme import com.infinitepower.newquiz.core.theme.spacing -import com.infinitepower.newquiz.core.ui.components.category.CategoryComponent import com.infinitepower.newquiz.core.ui.components.rememberIsInternetAvailable +import com.infinitepower.newquiz.core.ui.home.HomeLazyColumn +import com.infinitepower.newquiz.core.ui.home.homeCategoriesItems +import com.infinitepower.newquiz.domain.repository.home.HomeCategories import com.infinitepower.newquiz.model.comparison_quiz.ComparisonMode import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategory import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizFormatType +import com.infinitepower.newquiz.model.toUiText import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.infinitepower.newquiz.core.R as CoreR @@ -45,7 +45,7 @@ fun ComparisonQuizListScreen( onCategoryClick = { category -> navigator.navigate( ComparisonQuizScreenDestination( - category = category, + category = category.toEntity(), comparisonMode = uiState.selectedMode ) ) @@ -66,10 +66,9 @@ private fun ComparisonQuizListScreenImpl( val isInternetAvailable = rememberIsInternetAvailable() - LazyColumn( - contentPadding = PaddingValues(spaceMedium), - verticalArrangement = Arrangement.spacedBy(spaceMedium) - ) { + var seeAllCategories by remember { mutableStateOf(false) } + + HomeLazyColumn { item { Column { Text( @@ -92,20 +91,14 @@ private fun ComparisonQuizListScreenImpl( ) } - items( - items = uiState.categories, - key = { category -> category.id } - ) { category -> - CategoryComponent( - modifier = Modifier - .fillMaxWidth() - .height(120.dp), - title = category.title, - imageUrl = category.imageUrl, - onClick = { onCategoryClick(category) }, - enabled = isInternetAvailable - ) - } + homeCategoriesItems( + seeAllCategories = seeAllCategories, + recentCategories = uiState.homeCategories.recentCategories, + otherCategories = uiState.homeCategories.otherCategories, + isInternetAvailable = isInternetAvailable, + onCategoryClick = onCategoryClick, + onSeeAllCategoriesClick = { seeAllCategories = !seeAllCategories } + ) } } @@ -116,18 +109,21 @@ private fun ComparisonQuizListScreenPreview() { Surface { ComparisonQuizListScreenImpl( uiState = ComparisonQuizListScreenUiState( - categories = listOf( - ComparisonQuizCategory( - id = "test", - description = "Description", - title = "Title", - imageUrl = "", - questionDescription = ComparisonQuizCategory.QuestionDescription( - greater = "Greater", - less = "Less" - ), - formatType = ComparisonQuizFormatType.Number - ) + homeCategories = HomeCategories( + recentCategories = listOf( + ComparisonQuizCategory( + id = "test", + description = "Description", + name = "Title".toUiText(), + image = "", + questionDescription = ComparisonQuizCategory.QuestionDescription( + greater = "Greater", + less = "Less" + ), + formatType = ComparisonQuizFormatType.Number + ) + ), + otherCategories = emptyList() ) ), onCategoryClick = {}, diff --git a/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreenUiState.kt b/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreenUiState.kt index 5f483333..954ed076 100644 --- a/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreenUiState.kt +++ b/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreenUiState.kt @@ -1,11 +1,13 @@ package com.infinitepower.newquiz.comparison_quiz.list import androidx.annotation.Keep +import com.infinitepower.newquiz.domain.repository.home.HomeCategories +import com.infinitepower.newquiz.domain.repository.home.emptyHomeCategories import com.infinitepower.newquiz.model.comparison_quiz.ComparisonMode import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategory @Keep data class ComparisonQuizListScreenUiState( - val categories: List = emptyList(), + val homeCategories: HomeCategories = emptyHomeCategories(), val selectedMode: ComparisonMode = ComparisonMode.GREATER ) diff --git a/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreenViewModel.kt b/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreenViewModel.kt index f3e68d22..5da94dc7 100644 --- a/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreenViewModel.kt +++ b/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreenViewModel.kt @@ -1,24 +1,31 @@ package com.infinitepower.newquiz.comparison_quiz.list import androidx.lifecycle.ViewModel -import com.infinitepower.newquiz.domain.repository.comparison_quiz.ComparisonQuizRepository +import androidx.lifecycle.viewModelScope +import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import javax.inject.Inject @HiltViewModel class ComparisonQuizListScreenViewModel @Inject constructor( - private val comparisonQuizRepository: ComparisonQuizRepository + private val recentCategoriesRepository: RecentCategoriesRepository ) : ViewModel() { private val _uiState = MutableStateFlow(ComparisonQuizListScreenUiState()) val uiState = _uiState.asStateFlow() init { - _uiState.update { currentState -> - currentState.copy(categories = comparisonQuizRepository.getCategories()) - } + recentCategoriesRepository + .getComparisonCategories() + .onEach { homeCategories -> + _uiState.update { currentState -> + currentState.copy(homeCategories = homeCategories) + } + }.launchIn(viewModelScope) } fun onEvent(event: ComparisonQuizListScreenUiEvent) { diff --git a/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/ui/ComparisonQuizScreen.kt b/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/ui/ComparisonQuizScreen.kt index 973b1881..e1481325 100644 --- a/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/ui/ComparisonQuizScreen.kt +++ b/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/ui/ComparisonQuizScreen.kt @@ -49,16 +49,18 @@ import com.infinitepower.newquiz.core.ui.components.skip_question.SkipIconButton import com.infinitepower.newquiz.core.ui.components.skip_question.SkipQuestionDialog import com.infinitepower.newquiz.model.comparison_quiz.ComparisonMode import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategory +import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategoryEntity import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCurrentQuestion import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizFormatType import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizItem +import com.infinitepower.newquiz.model.toUiText import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.infinitepower.newquiz.core.R as CoreR @Keep data class ComparisonQuizListScreenNavArg( - val category: ComparisonQuizCategory, + val category: ComparisonQuizCategoryEntity, val comparisonMode: ComparisonMode = ComparisonMode.GREATER ) @@ -83,7 +85,7 @@ fun ComparisonQuizScreen( navigator.navigate( ComparisonQuizScreenDestination( - category = category, + category = category.toEntity(), comparisonMode = comparisonMode ) ) { @@ -423,9 +425,9 @@ private fun ComparisonQuizScreenPreview() { ), gameDescription = "Which one is more popular?", gameCategory = ComparisonQuizCategory( - title = "Social", + name = "Social".toUiText(), description = "Social media", - imageUrl = "", + image = "", id = "social", questionDescription = ComparisonQuizCategory.QuestionDescription( greater = "Which one is more popular?", diff --git a/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/ui/ComparisonQuizViewModel.kt b/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/ui/ComparisonQuizViewModel.kt index 33c1c514..ac8398bb 100644 --- a/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/ui/ComparisonQuizViewModel.kt +++ b/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/ui/ComparisonQuizViewModel.kt @@ -7,9 +7,11 @@ import androidx.work.WorkManager import com.infinitepower.newquiz.core.game.ComparisonQuizCore import com.infinitepower.newquiz.data.worker.UpdateGlobalEventDataWorker import com.infinitepower.newquiz.domain.repository.comparison_quiz.ComparisonQuizRepository +import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import com.infinitepower.newquiz.domain.repository.user.auth.AuthUserRepository import com.infinitepower.newquiz.model.comparison_quiz.ComparisonMode import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategory +import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategoryEntity import com.infinitepower.newquiz.model.global_event.GameEvent import com.infinitepower.newquiz.online_services.domain.user.UserRepository import dagger.hilt.android.lifecycle.HiltViewModel @@ -29,7 +31,8 @@ class ComparisonQuizViewModel @Inject constructor( private val comparisonQuizRepository: ComparisonQuizRepository, private val workManager: WorkManager, private val authUserRepository: AuthUserRepository, - private val userRepository: UserRepository + private val userRepository: UserRepository, + private val recentCategoriesRepository: RecentCategoriesRepository ) : ViewModel() { private val _uiState = MutableStateFlow(ComparisonQuizUiState()) val uiState = _uiState.asStateFlow() @@ -92,6 +95,8 @@ class ComparisonQuizViewModel @Inject constructor( ) launch { + recentCategoriesRepository.addComparisonCategory(category.id) + UpdateGlobalEventDataWorker.enqueueWork( workManager = workManager, GameEvent.ComparisonQuiz.PlayWithComparisonMode(comparisonMode), @@ -124,9 +129,11 @@ class ComparisonQuizViewModel @Inject constructor( } private fun getCategory(): ComparisonQuizCategory { - return savedStateHandle - .get(ComparisonQuizListScreenNavArg::category.name) + val categoryEntity = savedStateHandle + .get(ComparisonQuizListScreenNavArg::category.name) ?: throw IllegalArgumentException("Category is null") + + return categoryEntity.toModel() } private fun getComparisonMode(): ComparisonMode { diff --git a/data/src/main/java/com/infinitepower/newquiz/data/repository/comparison_quiz/ComparisonQuizRepositoryImpl.kt b/data/src/main/java/com/infinitepower/newquiz/data/repository/comparison_quiz/ComparisonQuizRepositoryImpl.kt index 29785804..e4c8ba24 100644 --- a/data/src/main/java/com/infinitepower/newquiz/data/repository/comparison_quiz/ComparisonQuizRepositoryImpl.kt +++ b/data/src/main/java/com/infinitepower/newquiz/data/repository/comparison_quiz/ComparisonQuizRepositoryImpl.kt @@ -8,6 +8,7 @@ import com.infinitepower.newquiz.core.dataStore.manager.DataStoreManager import com.infinitepower.newquiz.core.di.ComparisonQuizDataStoreManager import com.infinitepower.newquiz.domain.repository.comparison_quiz.ComparisonQuizRepository import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategory +import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategoryEntity import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizItem import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizItemEntity import com.infinitepower.newquiz.model.comparison_quiz.toComparisonQuizItem @@ -36,8 +37,10 @@ class ComparisonQuizRepositoryImpl @Inject constructor( override fun getCategories(): List { if (categoriesCache.isEmpty()) { val categoriesStr = remoteConfigApi.getString("comparison_quiz_categories") + val categoriesEntity: List = Json.decodeFromString(categoriesStr) + val categories = categoriesEntity.map(ComparisonQuizCategoryEntity::toModel) - categoriesCache.addAll(Json.decodeFromString(categoriesStr)) + categoriesCache.addAll(categories) } return categoriesCache diff --git a/data/src/main/java/com/infinitepower/newquiz/data/repository/daily_challenge/util/DailyChallengeTypeTitleUtil.kt b/data/src/main/java/com/infinitepower/newquiz/data/repository/daily_challenge/util/DailyChallengeTypeTitleUtil.kt index 5455a8ef..c000348e 100644 --- a/data/src/main/java/com/infinitepower/newquiz/data/repository/daily_challenge/util/DailyChallengeTypeTitleUtil.kt +++ b/data/src/main/java/com/infinitepower/newquiz/data/repository/daily_challenge/util/DailyChallengeTypeTitleUtil.kt @@ -64,7 +64,9 @@ fun GameEvent.getTitle( // Comparison quiz is GameEvent.ComparisonQuiz.PlayQuizWithCategory -> { - val categoryName = comparisonQuizCategories.find { it.id == this.categoryId }?.title.orEmpty() + val categoryName = comparisonQuizCategories + .find { it.id == this.categoryId } + ?.name ?: "" UiText.PluralStringResource( resId = CoreR.plurals.play_comparison_quiz_game_in_category, diff --git a/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt b/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt index 70340e32..5018f3d7 100644 --- a/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt +++ b/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt @@ -6,9 +6,12 @@ import com.infinitepower.newquiz.core.dataStore.manager.PreferenceRequest import com.infinitepower.newquiz.core.di.RecentCategoriesDataStoreManager import com.infinitepower.newquiz.data.local.multi_choice_quiz.category.multiChoiceQuestionCategories import com.infinitepower.newquiz.data.local.wordle.WordleCategories +import com.infinitepower.newquiz.domain.repository.comparison_quiz.ComparisonQuizRepository import com.infinitepower.newquiz.domain.repository.home.HomeCategories +import com.infinitepower.newquiz.domain.repository.home.HomeCategoriesFlow import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import com.infinitepower.newquiz.model.BaseCategory +import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategory import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceBaseCategory import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory import com.infinitepower.newquiz.model.wordle.WordleCategory @@ -19,20 +22,27 @@ import javax.inject.Singleton @Singleton class RecentCategoriesRepositoryImpl @Inject constructor( - @RecentCategoriesDataStoreManager private val recentCategoriesDataStoreManager: DataStoreManager + @RecentCategoriesDataStoreManager private val recentCategoriesDataStoreManager: DataStoreManager, + private val comparisonQuizRepository: ComparisonQuizRepository ) : RecentCategoriesRepository { - override fun getMultiChoiceCategories(): Flow> = getHomeCategories( + override fun getMultiChoiceCategories(): HomeCategoriesFlow = getHomeCategories( allCategories = multiChoiceQuestionCategories, request = RecentCategoryDataStoreCommon.MultiChoice, isInternetAvailable = true ) - override fun getWordleCategories(): Flow> = getHomeCategories( + override fun getWordleCategories(): HomeCategoriesFlow = getHomeCategories( allCategories = WordleCategories.allCategories, request = RecentCategoryDataStoreCommon.Wordle, isInternetAvailable = true ) + override fun getComparisonCategories(): HomeCategoriesFlow = getHomeCategories( + allCategories = comparisonQuizRepository.getCategories(), + request = RecentCategoryDataStoreCommon.ComparisonQuiz, + isInternetAvailable = true + ) + private fun getHomeCategories( allCategories: List, request: PreferenceRequest>, @@ -104,15 +114,23 @@ class RecentCategoriesRepositoryImpl @Inject constructor( addCategory(categoryId, RecentCategoryDataStoreCommon.Wordle) } - override suspend fun cleanMultiChoiceCategories() { + override suspend fun addComparisonCategory(categoryId: String) { + addCategory(categoryId, RecentCategoryDataStoreCommon.ComparisonQuiz) + } + + override suspend fun cleanAllSavedCategories() { recentCategoriesDataStoreManager.editPreference( key = RecentCategoryDataStoreCommon.MultiChoice.key, newValue = emptySet() ) - } - - override suspend fun cleanAll() { - cleanMultiChoiceCategories() + recentCategoriesDataStoreManager.editPreference( + key = RecentCategoryDataStoreCommon.Wordle.key, + newValue = emptySet() + ) + recentCategoriesDataStoreManager.editPreference( + key = RecentCategoryDataStoreCommon.ComparisonQuiz.key, + newValue = emptySet() + ) } private suspend fun addCategory( diff --git a/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt b/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt index cf7868f1..69e64cd2 100644 --- a/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt +++ b/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt @@ -1,6 +1,7 @@ package com.infinitepower.newquiz.domain.repository.home import androidx.annotation.Keep +import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategory import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceBaseCategory import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory import com.infinitepower.newquiz.model.wordle.WordleCategory @@ -17,16 +18,20 @@ fun emptyHomeCategories() = HomeCategories( otherCategories = emptyList() ) +typealias HomeCategoriesFlow = Flow> + interface RecentCategoriesRepository { - fun getMultiChoiceCategories(): Flow> + fun getMultiChoiceCategories(): HomeCategoriesFlow + + fun getWordleCategories(): HomeCategoriesFlow - fun getWordleCategories(): Flow> + fun getComparisonCategories(): HomeCategoriesFlow suspend fun addMultiChoiceCategory(category: MultiChoiceBaseCategory) suspend fun addWordleCategory(categoryId: String) - suspend fun cleanMultiChoiceCategories() + suspend fun addComparisonCategory(categoryId: String) - suspend fun cleanAll() + suspend fun cleanAllSavedCategories() } \ No newline at end of file diff --git a/model/src/main/java/com/infinitepower/newquiz/model/comparison_quiz/ComparisonQuizCategory.kt b/model/src/main/java/com/infinitepower/newquiz/model/comparison_quiz/ComparisonQuizCategory.kt index e98504e2..3bae2f4a 100644 --- a/model/src/main/java/com/infinitepower/newquiz/model/comparison_quiz/ComparisonQuizCategory.kt +++ b/model/src/main/java/com/infinitepower/newquiz/model/comparison_quiz/ComparisonQuizCategory.kt @@ -1,6 +1,8 @@ package com.infinitepower.newquiz.model.comparison_quiz import androidx.annotation.Keep +import com.infinitepower.newquiz.model.BaseCategory +import com.infinitepower.newquiz.model.UiText import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -15,23 +17,24 @@ import java.util.Locale /** * A category of comparison quizzes. * @param id The id of the category. - * @param title The title of the category. - * @param imageUrl The url of the image of the category. + * @param name The title of the category. + * @param image The url of the image of the category. * @param questionDescription The description of the question. * @param helperValueSuffix The suffix of the question value. */ @Keep @Serializable data class ComparisonQuizCategory( - val id: String, - val title: String, + override val id: String, + override val name: UiText, + override val image: String, + override val requireInternetConnection: Boolean = true, val description: String, - val imageUrl: String, val questionDescription: QuestionDescription, val formatType: ComparisonQuizFormatType, val helperValueSuffix: String? = null, val dataSourceAttribution: DataSourceAttribution? = null -) : java.io.Serializable { +) : BaseCategory, java.io.Serializable { fun formatValueToString(value: Double): String { return formatType.formatValueToString(value, helperValueSuffix) } @@ -59,6 +62,18 @@ data class ComparisonQuizCategory( val text: String, val logo: String? = null ) : java.io.Serializable + + fun toEntity(): ComparisonQuizCategoryEntity = ComparisonQuizCategoryEntity( + id = id, + name = name.toString(), + image = image, + requireInternetConnection = requireInternetConnection, + description = description, + questionDescription = questionDescription, + formatType = formatType, + helperValueSuffix = helperValueSuffix, + dataSourceAttribution = dataSourceAttribution + ) } @Serializable(with = ComparisonQuizFormatTypeSerializer::class) diff --git a/model/src/main/java/com/infinitepower/newquiz/model/comparison_quiz/ComparisonQuizCategoryEntity.kt b/model/src/main/java/com/infinitepower/newquiz/model/comparison_quiz/ComparisonQuizCategoryEntity.kt new file mode 100644 index 00000000..f8ad96be --- /dev/null +++ b/model/src/main/java/com/infinitepower/newquiz/model/comparison_quiz/ComparisonQuizCategoryEntity.kt @@ -0,0 +1,31 @@ +package com.infinitepower.newquiz.model.comparison_quiz + +import androidx.annotation.Keep +import com.infinitepower.newquiz.model.toUiText +import kotlinx.serialization.Serializable + +@Keep +@Serializable +data class ComparisonQuizCategoryEntity( + val id: String, + val name: String, + val image: String, + val requireInternetConnection: Boolean = true, + val description: String, + val questionDescription: ComparisonQuizCategory.QuestionDescription, + val formatType: ComparisonQuizFormatType, + val helperValueSuffix: String? = null, + val dataSourceAttribution: ComparisonQuizCategory.DataSourceAttribution? = null +) : java.io.Serializable { + fun toModel(): ComparisonQuizCategory = ComparisonQuizCategory( + id = id, + name = name.toUiText(), + image = image, + requireInternetConnection = requireInternetConnection, + description = description, + questionDescription = questionDescription, + formatType = formatType, + helperValueSuffix = helperValueSuffix, + dataSourceAttribution = dataSourceAttribution + ) +} diff --git a/model/src/test/java/com/infinitepower/newquiz/model/comparison_quiz/ComparisonQuizCategoryTest.kt b/model/src/test/java/com/infinitepower/newquiz/model/comparison_quiz/ComparisonQuizCategoryTest.kt index 774e384e..0e646620 100644 --- a/model/src/test/java/com/infinitepower/newquiz/model/comparison_quiz/ComparisonQuizCategoryTest.kt +++ b/model/src/test/java/com/infinitepower/newquiz/model/comparison_quiz/ComparisonQuizCategoryTest.kt @@ -1,6 +1,7 @@ package com.infinitepower.newquiz.model.comparison_quiz import com.google.common.truth.Truth.assertThat +import com.infinitepower.newquiz.model.toUiText import org.junit.jupiter.api.Test internal class ComparisonQuizCategoryTest { @@ -13,9 +14,9 @@ internal class ComparisonQuizCategoryTest { val category = ComparisonQuizCategory( id = "1", - title = "Category Title", + name = "Category Title".toUiText(), description = "Category Description", - imageUrl = "https://example.com/image.png", + image = "https://example.com/image.png", questionDescription = questionDescription, formatType = ComparisonQuizFormatType.Number, helperValueSuffix = null, @@ -35,9 +36,9 @@ internal class ComparisonQuizCategoryTest { val category = ComparisonQuizCategory( id = "1", - title = "Category Title", + name = "Category Title".toUiText(), description = "Category Description", - imageUrl = "https://example.com/image.png", + image = "https://example.com/image.png", questionDescription = questionDescription, formatType = ComparisonQuizFormatType.Number, helperValueSuffix = null, diff --git a/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/SettingsScreenUiEvent.kt b/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/SettingsScreenUiEvent.kt index 31c451c3..c350e71c 100644 --- a/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/SettingsScreenUiEvent.kt +++ b/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/SettingsScreenUiEvent.kt @@ -8,9 +8,6 @@ sealed interface SettingsScreenUiEvent { object SignOut : SettingsScreenUiEvent data class EnableLoggingAnalytics(val enabled: Boolean) : SettingsScreenUiEvent - data class EnableGeneralAnalytics(val enabled: Boolean) : SettingsScreenUiEvent - data class EnableCrashlytics(val enabled: Boolean) : SettingsScreenUiEvent - data class EnablePerformanceMonitoring(val enabled: Boolean) : SettingsScreenUiEvent - object ClearMultiChoiceQuizRecentCategories : SettingsScreenUiEvent + object ClearHomeRecentCategories : SettingsScreenUiEvent } diff --git a/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/SettingsViewModel.kt b/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/SettingsViewModel.kt index 45cf9ca8..e32749d8 100644 --- a/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/SettingsViewModel.kt +++ b/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/SettingsViewModel.kt @@ -73,10 +73,7 @@ class SettingsViewModel @Inject constructor( is SettingsScreenUiEvent.DownloadTranslationModel -> downloadTranslationModel() is SettingsScreenUiEvent.SignOut -> authUserRepository.signOut() is SettingsScreenUiEvent.EnableLoggingAnalytics -> enableLoggingAnalytics(event.enabled) - is SettingsScreenUiEvent.EnableGeneralAnalytics -> {} - is SettingsScreenUiEvent.EnableCrashlytics -> {} - is SettingsScreenUiEvent.EnablePerformanceMonitoring -> {} - is SettingsScreenUiEvent.ClearMultiChoiceQuizRecentCategories -> cleanMultiChoiceRecentCategoriesItems() + is SettingsScreenUiEvent.ClearHomeRecentCategories -> clearHomeRecentCategories() } } @@ -90,8 +87,8 @@ class SettingsViewModel @Inject constructor( } } - private fun cleanMultiChoiceRecentCategoriesItems() = viewModelScope.launch(Dispatchers.IO) { - recentCategoriesRepository.cleanMultiChoiceCategories() + private fun clearHomeRecentCategories() = viewModelScope.launch(Dispatchers.IO) { + recentCategoriesRepository.cleanAllSavedCategories() } private fun enableLoggingAnalytics(enabled: Boolean) = viewModelScope.launch(Dispatchers.IO) { diff --git a/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/components/PreferencesScreen.kt b/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/components/PreferencesScreen.kt index 1b5e30af..529d564f 100644 --- a/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/components/PreferencesScreen.kt +++ b/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/components/PreferencesScreen.kt @@ -71,13 +71,13 @@ internal fun PreferencesScreen( is SettingsScreenPageData.General -> page.items( scope = scope, dataStoreManager = dataStoreManager, - enableLoggingAnalytics = { onEvent(SettingsScreenUiEvent.EnableLoggingAnalytics(it)) } + enableLoggingAnalytics = { onEvent(SettingsScreenUiEvent.EnableLoggingAnalytics(it)) }, + cleanRecentCategories = { onEvent(SettingsScreenUiEvent.ClearHomeRecentCategories) } ) is SettingsScreenPageData.MultiChoiceQuiz -> page.items( translationModelState = uiState.translationModelState, downloadTranslationModel = { onEvent(SettingsScreenUiEvent.DownloadTranslationModel) }, - deleteTranslationModel = { onEvent(SettingsScreenUiEvent.DeleteTranslationModel) }, - cleanRecentCategories = { onEvent(SettingsScreenUiEvent.ClearMultiChoiceQuizRecentCategories) } + deleteTranslationModel = { onEvent(SettingsScreenUiEvent.DeleteTranslationModel) } ) is SettingsScreenPageData.Wordle -> page.items( onChangeWordleLang = coreLoggingAnalytics::setWordleLangUserProperty diff --git a/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/data/SettingsScreenPageData.kt b/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/data/SettingsScreenPageData.kt index be85c322..cf451743 100644 --- a/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/data/SettingsScreenPageData.kt +++ b/settings-presentation/src/main/java/com/infinitepower/newquiz/settings_presentation/data/SettingsScreenPageData.kt @@ -127,7 +127,8 @@ sealed class SettingsScreenPageData(val key: ScreenKey) { fun items( scope: CoroutineScope, dataStoreManager: DataStoreManager, - enableLoggingAnalytics: (enabled: Boolean) -> Unit + enableLoggingAnalytics: (enabled: Boolean) -> Unit, + cleanRecentCategories: () -> Unit ) = listOf( Preference.PreferenceItem.SwitchPreference( request = SettingsCommon.ShowLoginCard, @@ -155,6 +156,17 @@ sealed class SettingsScreenPageData(val key: ScreenKey) { scope.launch(Dispatchers.IO) { dataStoreManager.clearPreferences() } } ), + Preference.PreferenceItem.TextPreference( + title = stringResource(id = CoreR.string.clear_recent_categories), + summary = stringResource(id = CoreR.string.clear_recent_categories_description), + onClick = cleanRecentCategories, + icon = { + Icon( + imageVector = Icons.Rounded.ClearAll, + contentDescription = stringResource(id = CoreR.string.clear_recent_categories), + ) + } + ), Preference.PreferenceGroup( title = stringResource(id = CoreR.string.animations), preferenceItems = listOf( @@ -246,8 +258,7 @@ sealed class SettingsScreenPageData(val key: ScreenKey) { fun items( translationModelState: TranslatorModelState, downloadTranslationModel: () -> Unit, - deleteTranslationModel: () -> Unit, - cleanRecentCategories: () -> Unit + deleteTranslationModel: () -> Unit ) = listOf( Preference.PreferenceItem.SeekBarPreference( request = SettingsCommon.MultiChoiceQuizQuestionsSize, @@ -261,17 +272,6 @@ sealed class SettingsScreenPageData(val key: ScreenKey) { }, valueRange = (5..20) ), - Preference.PreferenceItem.TextPreference( - title = stringResource(id = CoreR.string.clear_recent_categories), - summary = stringResource(id = CoreR.string.clear_recent_categories_description), - onClick = cleanRecentCategories, - icon = { - Icon( - imageVector = Icons.Rounded.ClearAll, - contentDescription = stringResource(id = CoreR.string.clear_recent_categories), - ) - } - ), Preference.PreferenceGroup( title = stringResource(id = CoreR.string.translation), preferenceItems = listOf( diff --git a/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreen.kt b/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreen.kt index e7194eab..97f2c07c 100644 --- a/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreen.kt +++ b/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreen.kt @@ -1,9 +1,5 @@ package com.infinitepower.newquiz.wordle.list -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.QuestionMark import androidx.compose.material3.ExperimentalMaterial3Api @@ -21,8 +17,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.infinitepower.newquiz.core.common.annotation.compose.AllPreviewsNightLight import com.infinitepower.newquiz.core.theme.NewQuizTheme -import com.infinitepower.newquiz.core.theme.spacing import com.infinitepower.newquiz.core.ui.components.rememberIsInternetAvailable +import com.infinitepower.newquiz.core.ui.home.HomeLazyColumn import com.infinitepower.newquiz.core.ui.home.homeCategoriesItems import com.infinitepower.newquiz.core.ui.home_card.components.HomeGroupTitle import com.infinitepower.newquiz.core.ui.home_card.components.HomeLargeCard @@ -58,21 +54,11 @@ private fun WordleListScreenImpl( uiState: WordleListUiState, navigateToWordleQuiz: (wordleQuizType: WordleQuizType) -> Unit ) { - val spaceMedium = MaterialTheme.spacing.medium - val isInternetAvailable = rememberIsInternetAvailable() var seeAllCategories by remember { mutableStateOf(false) } - LazyColumn( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(spaceMedium), - contentPadding = PaddingValues( - start = spaceMedium, - end = spaceMedium, - bottom = MaterialTheme.spacing.large, - ) - ) { + HomeLazyColumn { item { HomeGroupTitle(title = stringResource(id = CoreR.string.wordle_infinite)) } From 27f6b0dd34d6f81f9355e8f6e4aad2d349e96ecf Mon Sep 17 00:00:00 2001 From: joaomanaia Date: Fri, 16 Jun 2023 18:19:56 +0100 Subject: [PATCH 4/4] Made tests for RecentCategoriesRepositoryImpl, moved NetworkStatusTracker.kt to core module --- .../ui/ComparisonQuizScreenTest.kt | 12 +- .../list/ComparisonQuizListScreenViewModel.kt | 25 +- .../core/ComparisonQuizCoreImplTest.kt | 5 +- .../di/{CoreModule.kt => AnalyticsModule.kt} | 2 +- .../newquiz/core/di/NetworkStatusModule.kt | 17 + .../newquiz/core/network/NetworkStatus.kt | 11 + .../core/network/NetworkStatusTracker.kt | 9 + .../core/network/NetworkStatusTrackerImpl.kt | 23 +- .../data/FakeComparisonQuizRepositoryImpl.kt | 5 +- .../home/RecentCategoriesRepositoryImpl.kt | 83 ++- .../RecentCategoriesRepositoryImplTest.kt | 596 ++++++++++++++++++ .../home/RecentCategoriesRepository.kt | 15 +- .../MultiChoiceQuizScreenViewModel.kt | 2 +- .../MultiChoiceQuizListScreenViewModel.kt | 25 +- .../core/OnlineServicesCore.kt | 9 - .../core/OnlineServicesCoreImpl.kt | 20 - .../core/network/NetworkStatus.kt | 7 - .../core/network/NetworkStatusTracker.kt | 7 - .../MultiChoiceQuizEndGameWorker.kt | 7 +- .../di/OnlineServicesModule.kt | 24 - .../wordle/list/WordleListScreenViewModel.kt | 25 +- .../wordle/util/worker/WordleEndGameWorker.kt | 6 +- 22 files changed, 793 insertions(+), 142 deletions(-) rename core/src/main/java/com/infinitepower/newquiz/core/di/{CoreModule.kt => AnalyticsModule.kt} (95%) create mode 100644 core/src/main/java/com/infinitepower/newquiz/core/di/NetworkStatusModule.kt create mode 100644 core/src/main/java/com/infinitepower/newquiz/core/network/NetworkStatus.kt create mode 100644 core/src/main/java/com/infinitepower/newquiz/core/network/NetworkStatusTracker.kt rename {online_services/src/main/java/com/infinitepower/newquiz/online_services => core/src/main/java/com/infinitepower/newquiz}/core/network/NetworkStatusTrackerImpl.kt (77%) create mode 100644 data/src/test/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImplTest.kt delete mode 100644 online_services/src/main/java/com/infinitepower/newquiz/online_services/core/OnlineServicesCore.kt delete mode 100644 online_services/src/main/java/com/infinitepower/newquiz/online_services/core/OnlineServicesCoreImpl.kt delete mode 100644 online_services/src/main/java/com/infinitepower/newquiz/online_services/core/network/NetworkStatus.kt delete mode 100644 online_services/src/main/java/com/infinitepower/newquiz/online_services/core/network/NetworkStatusTracker.kt delete mode 100644 online_services/src/main/java/com/infinitepower/newquiz/online_services/di/OnlineServicesModule.kt diff --git a/comparison-quiz/src/androidTest/java/com/infinitepower/newquiz/comparison_quiz/ui/ComparisonQuizScreenTest.kt b/comparison-quiz/src/androidTest/java/com/infinitepower/newquiz/comparison_quiz/ui/ComparisonQuizScreenTest.kt index b251c457..d3e55640 100644 --- a/comparison-quiz/src/androidTest/java/com/infinitepower/newquiz/comparison_quiz/ui/ComparisonQuizScreenTest.kt +++ b/comparison-quiz/src/androidTest/java/com/infinitepower/newquiz/comparison_quiz/ui/ComparisonQuizScreenTest.kt @@ -26,12 +26,14 @@ import com.infinitepower.newquiz.core.game.ComparisonQuizCore import com.infinitepower.newquiz.core_test.compose.theme.NewQuizTestTheme import com.infinitepower.newquiz.core_test.utils.setDeviceLocale import com.infinitepower.newquiz.domain.repository.comparison_quiz.ComparisonQuizRepository +import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import com.infinitepower.newquiz.domain.repository.user.auth.AuthUserRepository import com.infinitepower.newquiz.model.comparison_quiz.ComparisonMode import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategory import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizFormatType import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizItem import com.infinitepower.newquiz.model.config.RemoteConfigApi +import com.infinitepower.newquiz.model.toUiText import com.infinitepower.newquiz.online_services.domain.user.UserRepository import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator import io.mockk.every @@ -58,6 +60,7 @@ internal class ComparisonQuizScreenTest { private val authUserRepository = mockk(relaxed = true) private val userRepository = mockk(relaxed = true) private val remoteConfigApi = mockk(relaxed = true) + private val recentCategoriesRepository = mockk(relaxed = true) private lateinit var comparisonQuizCore: ComparisonQuizCore @@ -70,9 +73,9 @@ internal class ComparisonQuizScreenTest { private val category by lazy { ComparisonQuizCategory( id = "numbers", - title = "Numbers", + name = "Numbers".toUiText(), description = "Numbers description", - imageUrl = "", + image = "", questionDescription = ComparisonQuizCategory.QuestionDescription( greater = "Which number is greater?", less = "Which number is lesser?", @@ -132,13 +135,14 @@ internal class ComparisonQuizScreenTest { savedStateHandle = SavedStateHandle( mapOf( ComparisonQuizListScreenNavArg::comparisonMode.name to comparisonMode, - ComparisonQuizListScreenNavArg::category.name to category + ComparisonQuizListScreenNavArg::category.name to category.toEntity() ) ), comparisonQuizRepository = comparisonQuizRepository, workManager = workManager, authUserRepository = authUserRepository, - userRepository = userRepository + userRepository = userRepository, + recentCategoriesRepository = recentCategoriesRepository ) composeTestRule.setContent { diff --git a/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreenViewModel.kt b/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreenViewModel.kt index 5da94dc7..fb694e92 100644 --- a/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreenViewModel.kt +++ b/comparison-quiz/src/main/java/com/infinitepower/newquiz/comparison_quiz/list/ComparisonQuizListScreenViewModel.kt @@ -2,26 +2,43 @@ package com.infinitepower.newquiz.comparison_quiz.list import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.infinitepower.newquiz.core.network.NetworkStatus +import com.infinitepower.newquiz.core.network.NetworkStatusTracker import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import javax.inject.Inject @HiltViewModel +@OptIn(ExperimentalCoroutinesApi::class) class ComparisonQuizListScreenViewModel @Inject constructor( - private val recentCategoriesRepository: RecentCategoriesRepository + private val recentCategoriesRepository: RecentCategoriesRepository, + private val networkStatusTracker: NetworkStatusTracker ) : ViewModel() { private val _uiState = MutableStateFlow(ComparisonQuizListScreenUiState()) val uiState = _uiState.asStateFlow() init { - recentCategoriesRepository - .getComparisonCategories() - .onEach { homeCategories -> + val networkStatus = networkStatusTracker + .networkStatus + .stateIn( + initialValue = NetworkStatus.Unknown, + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000) + ) + + networkStatus + .flatMapLatest { status -> + recentCategoriesRepository.getComparisonCategories(isInternetAvailable = status.isAvailable()) + }.onEach { homeCategories -> _uiState.update { currentState -> currentState.copy(homeCategories = homeCategories) } diff --git a/comparison-quiz/src/test/java/com/infinitepower/newquiz/comparison_quiz/core/ComparisonQuizCoreImplTest.kt b/comparison-quiz/src/test/java/com/infinitepower/newquiz/comparison_quiz/core/ComparisonQuizCoreImplTest.kt index 5bf12880..3ce45488 100644 --- a/comparison-quiz/src/test/java/com/infinitepower/newquiz/comparison_quiz/core/ComparisonQuizCoreImplTest.kt +++ b/comparison-quiz/src/test/java/com/infinitepower/newquiz/comparison_quiz/core/ComparisonQuizCoreImplTest.kt @@ -11,6 +11,7 @@ import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCurrentQues import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizFormatType import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizItem import com.infinitepower.newquiz.model.config.RemoteConfigApi +import com.infinitepower.newquiz.model.toUiText import com.infinitepower.newquiz.online_services.domain.user.UserRepository import io.mockk.coEvery import io.mockk.coVerify @@ -448,9 +449,9 @@ internal class ComparisonQuizCoreImplTest { ) = ComparisonQuizCore.InitializationData( category = ComparisonQuizCategory( id = "id", - title = "title", + name = "title".toUiText(), description = "description", - imageUrl = "imageUrl", + image = "imageUrl", questionDescription = ComparisonQuizCategory.QuestionDescription( greater = "greater", less = "less" diff --git a/core/src/main/java/com/infinitepower/newquiz/core/di/CoreModule.kt b/core/src/main/java/com/infinitepower/newquiz/core/di/AnalyticsModule.kt similarity index 95% rename from core/src/main/java/com/infinitepower/newquiz/core/di/CoreModule.kt rename to core/src/main/java/com/infinitepower/newquiz/core/di/AnalyticsModule.kt index 6df71d5d..4b58599f 100644 --- a/core/src/main/java/com/infinitepower/newquiz/core/di/CoreModule.kt +++ b/core/src/main/java/com/infinitepower/newquiz/core/di/AnalyticsModule.kt @@ -11,7 +11,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object CoreModule { +object AnalyticsModule { @Provides @Singleton fun provideFirebaseAnalytics(): FirebaseAnalytics = Firebase.analytics diff --git a/core/src/main/java/com/infinitepower/newquiz/core/di/NetworkStatusModule.kt b/core/src/main/java/com/infinitepower/newquiz/core/di/NetworkStatusModule.kt new file mode 100644 index 00000000..1e7cb7a3 --- /dev/null +++ b/core/src/main/java/com/infinitepower/newquiz/core/di/NetworkStatusModule.kt @@ -0,0 +1,17 @@ +package com.infinitepower.newquiz.core.di + +import com.infinitepower.newquiz.core.network.NetworkStatusTracker +import com.infinitepower.newquiz.core.network.NetworkStatusTrackerImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +abstract class NetworkStatusModule { + @Binds + abstract fun bindNetworkStatusTracker( + networkStatusTrackerImpl: NetworkStatusTrackerImpl + ): NetworkStatusTracker +} \ No newline at end of file diff --git a/core/src/main/java/com/infinitepower/newquiz/core/network/NetworkStatus.kt b/core/src/main/java/com/infinitepower/newquiz/core/network/NetworkStatus.kt new file mode 100644 index 00000000..4d3cbea6 --- /dev/null +++ b/core/src/main/java/com/infinitepower/newquiz/core/network/NetworkStatus.kt @@ -0,0 +1,11 @@ +package com.infinitepower.newquiz.core.network + +sealed class NetworkStatus { + object Available : NetworkStatus() + + object Unavailable : NetworkStatus() + + object Unknown : NetworkStatus() + + fun isAvailable() = this is Available +} diff --git a/core/src/main/java/com/infinitepower/newquiz/core/network/NetworkStatusTracker.kt b/core/src/main/java/com/infinitepower/newquiz/core/network/NetworkStatusTracker.kt new file mode 100644 index 00000000..5f63d9a7 --- /dev/null +++ b/core/src/main/java/com/infinitepower/newquiz/core/network/NetworkStatusTracker.kt @@ -0,0 +1,9 @@ +package com.infinitepower.newquiz.core.network + +import kotlinx.coroutines.flow.Flow + +interface NetworkStatusTracker { + val networkStatus: Flow + + suspend fun connectionAvailable(): Boolean +} \ No newline at end of file diff --git a/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/network/NetworkStatusTrackerImpl.kt b/core/src/main/java/com/infinitepower/newquiz/core/network/NetworkStatusTrackerImpl.kt similarity index 77% rename from online_services/src/main/java/com/infinitepower/newquiz/online_services/core/network/NetworkStatusTrackerImpl.kt rename to core/src/main/java/com/infinitepower/newquiz/core/network/NetworkStatusTrackerImpl.kt index c9f4c210..2d74316c 100644 --- a/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/network/NetworkStatusTrackerImpl.kt +++ b/core/src/main/java/com/infinitepower/newquiz/core/network/NetworkStatusTrackerImpl.kt @@ -1,4 +1,4 @@ -package com.infinitepower.newquiz.online_services.core.network +package com.infinitepower.newquiz.core.network import android.content.Context import android.net.ConnectivityManager @@ -9,6 +9,8 @@ import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import javax.inject.Inject import javax.inject.Singleton @@ -21,16 +23,16 @@ class NetworkStatusTrackerImpl @Inject constructor( context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager } - override val netWorkStatus = callbackFlow { + override val networkStatus = callbackFlow { val netWorkStatusCallback = object : ConnectivityManager.NetworkCallback() { - override fun onUnavailable() { - trySend(NetworkStatus.Unavailable) - } - override fun onAvailable(network: Network) { trySend(NetworkStatus.Available) } + override fun onUnavailable() { + trySend(NetworkStatus.Unavailable) + } + override fun onLost(network: Network) { trySend(NetworkStatus.Unavailable) } @@ -39,21 +41,28 @@ class NetworkStatusTrackerImpl @Inject constructor( val request = NetworkRequest .Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .build() + connectivityManager.registerNetworkCallback(request, netWorkStatusCallback) awaitClose { connectivityManager.unregisterNetworkCallback(netWorkStatusCallback) } - } + }.distinctUntilChanged() + + override suspend fun connectionAvailable() = networkStatus.first().isAvailable() } internal inline fun Flow.map( crossinline onUnavailable: suspend () -> Result, crossinline onAvailable: suspend () -> Result, + crossinline onUnknown: suspend () -> Result ): Flow = map { status -> when (status) { NetworkStatus.Unavailable -> onUnavailable() NetworkStatus.Available -> onAvailable() + NetworkStatus.Unknown -> onUnknown() } } \ No newline at end of file diff --git a/data/src/androidTest/java/com/infinitepower/newquiz/data/FakeComparisonQuizRepositoryImpl.kt b/data/src/androidTest/java/com/infinitepower/newquiz/data/FakeComparisonQuizRepositoryImpl.kt index cef1a3e3..8616a24c 100644 --- a/data/src/androidTest/java/com/infinitepower/newquiz/data/FakeComparisonQuizRepositoryImpl.kt +++ b/data/src/androidTest/java/com/infinitepower/newquiz/data/FakeComparisonQuizRepositoryImpl.kt @@ -5,6 +5,7 @@ import com.infinitepower.newquiz.domain.repository.comparison_quiz.ComparisonQui import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategory import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizFormatType import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizItem +import com.infinitepower.newquiz.model.toUiText import kotlinx.coroutines.flow.emptyFlow import javax.inject.Inject import javax.inject.Singleton @@ -15,9 +16,9 @@ class FakeComparisonQuizRepositoryImpl @Inject constructor() : ComparisonQuizRep return listOf( ComparisonQuizCategory( id = "1", - title = "Category 1", + name = "Category 1".toUiText(), description = "Description 1", - imageUrl = "", + image = "", questionDescription = ComparisonQuizCategory.QuestionDescription( greater = "Greater", less = "Less" diff --git a/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt b/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt index 5018f3d7..c9af4efc 100644 --- a/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt +++ b/data/src/main/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImpl.kt @@ -12,7 +12,6 @@ import com.infinitepower.newquiz.domain.repository.home.HomeCategoriesFlow import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import com.infinitepower.newquiz.model.BaseCategory import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategory -import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceBaseCategory import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory import com.infinitepower.newquiz.model.wordle.WordleCategory import kotlinx.coroutines.flow.Flow @@ -25,37 +24,51 @@ class RecentCategoriesRepositoryImpl @Inject constructor( @RecentCategoriesDataStoreManager private val recentCategoriesDataStoreManager: DataStoreManager, private val comparisonQuizRepository: ComparisonQuizRepository ) : RecentCategoriesRepository { - override fun getMultiChoiceCategories(): HomeCategoriesFlow = getHomeCategories( + override fun getMultiChoiceCategories( + isInternetAvailable: Boolean + ): HomeCategoriesFlow = getHomeCategories( allCategories = multiChoiceQuestionCategories, request = RecentCategoryDataStoreCommon.MultiChoice, - isInternetAvailable = true + isInternetAvailable = isInternetAvailable ) - override fun getWordleCategories(): HomeCategoriesFlow = getHomeCategories( + override fun getWordleCategories( + isInternetAvailable: Boolean + ): HomeCategoriesFlow = getHomeCategories( allCategories = WordleCategories.allCategories, request = RecentCategoryDataStoreCommon.Wordle, - isInternetAvailable = true + isInternetAvailable = isInternetAvailable ) - override fun getComparisonCategories(): HomeCategoriesFlow = getHomeCategories( + override fun getComparisonCategories( + isInternetAvailable: Boolean + ): HomeCategoriesFlow = getHomeCategories( allCategories = comparisonQuizRepository.getCategories(), request = RecentCategoryDataStoreCommon.ComparisonQuiz, - isInternetAvailable = true + isInternetAvailable = isInternetAvailable ) + fun getHomeCategories( + allCategories: List, + recentCategoriesFlow: Flow>, + isInternetAvailable: Boolean + ): Flow> = recentCategoriesFlow.map { recentCategoriesIds -> + getHomeBaseCategories( + savedRecentCategoriesIds = recentCategoriesIds, + allCategories = allCategories, + isInternetAvailable = isInternetAvailable + ) + } + private fun getHomeCategories( allCategories: List, request: PreferenceRequest>, isInternetAvailable: Boolean - ): Flow> = recentCategoriesDataStoreManager - .getPreferenceFlow(request) - .map { recentCategoriesIds -> - getHomeBaseCategories( - savedRecentCategoriesIds = recentCategoriesIds, - allCategories = allCategories, - isInternetAvailable = isInternetAvailable - ) - } + ): Flow> = getHomeCategories( + allCategories = allCategories, + recentCategoriesFlow = recentCategoriesDataStoreManager.getPreferenceFlow(request), + isInternetAvailable = isInternetAvailable + ) private fun getHomeBaseCategories( savedRecentCategoriesIds: Set, @@ -101,13 +114,15 @@ class RecentCategoriesRepositoryImpl @Inject constructor( allCategories // If there is no internet, we only show the categories that don't require internet connection .filter { !it.requireInternetConnection || isInternetAvailable } + // If there are no categories that don't require internet connection, we use all categories + .ifEmpty { allCategories } .shuffled() .take(3) } } - override suspend fun addMultiChoiceCategory(category: MultiChoiceBaseCategory) { - addCategory(category.id, RecentCategoryDataStoreCommon.MultiChoice) + override suspend fun addMultiChoiceCategory(categoryId: String) { + addCategory(categoryId, RecentCategoryDataStoreCommon.MultiChoice) } override suspend fun addWordleCategory(categoryId: String) { @@ -118,21 +133,6 @@ class RecentCategoriesRepositoryImpl @Inject constructor( addCategory(categoryId, RecentCategoryDataStoreCommon.ComparisonQuiz) } - override suspend fun cleanAllSavedCategories() { - recentCategoriesDataStoreManager.editPreference( - key = RecentCategoryDataStoreCommon.MultiChoice.key, - newValue = emptySet() - ) - recentCategoriesDataStoreManager.editPreference( - key = RecentCategoryDataStoreCommon.Wordle.key, - newValue = emptySet() - ) - recentCategoriesDataStoreManager.editPreference( - key = RecentCategoryDataStoreCommon.ComparisonQuiz.key, - newValue = emptySet() - ) - } - private suspend fun addCategory( id: String, preferenceRequest: PreferenceRequest> @@ -156,4 +156,19 @@ class RecentCategoriesRepositoryImpl @Inject constructor( newValue = newCategoriesIds ) } -} \ No newline at end of file + + override suspend fun cleanAllSavedCategories() { + recentCategoriesDataStoreManager.editPreference( + key = RecentCategoryDataStoreCommon.MultiChoice.key, + newValue = emptySet() + ) + recentCategoriesDataStoreManager.editPreference( + key = RecentCategoryDataStoreCommon.Wordle.key, + newValue = emptySet() + ) + recentCategoriesDataStoreManager.editPreference( + key = RecentCategoryDataStoreCommon.ComparisonQuiz.key, + newValue = emptySet() + ) + } +} diff --git a/data/src/test/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImplTest.kt b/data/src/test/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImplTest.kt new file mode 100644 index 00000000..61119d7b --- /dev/null +++ b/data/src/test/java/com/infinitepower/newquiz/data/repository/home/RecentCategoriesRepositoryImplTest.kt @@ -0,0 +1,596 @@ +package com.infinitepower.newquiz.data.repository.home + +import com.google.common.truth.Truth.assertThat +import com.infinitepower.newquiz.core.common.dataStore.RecentCategoryDataStoreCommon +import com.infinitepower.newquiz.core.dataStore.manager.DataStoreManager +import com.infinitepower.newquiz.core.dataStore.manager.PreferenceRequest +import com.infinitepower.newquiz.data.local.multi_choice_quiz.category.multiChoiceQuestionCategories +import com.infinitepower.newquiz.data.local.wordle.WordleCategories +import com.infinitepower.newquiz.domain.repository.comparison_quiz.ComparisonQuizRepository +import com.infinitepower.newquiz.model.BaseCategory +import com.infinitepower.newquiz.model.UiText +import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategory +import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizFormatType +import com.infinitepower.newquiz.model.toUiText +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coJustRun +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource + +/** + * Tests for [RecentCategoriesRepositoryImpl] + */ +internal class RecentCategoriesRepositoryImplTest { + private lateinit var recentCategoriesRepository: RecentCategoriesRepositoryImpl + + private val recentCategoriesDataStoreManager: DataStoreManager = mockk(relaxed = true) + private val comparisonQuizRepository: ComparisonQuizRepository = mockk(relaxed = true) + + @BeforeEach + fun setUp() { + // Create an instance of RecentCategoriesRepositoryImpl with mocked dependencies + recentCategoriesRepository = RecentCategoriesRepositoryImpl( + recentCategoriesDataStoreManager, + comparisonQuizRepository + ) + } + + @AfterEach + fun tearDown() { + // Clear any recorded calls and reset mocks after each test + clearAllMocks() + } + + private data class TestCategory( + override val id: String, + override val name: UiText, + override val image: String, + override val requireInternetConnection: Boolean + ) : BaseCategory + + private val testCategories = List(10) { + TestCategory( + id = "id$it", + name = "name$it".toUiText(), + image = "image$it", + requireInternetConnection = it % 2 == 0 + ) + } + + @Test + fun `getCategories should return expected result, when connection available and have recent categories`() = runTest { + // Mock the necessary dependencies and data + val recentCategories = listOf( + testCategories[0], + testCategories[1], + testCategories[2] + ) + + val recentCategoriesIds = recentCategories + .map { it.id } + .toSet() + + // Call the method under test + val result = recentCategoriesRepository.getHomeCategories( + allCategories = testCategories, + recentCategoriesFlow = flowOf(recentCategoriesIds), + isInternetAvailable = true + ).first() + + val otherCategories = testCategories.filterNot { it in recentCategories } + + // Verify the result + assertThat(result.recentCategories.size).isAtMost(3) + assertThat(result.recentCategories).containsExactlyElementsIn(recentCategories) + + assertThat(result.otherCategories).containsExactlyElementsIn(otherCategories) + } + + @Test + fun `getCategories should return expected result, when connection available and have no recent categories`() = runTest { + // Mock the necessary dependencies and data + val recentCategoriesIds = emptySet() + + // Call the method under test + val result = recentCategoriesRepository.getHomeCategories( + allCategories = testCategories, + recentCategoriesFlow = flowOf(recentCategoriesIds), + isInternetAvailable = true + ).first() + + // Verify the result + assertThat(result.recentCategories).isNotEmpty() + assertThat(result.recentCategories).hasSize(3) + + assertThat(result.otherCategories).hasSize(testCategories.size - 3) + assertThat(result.otherCategories).containsExactlyElementsIn(testCategories - result.recentCategories.toSet()) + } + + @Test + fun `getCategories should return expected result, when connection not available and have recent categories`() = runTest { + // Mock the necessary dependencies and data + val recentCategories = listOf( + testCategories[0], + testCategories[1], + testCategories[2] + ) + + val recentCategoriesIds = recentCategories + .map { it.id } + .toSet() + + // Call the method under test + val result = recentCategoriesRepository.getHomeCategories( + allCategories = testCategories, + recentCategoriesFlow = flowOf(recentCategoriesIds), + isInternetAvailable = false + ).first() + + val otherCategories = testCategories.filterNot { it in recentCategories } + + // Verify the result + assertThat(result.recentCategories.size).isAtMost(3) + assertThat(result.recentCategories).containsExactlyElementsIn(recentCategories) + + assertThat(result.otherCategories).containsExactlyElementsIn(otherCategories) + + // Check that the other categories with no internet connection are on the top of the list + val otherCategoriesSorted = otherCategories.sortedBy { it.requireInternetConnection } + assertThat(result.otherCategories).isEqualTo(otherCategoriesSorted) + } + + @Test + fun `getCategories should return expected result, when connection not available and have no recent categories`() = runTest { + // Mock the necessary dependencies and data + val recentCategoriesIds = emptySet() + + // Call the method under test + val result = recentCategoriesRepository.getHomeCategories( + allCategories = testCategories, + recentCategoriesFlow = flowOf(recentCategoriesIds), + isInternetAvailable = false + ).first() + + // Verify the result + assertThat(result.recentCategories).isNotEmpty() + + assertThat(result.recentCategories.size).isAtMost(3) + + assertThat(result.otherCategories).hasSize(testCategories.size - result.recentCategories.size) + assertThat(result.otherCategories).containsExactlyElementsIn(testCategories - result.recentCategories.toSet()) + } + + @Test + fun `getCategories should return expected result, when connection not available and have no recent categories and all categories require internet`() = runTest { + val testCategories = List(10) { + TestCategory( + id = "id$it", + name = "name$it".toUiText(), + image = "image$it", + requireInternetConnection = true + ) + } + + // Mock the necessary dependencies and data + val recentCategoriesIds = emptySet() + + // Call the method under test + val result = recentCategoriesRepository.getHomeCategories( + allCategories = testCategories, + recentCategoriesFlow = flowOf(recentCategoriesIds), + isInternetAvailable = false + ).first() + + // Verify the result + assertThat(result.recentCategories).isNotEmpty() + assertThat(result.recentCategories).hasSize(3) + + assertThat(result.otherCategories).hasSize(testCategories.size - result.recentCategories.size) + } + + @ParameterizedTest + @MethodSource("getMultiChoiceCategoriesParams") + fun `getMultiChoiceCategories should return expected result with parameters`( + isInternetAvailable: Boolean, + recentCategoriesIds: Set + ) = runTest { + every { recentCategoriesDataStoreManager.getPreferenceFlow>(any()) } returns flowOf(recentCategoriesIds) + + // Call the method under test + val result = recentCategoriesRepository.getMultiChoiceCategories( + isInternetAvailable = isInternetAvailable + ).first() + + // Verify the result + assertThat(result).isNotNull() + + assertThat(result.recentCategories).isNotEmpty() + assertThat(result.recentCategories.size).isAtMost(3) + + if (recentCategoriesIds.isNotEmpty()) { + val recentCategories = multiChoiceQuestionCategories.filter { it.id in recentCategoriesIds } + assertThat(result.recentCategories).containsExactlyElementsIn(recentCategories) + } + + assertThat(result.otherCategories).hasSize(multiChoiceQuestionCategories.size - result.recentCategories.size) + assertThat(result.otherCategories).containsExactlyElementsIn(multiChoiceQuestionCategories - result.recentCategories.toSet()) + } + + @ParameterizedTest + @MethodSource("getWordleCategoriesParams") + fun `getWordCategories should return expected result with parameters`( + isInternetAvailable: Boolean, + recentCategoriesIds: Set + ) = runTest { + every { recentCategoriesDataStoreManager.getPreferenceFlow>(any()) } returns flowOf(recentCategoriesIds) + + // Call the method under test + val result = recentCategoriesRepository.getWordleCategories( + isInternetAvailable = isInternetAvailable + ).first() + + // Verify the result + assertThat(result).isNotNull() + + assertThat(result.recentCategories).isNotEmpty() + assertThat(result.recentCategories.size).isAtMost(3) + + val allWordleCategories = WordleCategories.allCategories + + if (recentCategoriesIds.isNotEmpty()) { + val recentCategories = allWordleCategories.filter { it.id in recentCategoriesIds } + assertThat(result.recentCategories).containsExactlyElementsIn(recentCategories) + } + + assertThat(result.otherCategories).hasSize(allWordleCategories.size - result.recentCategories.size) + assertThat(result.otherCategories).containsExactlyElementsIn(allWordleCategories - result.recentCategories.toSet()) + } + + @ParameterizedTest + @MethodSource("getComparisonCategoriesParams") + fun `getComparisonCategories should return expected result with parameters`( + isInternetAvailable: Boolean, + recentCategoriesIds: Set + ) = runTest { + every { comparisonQuizRepository.getCategories() } returns allComparisonQuizCategories + every { recentCategoriesDataStoreManager.getPreferenceFlow>(any()) } returns flowOf(recentCategoriesIds) + + // Call the method under test + val result = recentCategoriesRepository.getComparisonCategories( + isInternetAvailable = isInternetAvailable + ).first() + + // Verify the result + assertThat(result).isNotNull() + + assertThat(result.recentCategories).isNotEmpty() + assertThat(result.recentCategories.size).isAtMost(3) + + if (recentCategoriesIds.isNotEmpty()) { + val recentCategories = allComparisonQuizCategories.filter { it.id in recentCategoriesIds } + assertThat(result.recentCategories).containsExactlyElementsIn(recentCategories) + } + + assertThat(result.otherCategories).hasSize(allComparisonQuizCategories.size - result.recentCategories.size) + assertThat(result.otherCategories).containsExactlyElementsIn(allComparisonQuizCategories - result.recentCategories.toSet()) + } + + // Tests for addMultiChoiceCategory + @ParameterizedTest + @MethodSource("addMultiChoiceCategoryParams") + fun `addMultiChoiceCategory should add category to recent categories`( + categoryIdToAdd: String, + initialCategories: Set + ) = runTest { + // Mock the necessary dependencies and data + coEvery { recentCategoriesDataStoreManager.getPreference>(any()) } returns initialCategories + coJustRun { recentCategoriesDataStoreManager.editPreference>(any(), any()) } + + // Act + recentCategoriesRepository.addMultiChoiceCategory(categoryIdToAdd) + + // Assert + val slot = slot>>() + coVerify { recentCategoriesDataStoreManager.getPreference(capture(slot)) } + assertThat(slot.captured.key).isEqualTo(RecentCategoryDataStoreCommon.MultiChoice.key) + + val addFunctionShouldBeCalled = categoryIdToAdd !in initialCategories + + val newValueSlot = slot>() + coVerify( + exactly = if (addFunctionShouldBeCalled) 1 else 0 + ) { recentCategoriesDataStoreManager.editPreference(any(), capture(newValueSlot)) } + + if (addFunctionShouldBeCalled) { + assertThat(newValueSlot.captured.size).isAtMost(3) + assertThat(newValueSlot.captured).contains(categoryIdToAdd) + } else { + assertThat(newValueSlot.isCaptured).isFalse() + } + } + + // Tests for addWordleCategory + @ParameterizedTest + @MethodSource("addWordleCategoryParams") + fun `addWordleCategory should add category to recent categories`( + categoryIdToAdd: String, + initialCategories: Set + ) = runTest { + // Mock the necessary dependencies and data + coEvery { recentCategoriesDataStoreManager.getPreference>(any()) } returns initialCategories + coJustRun { recentCategoriesDataStoreManager.editPreference>(any(), any()) } + + // Act + recentCategoriesRepository.addWordleCategory(categoryIdToAdd) + + // Assert + val slot = slot>>() + coVerify { recentCategoriesDataStoreManager.getPreference(capture(slot)) } + assertThat(slot.captured.key).isEqualTo(RecentCategoryDataStoreCommon.Wordle.key) + + val addFunctionShouldBeCalled = categoryIdToAdd !in initialCategories + + val newValueSlot = slot>() + coVerify( + exactly = if (addFunctionShouldBeCalled) 1 else 0 + ) { recentCategoriesDataStoreManager.editPreference(any(), capture(newValueSlot)) } + + if (addFunctionShouldBeCalled) { + assertThat(newValueSlot.captured.size).isAtMost(3) + assertThat(newValueSlot.captured).contains(categoryIdToAdd) + } else { + assertThat(newValueSlot.isCaptured).isFalse() + } + } + + // Tests for addComparisonCategory + @ParameterizedTest + @MethodSource("addComparisonCategoryParams") + fun `addComparisonCategory should add category to recent categories`( + categoryIdToAdd: String, + initialCategories: Set + ) = runTest { + // Mock the necessary dependencies and data + coEvery { recentCategoriesDataStoreManager.getPreference>(any()) } returns initialCategories + coJustRun { recentCategoriesDataStoreManager.editPreference>(any(), any()) } + + // Act + recentCategoriesRepository.addComparisonCategory(categoryIdToAdd) + + // Assert + val slot = slot>>() + coVerify { recentCategoriesDataStoreManager.getPreference(capture(slot)) } + assertThat(slot.captured.key).isEqualTo(RecentCategoryDataStoreCommon.ComparisonQuiz.key) + + val addFunctionShouldBeCalled = categoryIdToAdd !in initialCategories + + val newValueSlot = slot>() + coVerify( + exactly = if (addFunctionShouldBeCalled) 1 else 0 + ) { recentCategoriesDataStoreManager.editPreference(any(), capture(newValueSlot)) } + + if (addFunctionShouldBeCalled) { + assertThat(newValueSlot.captured.size).isAtMost(3) + assertThat(newValueSlot.captured).contains(categoryIdToAdd) + } else { + assertThat(newValueSlot.isCaptured).isFalse() + } + } + + // Tests for cleanAllSavedCategories + @Test + fun `cleanAllSavedCategories should clean all saved categories`() = runTest { + // Mock the necessary dependencies and data + coJustRun { recentCategoriesDataStoreManager.editPreference>(any(), any()) } + + // Act + recentCategoriesRepository.cleanAllSavedCategories() + + coVerify(exactly = 1) { + recentCategoriesDataStoreManager.editPreference( + key = RecentCategoryDataStoreCommon.MultiChoice.key, + newValue = emptySet() + ) + recentCategoriesDataStoreManager.editPreference( + key = RecentCategoryDataStoreCommon.Wordle.key, + newValue = emptySet() + ) + recentCategoriesDataStoreManager.editPreference( + key = RecentCategoryDataStoreCommon.ComparisonQuiz.key, + newValue = emptySet() + ) + } + } + + companion object { + // Params for getCategories tests + @JvmStatic + private fun getCategoriesParams( + allCategories: List + ) = listOf( + Arguments.of( + true, + setOf( + allCategories[0].id, + allCategories[1].id, + allCategories[2].id + ) + ), + Arguments.of( + false, + setOf( + allCategories[0].id, + allCategories[1].id, + allCategories[2].id + ) + ), + Arguments.of( + true, + emptySet() + ), + Arguments.of( + false, + emptySet() + ), + Arguments.of( + true, + setOf( + allCategories.first().id + ) + ) + ) + + @JvmStatic + fun getMultiChoiceCategoriesParams() = getCategoriesParams(multiChoiceQuestionCategories) + + @JvmStatic + fun getWordleCategoriesParams() = getCategoriesParams(WordleCategories.allCategories) + + @JvmStatic + fun getComparisonCategoriesParams() = getCategoriesParams(allComparisonQuizCategories) + + // Params for addCategory tests + @JvmStatic + private fun addCategoryParams( + allCategories: List + ) = listOf( + Arguments.of( + allCategories[0].id, + emptySet() + ), + Arguments.of( + allCategories[0].id, + setOf( + allCategories[1].id + ) + ), + Arguments.of( + allCategories[0].id, + setOf( + allCategories[1].id, + allCategories[2].id + ) + ), + Arguments.of( + allCategories[0].id, + setOf( + allCategories[1].id, + allCategories[2].id, + allCategories[3].id + ) + ), + // Test that adding a category that already exists in the set will not change the set + Arguments.of( + allCategories[0].id, + setOf( + allCategories[0].id, + ) + ), + // Test that adding a category that already exists in the set will not change the set + Arguments.of( + allCategories[1].id, + setOf( + allCategories[0].id, + allCategories[1].id + ) + ), + // Test that adding a category that already exists in the set will not change the set + Arguments.of( + allCategories[1].id, + setOf( + allCategories[0].id, + allCategories[1].id, + allCategories[2].id + ) + ), + ) + + @JvmStatic + fun addMultiChoiceCategoryParams() = addCategoryParams(multiChoiceQuestionCategories) + + @JvmStatic + fun addWordleCategoryParams() = addCategoryParams(WordleCategories.allCategories) + + @JvmStatic + fun addComparisonCategoryParams() = addCategoryParams(allComparisonQuizCategories) + + private val allComparisonQuizCategories = listOf( + ComparisonQuizCategory( + id = "numbers", + name = "Numbers".toUiText(), + description = "Numbers description", + image = "", + questionDescription = ComparisonQuizCategory.QuestionDescription( + greater = "Which number is greater?", + less = "Which number is lesser?", + ), + formatType = ComparisonQuizFormatType.Number, + dataSourceAttribution = ComparisonQuizCategory.DataSourceAttribution( + text = "NewQuiz API", + logo = "" + ), + requireInternetConnection = false + ), + ComparisonQuizCategory( + id = "countries", + name = "Countries".toUiText(), + description = "Countries description", + image = "", + questionDescription = ComparisonQuizCategory.QuestionDescription( + greater = "Which country is bigger?", + less = "Which country is smaller?", + ), + formatType = ComparisonQuizFormatType.Number, + dataSourceAttribution = ComparisonQuizCategory.DataSourceAttribution( + text = "NewQuiz API", + logo = "" + ), + requireInternetConnection = false + ), + ComparisonQuizCategory( + id = "cities", + name = "Cities".toUiText(), + description = "Cities description", + image = "", + questionDescription = ComparisonQuizCategory.QuestionDescription( + greater = "Which city is bigger?", + less = "Which city is smaller?", + ), + formatType = ComparisonQuizFormatType.Number, + dataSourceAttribution = ComparisonQuizCategory.DataSourceAttribution( + text = "NewQuiz API", + logo = "" + ), + requireInternetConnection = true + ), + ComparisonQuizCategory( + id = "planets", + name = "Planets".toUiText(), + description = "Planets description", + image = "", + questionDescription = ComparisonQuizCategory.QuestionDescription( + greater = "Which planet is bigger?", + less = "Which planet is smaller?", + ), + formatType = ComparisonQuizFormatType.Number, + dataSourceAttribution = ComparisonQuizCategory.DataSourceAttribution( + text = "NewQuiz API", + logo = "" + ), + requireInternetConnection = true + ), + ) + } +} diff --git a/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt b/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt index 69e64cd2..8f777b31 100644 --- a/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt +++ b/domain/src/main/java/com/infinitepower/newquiz/domain/repository/home/RecentCategoriesRepository.kt @@ -2,7 +2,6 @@ package com.infinitepower.newquiz.domain.repository.home import androidx.annotation.Keep import com.infinitepower.newquiz.model.comparison_quiz.ComparisonQuizCategory -import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceBaseCategory import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceCategory import com.infinitepower.newquiz.model.wordle.WordleCategory import kotlinx.coroutines.flow.Flow @@ -21,13 +20,19 @@ fun emptyHomeCategories() = HomeCategories( typealias HomeCategoriesFlow = Flow> interface RecentCategoriesRepository { - fun getMultiChoiceCategories(): HomeCategoriesFlow + fun getMultiChoiceCategories( + isInternetAvailable: Boolean + ): HomeCategoriesFlow - fun getWordleCategories(): HomeCategoriesFlow + fun getWordleCategories( + isInternetAvailable: Boolean + ): HomeCategoriesFlow - fun getComparisonCategories(): HomeCategoriesFlow + fun getComparisonCategories( + isInternetAvailable: Boolean + ): HomeCategoriesFlow - suspend fun addMultiChoiceCategory(category: MultiChoiceBaseCategory) + suspend fun addMultiChoiceCategory(categoryId: String) suspend fun addWordleCategory(categoryId: String) diff --git a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/MultiChoiceQuizScreenViewModel.kt b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/MultiChoiceQuizScreenViewModel.kt index 6fb8cfbb..d3504dff 100644 --- a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/MultiChoiceQuizScreenViewModel.kt +++ b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/MultiChoiceQuizScreenViewModel.kt @@ -165,7 +165,7 @@ class QuizScreenViewModel @Inject constructor( val difficulty = savedStateHandle.get(MultiChoiceQuizScreenNavArg::difficulty.name) if (category.hasCategory) { - recentCategoriesRepository.addMultiChoiceCategory(category) + recentCategoriesRepository.addMultiChoiceCategory(category.id) } getRandomQuestionUseCase(questionSize, category, difficulty).collect { res -> diff --git a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreenViewModel.kt b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreenViewModel.kt index f1882e09..0fba7701 100644 --- a/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreenViewModel.kt +++ b/multi-choice-quiz/src/main/java/com/infinitepower/newquiz/multi_choice_quiz/list/MultiChoiceQuizListScreenViewModel.kt @@ -2,20 +2,28 @@ package com.infinitepower.newquiz.multi_choice_quiz.list import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.infinitepower.newquiz.core.network.NetworkStatus +import com.infinitepower.newquiz.core.network.NetworkStatusTracker import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import com.infinitepower.newquiz.domain.repository.multi_choice_quiz.saved_questions.SavedMultiChoiceQuestionsRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import javax.inject.Inject @HiltViewModel +@OptIn(ExperimentalCoroutinesApi::class) class MultiChoiceQuizListScreenViewModel @Inject constructor( private val savedQuestionsRepository: SavedMultiChoiceQuestionsRepository, - private val recentCategoriesRepository: RecentCategoriesRepository + private val recentCategoriesRepository: RecentCategoriesRepository, + private val networkStatusTracker: NetworkStatusTracker ) : ViewModel() { private val _uiState = MutableStateFlow(MultiChoiceQuizListScreenUiState()) val uiState = _uiState.asStateFlow() @@ -29,9 +37,18 @@ class MultiChoiceQuizListScreenViewModel @Inject constructor( } }.launchIn(viewModelScope) - recentCategoriesRepository - .getMultiChoiceCategories() - .onEach { homeCategories -> + val networkStatus = networkStatusTracker + .networkStatus + .stateIn( + initialValue = NetworkStatus.Unknown, + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000) + ) + + networkStatus + .flatMapLatest { status -> + recentCategoriesRepository.getMultiChoiceCategories(isInternetAvailable = status.isAvailable()) + }.onEach { homeCategories -> _uiState.update { currentState -> currentState.copy(homeCategories = homeCategories) } diff --git a/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/OnlineServicesCore.kt b/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/OnlineServicesCore.kt deleted file mode 100644 index 1eed1b4a..00000000 --- a/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/OnlineServicesCore.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.infinitepower.newquiz.online_services.core - -import kotlinx.coroutines.flow.Flow - -interface OnlineServicesCore { - suspend fun connectionAvailable(): Boolean - - fun connectionAvailableFlow(): Flow -} \ No newline at end of file diff --git a/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/OnlineServicesCoreImpl.kt b/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/OnlineServicesCoreImpl.kt deleted file mode 100644 index 0be1f9b1..00000000 --- a/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/OnlineServicesCoreImpl.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.infinitepower.newquiz.online_services.core - -import com.infinitepower.newquiz.online_services.core.network.NetworkStatus -import com.infinitepower.newquiz.online_services.core.network.NetworkStatusTracker -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class OnlineServicesCoreImpl @Inject constructor( - private val networkStatusTracker: NetworkStatusTracker -) : OnlineServicesCore { - override suspend fun connectionAvailable() = connectionAvailableFlow().first() - - override fun connectionAvailableFlow(): Flow = networkStatusTracker - .netWorkStatus - .map { it is NetworkStatus.Available } -} \ No newline at end of file diff --git a/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/network/NetworkStatus.kt b/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/network/NetworkStatus.kt deleted file mode 100644 index 1ebc3a67..00000000 --- a/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/network/NetworkStatus.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.infinitepower.newquiz.online_services.core.network - -sealed class NetworkStatus { - object Available : NetworkStatus() - - object Unavailable : NetworkStatus() -} diff --git a/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/network/NetworkStatusTracker.kt b/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/network/NetworkStatusTracker.kt deleted file mode 100644 index ae4197db..00000000 --- a/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/network/NetworkStatusTracker.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.infinitepower.newquiz.online_services.core.network - -import kotlinx.coroutines.flow.Flow - -interface NetworkStatusTracker { - val netWorkStatus: Flow -} \ No newline at end of file diff --git a/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/worker/multichoicequiz/MultiChoiceQuizEndGameWorker.kt b/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/worker/multichoicequiz/MultiChoiceQuizEndGameWorker.kt index 4a9821db..1c5d0459 100644 --- a/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/worker/multichoicequiz/MultiChoiceQuizEndGameWorker.kt +++ b/online_services/src/main/java/com/infinitepower/newquiz/online_services/core/worker/multichoicequiz/MultiChoiceQuizEndGameWorker.kt @@ -5,14 +5,13 @@ import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.infinitepower.newquiz.core.analytics.logging.multi_choice_quiz.MultiChoiceQuizLoggingAnalytics +import com.infinitepower.newquiz.core.network.NetworkStatusTracker import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceQuestionStep import com.infinitepower.newquiz.model.multi_choice_quiz.countCorrectQuestions -import com.infinitepower.newquiz.online_services.core.OnlineServicesCore import com.infinitepower.newquiz.online_services.domain.game.xp.MultiChoiceQuizXPRepository import com.infinitepower.newquiz.online_services.domain.user.UserRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json @HiltWorker @@ -22,7 +21,7 @@ class MultiChoiceQuizEndGameWorker @AssistedInject constructor( private val multiChoiceQuizLoggingAnalytics: MultiChoiceQuizLoggingAnalytics, private val multiChoiceQuizXPRepository: MultiChoiceQuizXPRepository, private val userRepository: UserRepository, - private val onlineServicesCore: OnlineServicesCore + private val networkStatusTracker: NetworkStatusTracker ) : CoroutineWorker(appContext, workerParams) { companion object { @@ -42,7 +41,7 @@ class MultiChoiceQuizEndGameWorker @AssistedInject constructor( val saveNewXP = inputData.getBoolean(INPUT_SAVE_NEW_XP, true) - if (onlineServicesCore.connectionAvailable() && saveNewXP) { + if (networkStatusTracker.connectionAvailable() && saveNewXP) { val randomXP = multiChoiceQuizXPRepository.generateQuestionsRandomXP(questionSteps) val averageQuizTime = questionSteps.getAverageQuizTime() diff --git a/online_services/src/main/java/com/infinitepower/newquiz/online_services/di/OnlineServicesModule.kt b/online_services/src/main/java/com/infinitepower/newquiz/online_services/di/OnlineServicesModule.kt deleted file mode 100644 index 352467ca..00000000 --- a/online_services/src/main/java/com/infinitepower/newquiz/online_services/di/OnlineServicesModule.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.infinitepower.newquiz.online_services.di - -import com.infinitepower.newquiz.online_services.core.OnlineServicesCore -import com.infinitepower.newquiz.online_services.core.OnlineServicesCoreImpl -import com.infinitepower.newquiz.online_services.core.network.NetworkStatusTracker -import com.infinitepower.newquiz.online_services.core.network.NetworkStatusTrackerImpl -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent - -@Module -@InstallIn(SingletonComponent::class) -abstract class OnlineServicesModule { - @Binds - abstract fun bindNetworkStatusTracker( - networkStatusTrackerImpl: NetworkStatusTrackerImpl - ): NetworkStatusTracker - - @Binds - abstract fun bindOnlineServicesCore( - onlineServicesCoreImpl: OnlineServicesCoreImpl - ): OnlineServicesCore -} \ No newline at end of file diff --git a/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreenViewModel.kt b/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreenViewModel.kt index 1ca9a4d6..9e724c38 100644 --- a/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreenViewModel.kt +++ b/wordle/src/main/java/com/infinitepower/newquiz/wordle/list/WordleListScreenViewModel.kt @@ -2,26 +2,43 @@ package com.infinitepower.newquiz.wordle.list import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.infinitepower.newquiz.core.network.NetworkStatus +import com.infinitepower.newquiz.core.network.NetworkStatusTracker import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import javax.inject.Inject @HiltViewModel +@OptIn(ExperimentalCoroutinesApi::class) class WordleListScreenViewModel @Inject constructor( - private val recentCategoriesRepository: RecentCategoriesRepository + private val recentCategoriesRepository: RecentCategoriesRepository, + private val networkStatusTracker: NetworkStatusTracker ) : ViewModel() { private val _uiState = MutableStateFlow(WordleListUiState()) val uiState = _uiState.asStateFlow() init { - recentCategoriesRepository - .getWordleCategories() - .onEach { homeCategories -> + val networkStatus = networkStatusTracker + .networkStatus + .stateIn( + initialValue = NetworkStatus.Unknown, + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000) + ) + + networkStatus + .flatMapLatest { status -> + recentCategoriesRepository.getWordleCategories(isInternetAvailable = status.isAvailable()) + }.onEach { homeCategories -> _uiState.update { currentState -> currentState.copy(homeCategories = homeCategories) } diff --git a/wordle/src/main/java/com/infinitepower/newquiz/wordle/util/worker/WordleEndGameWorker.kt b/wordle/src/main/java/com/infinitepower/newquiz/wordle/util/worker/WordleEndGameWorker.kt index 6888feab..6cf6c9a2 100644 --- a/wordle/src/main/java/com/infinitepower/newquiz/wordle/util/worker/WordleEndGameWorker.kt +++ b/wordle/src/main/java/com/infinitepower/newquiz/wordle/util/worker/WordleEndGameWorker.kt @@ -7,12 +7,12 @@ import androidx.work.WorkManager import androidx.work.WorkerParameters import com.infinitepower.newquiz.core.analytics.logging.maze.MazeLoggingAnalytics import com.infinitepower.newquiz.core.analytics.logging.wordle.WordleLoggingAnalytics +import com.infinitepower.newquiz.core.network.NetworkStatusTracker import com.infinitepower.newquiz.core.util.kotlin.toULong import com.infinitepower.newquiz.data.worker.UpdateGlobalEventDataWorker import com.infinitepower.newquiz.domain.repository.home.RecentCategoriesRepository import com.infinitepower.newquiz.model.global_event.GameEvent import com.infinitepower.newquiz.model.wordle.WordleQuizType -import com.infinitepower.newquiz.online_services.core.OnlineServicesCore import com.infinitepower.newquiz.online_services.domain.game.xp.WordleXpRepository import com.infinitepower.newquiz.online_services.domain.user.UserRepository import dagger.assisted.Assisted @@ -22,7 +22,7 @@ import dagger.assisted.AssistedInject class WordleEndGameWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted workerParams: WorkerParameters, - private val onlineServicesCore: OnlineServicesCore, + private val networkStatusTracker: NetworkStatusTracker, private val userRepository: UserRepository, private val wordleXpRepository: WordleXpRepository, private val wordleLoggingAnalytics: WordleLoggingAnalytics, @@ -70,7 +70,7 @@ class WordleEndGameWorker @AssistedInject constructor( mazeLoggingAnalytics.logMazeItemPlayed(isLastRowCorrect) } - if (onlineServicesCore.connectionAvailable()) { + if (networkStatusTracker.connectionAvailable()) { val newXp = if (isLastRowCorrect) { wordleXpRepository.generateRandomXP(currentRowPosition) } else 0