From d7d4cd6616342cdf9a3f5d968fe7c80703d1d3bf Mon Sep 17 00:00:00 2001 From: NiazSagor Date: Thu, 1 Jan 2026 05:59:23 +0600 Subject: [PATCH 1/3] Implement problem sets and add Blind 75 list This commit introduces a structured way to handle different sets of LeetCode problems, such as the "Blind 75". Key changes include: - Added `ProblemSet`, `ProblemSetFactory`, and `ProblemSetType` to define and manage problem collections. - Implemented `AssetProblemSetFactory` to create problem sets from JSON files stored in the assets directory. - Added a new JSON file `blind_75.json` containing the list of problems for the Blind 75 set. - Refactored `ProblemsRepositoryImpl` to use the new factory pattern for loading a master list of all problems and filtering them based on a selected problem set. - Updated `getProblems` method to accept an optional `ProblemSetType` to allow fetching specific lists. - Injected `AssetManager` and `Gson` via a new `FactoryModule` for Hilt dependency injection. --- app/src/main/assets/list/blind_75.json | 85 +++++++++++++++++++ .../plus/data/repository/di/FactoryModule.kt | 41 +++++++++ .../repository/problems/ProblemsRepository.kt | 3 +- .../problems/ProblemsRepositoryImpl.kt | 49 ++++------- .../plus/data/sets/AssetProblemSetFactory.kt | 56 ++++++++++++ .../dev/leetcode/plus/data/sets/ProblemSet.kt | 31 +++++++ .../plus/data/sets/ProblemSetFactory.kt | 9 ++ .../plus/domain/model/ProblemSetType.kt | 20 +++++ .../targetset/SetWeeklyTargetViewModel.kt | 2 +- 9 files changed, 260 insertions(+), 36 deletions(-) create mode 100644 app/src/main/assets/list/blind_75.json create mode 100644 app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/di/FactoryModule.kt create mode 100644 app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/AssetProblemSetFactory.kt create mode 100644 app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/ProblemSet.kt create mode 100644 app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/ProblemSetFactory.kt create mode 100644 app/src/main/kotlin/com/byteutility/dev/leetcode/plus/domain/model/ProblemSetType.kt diff --git a/app/src/main/assets/list/blind_75.json b/app/src/main/assets/list/blind_75.json new file mode 100644 index 0000000..106dacb --- /dev/null +++ b/app/src/main/assets/list/blind_75.json @@ -0,0 +1,85 @@ +[ + "Two Sum", + "Longest Substring Without Repeating Characters", + "Longest Palindromic Substring", + "Container With Most Water", + "3Sum", + "Remove Nth Node From End of List", + "Valid Parentheses", + "Merge Two Sorted Lists", + "Merge k Sorted Lists", + "Search in Rotated Sorted Array", + "Combination Sum", + "Group Anagrams", + "Maximum Subarray", + "Jump Game", + "Merge Intervals", + "Insert Interval", + "Unique Paths", + "Climbing Stairs", + "Set Matrix Zeroes", + "Search a 2D Matrix", + "Minimum Window Substring", + "Subsets", + "Word Search", + "Decode Ways", + "Validate Binary Search Tree", + "Same Tree", + "Binary Tree Level Order Traversal", + "Maximum Depth of Binary Tree", + "Construct Binary Tree from Preorder and Inorder Traversal", + "Best Time to Buy and Sell Stock", + "Binary Tree Maximum Path Sum", + "Valid Palindrome", + "Word Break", + "Linked List Cycle", + "Reorder List", + "Maximum Product Subarray", + "Find Minimum in Rotated Sorted Array", + "Reverse Bits", + "Number of 1 Bits", + "House Robber", + "Number of Islands", + "Reverse Linked List", + "Course Schedule", + "Implement Trie (Prefix Tree)", + "Design Add and Search Words Data Structure", + "Word Search II", + "House Robber II", + "Contains Duplicate", + "Invert Binary Tree", + "Kth Smallest Element in a BST", + "Lowest Common Ancestor of a Binary Search Tree", + "Lowest Common Ancestor of a Binary Tree", + "Product of Array Except Self", + "Valid Anagram", + "Meeting Rooms", + "Meeting Rooms II", + "Graph Valid Tree", + "Missing Number", + "Alien Dictionary", + "Encode and Decode Strings", + "Find Median from Data Stream", + "Longest Increasing Subsequence", + "Coin Change", + "Sum of Two Integers", + "Counting Bits", + "Longest Common Subsequence", + "Word Break Problem", + "Clone Graph", + "Pacific Atlantic Water Flow", + "Longest Consecutive Sequence", + "Number of Connected Components in an Undirected Graph", + "Non-overlapping Intervals", + "Reverse a Linked List", + "Detect Cycle in a Linked List", + "Spiral Matrix", + "Rotate Image", + "Longest Repeating Character Replacement", + "Palindromic Substrings", + "Invert/Flip Binary Tree", + "Serialize and Deserialize Binary Tree", + "Subtree of Another Tree", + "Lowest Common Ancestor of BST", + "Top K Frequent Elements" +] diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/di/FactoryModule.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/di/FactoryModule.kt new file mode 100644 index 0000000..4008f58 --- /dev/null +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/di/FactoryModule.kt @@ -0,0 +1,41 @@ +package com.byteutility.dev.leetcode.plus.data.repository.di + +import android.content.Context +import android.content.res.AssetManager +import com.byteutility.dev.leetcode.plus.data.sets.AssetProblemSetFactory +import com.byteutility.dev.leetcode.plus.data.sets.ProblemSetFactory +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object FactoryModule { + + @Provides + @Singleton + fun provideGson(): Gson { + return GsonBuilder() + .create() + } + + @Provides + @Singleton + fun provideAssetManager(@ApplicationContext context: Context): AssetManager { + return context.assets + } + + @Provides + @Singleton + fun provideProblemSetFactory( + assetManager: AssetManager, + gson: Gson + ): ProblemSetFactory { + return AssetProblemSetFactory(assetManager, gson) + } +} diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/problems/ProblemsRepository.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/problems/ProblemsRepository.kt index 6bbb69c..7cef439 100644 --- a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/problems/ProblemsRepository.kt +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/problems/ProblemsRepository.kt @@ -1,9 +1,10 @@ package com.byteutility.dev.leetcode.plus.data.repository.problems import com.byteutility.dev.leetcode.plus.data.model.LeetCodeProblem +import com.byteutility.dev.leetcode.plus.domain.model.ProblemSetType import com.byteutility.dev.leetcode.plus.network.responseVo.LeetCodeQuestionResponse interface ProblemsRepository { - suspend fun getProblems(limit: Long): List + suspend fun getProblems(type: ProblemSetType? = null): List suspend fun getSelectedRawQuestion(titleSlug: String): LeetCodeQuestionResponse } diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/problems/ProblemsRepositoryImpl.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/problems/ProblemsRepositoryImpl.kt index 9bff305..4cf8145 100644 --- a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/problems/ProblemsRepositoryImpl.kt +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/problems/ProblemsRepositoryImpl.kt @@ -1,54 +1,35 @@ package com.byteutility.dev.leetcode.plus.data.repository.problems -import android.content.Context import com.byteutility.dev.leetcode.plus.data.model.LeetCodeProblem +import com.byteutility.dev.leetcode.plus.data.sets.ProblemSet +import com.byteutility.dev.leetcode.plus.data.sets.ProblemSetFactory +import com.byteutility.dev.leetcode.plus.domain.model.ProblemSetType import com.byteutility.dev.leetcode.plus.network.RestApiService import com.byteutility.dev.leetcode.plus.network.responseVo.LeetCodeQuestionResponse -import com.byteutility.dev.leetcode.plus.network.responseVo.ProblemSetResponseVo -import com.google.gson.Gson -import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import javax.inject.Inject class ProblemsRepositoryImpl @Inject constructor( - @ApplicationContext private val context: Context, + private val factory: ProblemSetFactory, private val restApiService: RestApiService ) : ProblemsRepository { + private val masterList: List by lazy { + factory.loadMasterList() + } + private val setCache = mutableMapOf() + override suspend fun getProblems( - limit: Long + type: ProblemSetType? ): List { - var leetCodeProblems: List = mutableListOf() - withContext(Dispatchers.IO) { - val response: ProblemSetResponseVo = parseProblemsJson(context) - response.problemSetQuestionList.map { - LeetCodeProblem( - title = it.title, - difficulty = it.difficulty, - tag = it.topicTags.firstOrNull()?.name ?: "NO_TAG", - titleSlug = it.titleSlug, - ) - }.run { - leetCodeProblems = this + return withContext(Dispatchers.IO) { + if (type == null) return@withContext masterList + val problemSet = setCache.getOrPut(type) { + factory.createSet(type) } + masterList.filter { problemSet.isIncluded(it) } } - return leetCodeProblems - } - - private fun parseProblemsJson( - context: Context - ): ProblemSetResponseVo { - val jsonString = loadJsonFromAssets(context, "problems.json") - val gson = Gson() - return gson.fromJson(jsonString, ProblemSetResponseVo::class.java) - } - - private fun loadJsonFromAssets( - context: Context, - fileName: String - ): String { - return context.assets.open(fileName).bufferedReader().use { it.readText() } } @Throws diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/AssetProblemSetFactory.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/AssetProblemSetFactory.kt new file mode 100644 index 0000000..b27453c --- /dev/null +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/AssetProblemSetFactory.kt @@ -0,0 +1,56 @@ +package com.byteutility.dev.leetcode.plus.data.sets + +import android.content.res.AssetManager +import com.byteutility.dev.leetcode.plus.data.model.LeetCodeProblem +import com.byteutility.dev.leetcode.plus.domain.model.ProblemSetType +import com.byteutility.dev.leetcode.plus.domain.model.ProblemSetType.ExclusiveProblemSet +import com.byteutility.dev.leetcode.plus.domain.model.ProblemSetType.UserDefinedProblemSet +import com.byteutility.dev.leetcode.plus.network.responseVo.ProblemSetResponseVo +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import javax.inject.Inject + +class AssetProblemSetFactory @Inject constructor( + private val assetManager: AssetManager, + private val gson: Gson +) : ProblemSetFactory { + override fun createSet(type: ProblemSetType): ProblemSet { + return when (type) { + is ExclusiveProblemSet -> { + val titles = loadTitlesFromAssets(type.type.fileName) + ExclusiveProblemSet(displayName = type.displayName, titles = titles) + } + + is UserDefinedProblemSet -> { + UserDefinedProblemSet( + displayName = type.displayName, + titles = emptyList() + ) + } + } + } + + override fun loadMasterList(): List { + return loadAllProblemFromAssets("problems.json").toList() + } + + private fun loadTitlesFromAssets(fileName: String): List { + val json = assetManager.open("list/$fileName").bufferedReader().use { it.readText() } + val listType = object : TypeToken>() {}.type + return gson.fromJson>(json, listType) + } + + private fun loadAllProblemFromAssets(fileName: String): List { + val json = assetManager.open(fileName).bufferedReader().use { it.readText() } + val response = gson.fromJson(json, ProblemSetResponseVo::class.java) + val allProblems = response.problemSetQuestionList.map { + LeetCodeProblem( + title = it.title, + difficulty = it.difficulty, + tag = it.topicTags.firstOrNull()?.name ?: "NO_TAG", + titleSlug = it.titleSlug, + ) + } + return allProblems + } +} diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/ProblemSet.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/ProblemSet.kt new file mode 100644 index 0000000..8255a18 --- /dev/null +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/ProblemSet.kt @@ -0,0 +1,31 @@ +package com.byteutility.dev.leetcode.plus.data.sets + +import com.byteutility.dev.leetcode.plus.data.model.LeetCodeProblem + +interface ProblemSet { + val displayName: String + + /** + * Returns true if the given problem belongs to this specific set. + */ + fun isIncluded(problem: LeetCodeProblem): Boolean +} + +class ExclusiveProblemSet( + override val displayName: String, + private val titles: List +) : ProblemSet { + + override fun isIncluded(problem: LeetCodeProblem): Boolean { + return titles.contains(problem.title.trim()) + } +} + +class UserDefinedProblemSet( + override val displayName: String, + private val titles: List +) : ProblemSet { + override fun isIncluded(problem: LeetCodeProblem): Boolean { + return titles.contains(problem.title) + } +} diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/ProblemSetFactory.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/ProblemSetFactory.kt new file mode 100644 index 0000000..8b0dc80 --- /dev/null +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/ProblemSetFactory.kt @@ -0,0 +1,9 @@ +package com.byteutility.dev.leetcode.plus.data.sets + +import com.byteutility.dev.leetcode.plus.data.model.LeetCodeProblem +import com.byteutility.dev.leetcode.plus.domain.model.ProblemSetType + +interface ProblemSetFactory { + fun createSet(type: ProblemSetType): ProblemSet + fun loadMasterList(): List +} diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/domain/model/ProblemSetType.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/domain/model/ProblemSetType.kt new file mode 100644 index 0000000..340e4b8 --- /dev/null +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/domain/model/ProblemSetType.kt @@ -0,0 +1,20 @@ +package com.byteutility.dev.leetcode.plus.domain.model + +enum class ExclusiveProblemSetType(val fileName: String) { + Blind75("blind_75.json"), + NeetCode150("neetcode_150.json"), +} + +sealed interface ProblemSetType { + val displayName: String + + data class ExclusiveProblemSet( + override val displayName: String, + val type: ExclusiveProblemSetType, + ) : ProblemSetType + + data class UserDefinedProblemSet( + override val displayName: String, + val fileName: String, + ) : ProblemSetType +} diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/targetset/SetWeeklyTargetViewModel.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/targetset/SetWeeklyTargetViewModel.kt index aae3272..bb41c50 100644 --- a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/targetset/SetWeeklyTargetViewModel.kt +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/targetset/SetWeeklyTargetViewModel.kt @@ -37,7 +37,7 @@ class SetWeeklyTargetViewModel @Inject constructor( init { viewModelScope.launch(Dispatchers.IO) { - problemsList.value = problemsRepository.getProblems(limit = 3000) + problemsList.value = problemsRepository.getProblems() } } From 7ed3ab2b837a87d4fbd556787e84acbe09f856d7 Mon Sep 17 00:00:00 2001 From: NiazSagor Date: Thu, 1 Jan 2026 06:00:38 +0600 Subject: [PATCH 2/3] Load all problems by default --- .../plus/ui/screens/allproblems/AllProblemsViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/allproblems/AllProblemsViewModel.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/allproblems/AllProblemsViewModel.kt index 41b03a7..45c4d97 100644 --- a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/allproblems/AllProblemsViewModel.kt +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/allproblems/AllProblemsViewModel.kt @@ -67,7 +67,7 @@ class AllProblemsViewModel @Inject constructor( init { viewModelScope.launch(Dispatchers.IO) { - _allProblemsList.value = problemsRepository.getProblems(limit = 3000) + _allProblemsList.value = problemsRepository.getProblems() _allTags.value = _allProblemsList.value.map { it.tag }.distinct() _allDifficulties.value = _allProblemsList.value.map { it.difficulty }.distinct() } From 66aaff44572e060af88c824903b6ef6bda6e66d9 Mon Sep 17 00:00:00 2001 From: NiazSagor Date: Thu, 1 Jan 2026 23:51:24 +0600 Subject: [PATCH 3/3] Implement filtering by predefined problem sets This commit introduces the ability to filter problems by predefined sets, such as "Blind 75". Changes include: * Refactoring `ExclusiveProblemSet` to a more generic `PredefinedProblemSet` that uses `SetMetadata`. * Creating `PredefinedProblemSetMetadataProvider` to load problem set metadata from a new `predefined_problem_set_manifest.json` asset file. * Updating `AllProblemsViewModel` to load problems based on the selected problem set, making the list of problems, tags, and difficulties dynamic. * Adding a "Problem Sets" section with `FilterChip`s to the `FilterBottomSheet` in `AllProblemsScreen` to allow users to select a predefined set. --- .../predefined_problem_set_manifest.json | 7 ++ .../PredefinedProblemSetMetadataProvider.kt | 30 ++++++++ .../plus/data/sets/AssetProblemSetFactory.kt | 8 +- .../dev/leetcode/plus/data/sets/ProblemSet.kt | 2 +- .../plus/domain/model/ProblemSetType.kt | 12 +-- .../leetcode/plus/domain/model/SetMetadata.kt | 3 + .../ui/navigation/LeetCodePlusNavGraph.kt | 2 +- .../screens/allproblems/AllProblemsScreen.kt | 40 +++++++++- .../allproblems/AllProblemsViewModel.kt | 74 ++++++++++++++----- .../contest/details/ContestDetailViewModel.kt | 1 - 10 files changed, 143 insertions(+), 36 deletions(-) create mode 100644 app/src/main/assets/predefined_problem_set_manifest.json create mode 100644 app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/problems/predefined/PredefinedProblemSetMetadataProvider.kt create mode 100644 app/src/main/kotlin/com/byteutility/dev/leetcode/plus/domain/model/SetMetadata.kt diff --git a/app/src/main/assets/predefined_problem_set_manifest.json b/app/src/main/assets/predefined_problem_set_manifest.json new file mode 100644 index 0000000..c6e0554 --- /dev/null +++ b/app/src/main/assets/predefined_problem_set_manifest.json @@ -0,0 +1,7 @@ +[ + { + "id": "blind_75", + "name": "Blind 75", + "file": "blind_75.json" + } +] diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/problems/predefined/PredefinedProblemSetMetadataProvider.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/problems/predefined/PredefinedProblemSetMetadataProvider.kt new file mode 100644 index 0000000..6177ce7 --- /dev/null +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/repository/problems/predefined/PredefinedProblemSetMetadataProvider.kt @@ -0,0 +1,30 @@ +package com.byteutility.dev.leetcode.plus.data.repository.problems.predefined + +import android.content.res.AssetManager +import android.util.Log +import com.byteutility.dev.leetcode.plus.domain.model.SetMetadata +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import java.io.IOException +import javax.inject.Inject + +class PredefinedProblemSetMetadataProvider @Inject constructor( + private val assetManager: AssetManager, + private val gson: Gson +) { + + fun getAvailableStaticSets(): List { + return try { + assetManager.open("predefined_problem_set_manifest.json").bufferedReader() + .use { reader -> + val type = object : TypeToken>() {}.type + gson.fromJson(reader, type) + } + } catch (e: IOException) { + Log.e(this.javaClass.name, "getAvailableStaticSets: exception ${e.message}") + emptyList() + } finally { + emptyList() + } + } +} diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/AssetProblemSetFactory.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/AssetProblemSetFactory.kt index b27453c..077197d 100644 --- a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/AssetProblemSetFactory.kt +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/AssetProblemSetFactory.kt @@ -3,7 +3,7 @@ package com.byteutility.dev.leetcode.plus.data.sets import android.content.res.AssetManager import com.byteutility.dev.leetcode.plus.data.model.LeetCodeProblem import com.byteutility.dev.leetcode.plus.domain.model.ProblemSetType -import com.byteutility.dev.leetcode.plus.domain.model.ProblemSetType.ExclusiveProblemSet +import com.byteutility.dev.leetcode.plus.domain.model.ProblemSetType.PredefinedProblemSet import com.byteutility.dev.leetcode.plus.domain.model.ProblemSetType.UserDefinedProblemSet import com.byteutility.dev.leetcode.plus.network.responseVo.ProblemSetResponseVo import com.google.gson.Gson @@ -16,9 +16,9 @@ class AssetProblemSetFactory @Inject constructor( ) : ProblemSetFactory { override fun createSet(type: ProblemSetType): ProblemSet { return when (type) { - is ExclusiveProblemSet -> { - val titles = loadTitlesFromAssets(type.type.fileName) - ExclusiveProblemSet(displayName = type.displayName, titles = titles) + is PredefinedProblemSet -> { + val titles = loadTitlesFromAssets(type.metadata.file) + PredefinedProblemSet(displayName = type.displayName, titles = titles) } is UserDefinedProblemSet -> { diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/ProblemSet.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/ProblemSet.kt index 8255a18..251ec0b 100644 --- a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/ProblemSet.kt +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/data/sets/ProblemSet.kt @@ -11,7 +11,7 @@ interface ProblemSet { fun isIncluded(problem: LeetCodeProblem): Boolean } -class ExclusiveProblemSet( +class PredefinedProblemSet( override val displayName: String, private val titles: List ) : ProblemSet { diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/domain/model/ProblemSetType.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/domain/model/ProblemSetType.kt index 340e4b8..fbb8e7c 100644 --- a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/domain/model/ProblemSetType.kt +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/domain/model/ProblemSetType.kt @@ -1,17 +1,11 @@ package com.byteutility.dev.leetcode.plus.domain.model -enum class ExclusiveProblemSetType(val fileName: String) { - Blind75("blind_75.json"), - NeetCode150("neetcode_150.json"), -} - sealed interface ProblemSetType { val displayName: String - data class ExclusiveProblemSet( - override val displayName: String, - val type: ExclusiveProblemSetType, - ) : ProblemSetType + data class PredefinedProblemSet(val metadata: SetMetadata) : ProblemSetType { + override val displayName: String = metadata.name + } data class UserDefinedProblemSet( override val displayName: String, diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/domain/model/SetMetadata.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/domain/model/SetMetadata.kt new file mode 100644 index 0000000..0536e2e --- /dev/null +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/domain/model/SetMetadata.kt @@ -0,0 +1,3 @@ +package com.byteutility.dev.leetcode.plus.domain.model + +data class SetMetadata(val id: String, val name: String, val file: String) diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/navigation/LeetCodePlusNavGraph.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/navigation/LeetCodePlusNavGraph.kt index bb79da9..034a925 100644 --- a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/navigation/LeetCodePlusNavGraph.kt +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/navigation/LeetCodePlusNavGraph.kt @@ -9,13 +9,13 @@ import androidx.navigation.compose.rememberNavController import androidx.navigation.toRoute import com.byteutility.dev.leetcode.plus.troubleshoot.TroubleShootScreen import com.byteutility.dev.leetcode.plus.ui.screens.MainScreen +import com.byteutility.dev.leetcode.plus.ui.screens.contest.details.ContestDetailScreen import com.byteutility.dev.leetcode.plus.ui.screens.leetcodelogin.LeetCodeLoginWebView import com.byteutility.dev.leetcode.plus.ui.screens.login.UserLoginScreen import com.byteutility.dev.leetcode.plus.ui.screens.problem.details.ProblemDetailsScreen import com.byteutility.dev.leetcode.plus.ui.screens.solutions.VideoSolutionsScreen import com.byteutility.dev.leetcode.plus.ui.screens.targetset.SetWeeklyTargetScreen import com.byteutility.dev.leetcode.plus.ui.screens.targetstatus.GoalProgressScreen -import com.byteutility.dev.leetcode.plus.ui.screens.contest.details.ContestDetailScreen import com.byteutility.dev.leetcode.plus.ui.screens.webview.CommonWebViewScreen @Composable diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/allproblems/AllProblemsScreen.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/allproblems/AllProblemsScreen.kt index aeaba66..e0bb21e 100644 --- a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/allproblems/AllProblemsScreen.kt +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/allproblems/AllProblemsScreen.kt @@ -64,6 +64,7 @@ import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.byteutility.dev.leetcode.plus.data.model.LeetCodeProblem +import com.byteutility.dev.leetcode.plus.domain.model.SetMetadata import com.byteutility.dev.leetcode.plus.ui.theme.easyCategory import com.byteutility.dev.leetcode.plus.ui.theme.hardCategory import com.byteutility.dev.leetcode.plus.ui.theme.mediumCategory @@ -86,6 +87,8 @@ fun AllProblemsScreen( val selectedTags by viewModel.selectedTags.collectAsStateWithLifecycle() var showFilterBottomSheet by remember { mutableStateOf(false) } val activeFilterCount by viewModel.activeFilterCount.collectAsStateWithLifecycle() + val selectedStaticProblemSet by viewModel.selectedStaticProblemSet.collectAsStateWithLifecycle() + val predefinedProblemSet = viewModel.predefinedProblemSets Scaffold( topBar = { @@ -129,6 +132,8 @@ fun AllProblemsScreen( if (showFilterBottomSheet) { FilterBottomSheet( + problemSets = predefinedProblemSet, + selectedStaticProblemSet = selectedStaticProblemSet, selectedTags = selectedTags, selectedDifficulties = selectedDifficulties, tags = tags, @@ -140,7 +145,8 @@ fun AllProblemsScreen( viewModel.clearFilters() showFilterBottomSheet = false }, - onDismiss = { showFilterBottomSheet = false } + onDismiss = { showFilterBottomSheet = false }, + onProblemSetSelected = { viewModel.onProblemSetSelected(it) } ) } @@ -308,6 +314,7 @@ fun getDifficultyColor(difficulty: String): Color { @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable fun FilterBottomSheet( + problemSets: List, tags: List, difficulties: List, onTagSelected: (String) -> Unit, @@ -315,8 +322,10 @@ fun FilterBottomSheet( onApply: () -> Unit, onClear: () -> Unit, onDismiss: () -> Unit, + onProblemSetSelected: (SetMetadata) -> Unit, selectedTags: List, - selectedDifficulties: List + selectedDifficulties: List, + selectedStaticProblemSet: SetMetadata? ) { val scrollState = rememberScrollState() val modalBottomSheetState = rememberModalBottomSheetState() @@ -361,6 +370,28 @@ fun FilterBottomSheet( HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + Text(text = "Problem Sets", style = MaterialTheme.typography.titleMedium) + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.fillMaxWidth() + ) { + problemSets.forEach { set -> + FilterChip( + selected = if (selectedStaticProblemSet == null) false else set == selectedStaticProblemSet, + onClick = { onProblemSetSelected(set) }, + label = { Text(set.name) }, + leadingIcon = if (selectedStaticProblemSet == set) { + { Icon(Icons.Default.Check, contentDescription = null) } + } else { + null + } + ) + } + } + + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + Text(text = "Tags", style = MaterialTheme.typography.titleMedium) FlowRow( @@ -421,7 +452,10 @@ fun PreviewFilterBottomSheet() { onClear = {}, onDismiss = {}, selectedTags = listOf("Array", "String"), - selectedDifficulties = listOf("Easy", "Medium") + selectedDifficulties = listOf("Easy", "Medium"), + problemSets = emptyList(), + onProblemSetSelected = {}, + selectedStaticProblemSet = null ) } diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/allproblems/AllProblemsViewModel.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/allproblems/AllProblemsViewModel.kt index 45c4d97..6600269 100644 --- a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/allproblems/AllProblemsViewModel.kt +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/allproblems/AllProblemsViewModel.kt @@ -2,24 +2,33 @@ package com.byteutility.dev.leetcode.plus.ui.screens.allproblems import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.byteutility.dev.leetcode.plus.data.model.LeetCodeProblem import com.byteutility.dev.leetcode.plus.data.repository.problems.ProblemsRepository +import com.byteutility.dev.leetcode.plus.data.repository.problems.predefined.PredefinedProblemSetMetadataProvider +import com.byteutility.dev.leetcode.plus.domain.model.ProblemSetType +import com.byteutility.dev.leetcode.plus.domain.model.SetMetadata import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch import javax.inject.Inject +@OptIn(ExperimentalCoroutinesApi::class) @HiltViewModel class AllProblemsViewModel @Inject constructor( private val problemsRepository: ProblemsRepository, + private val predefinedProblemSetMetadataProvider: PredefinedProblemSetMetadataProvider, ) : ViewModel() { - private val _allTags = MutableStateFlow>(emptyList()) + val predefinedProblemSets = predefinedProblemSetMetadataProvider.getAvailableStaticSets() + + private val _selectedStaticProblemSet = MutableStateFlow(null) + + val selectedStaticProblemSet = _selectedStaticProblemSet.asStateFlow() private val _selectedTags = MutableStateFlow>(emptyList()) @@ -36,9 +45,42 @@ class AllProblemsViewModel @Inject constructor( selectedTags.size + selectedDifficulties.size } - private val _allProblemsList = MutableStateFlow>(emptyList()) - - private val _allDifficulties = MutableStateFlow>(emptyList()) + private val _allProblemsList = _selectedStaticProblemSet + .flatMapLatest { set -> + flow { + var problemSet: ProblemSetType? = null + if (set != null) { + problemSet = ProblemSetType.PredefinedProblemSet(metadata = set) + } + emit(problemsRepository.getProblems(problemSet)) + } + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = emptyList() + ) + + val tags = _allProblemsList + .flatMapLatest { list -> + flow { + emit(list.map { it.tag }.distinct()) + } + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = emptyList() + ) + + val difficulties = _allProblemsList + .flatMapLatest { list -> + flow { + emit(list.map { it.difficulty }.distinct()) + } + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = emptyList() + ) val activeFilterCount = _activeFilterCount.stateIn( scope = viewModelScope, @@ -62,16 +104,6 @@ class AllProblemsViewModel @Inject constructor( started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) - val tags = _allTags.asStateFlow() - val difficulties = _allDifficulties.asStateFlow() - - init { - viewModelScope.launch(Dispatchers.IO) { - _allProblemsList.value = problemsRepository.getProblems() - _allTags.value = _allProblemsList.value.map { it.tag }.distinct() - _allDifficulties.value = _allProblemsList.value.map { it.difficulty }.distinct() - } - } fun onTagSelected(tag: String) { if (_selectedTags.value.contains(tag)) { @@ -93,4 +125,12 @@ class AllProblemsViewModel @Inject constructor( _selectedTags.value = emptyList() _selectedDifficulties.value = emptyList() } + + fun onProblemSetSelected(setMetadata: SetMetadata) { + if (_selectedStaticProblemSet.value == setMetadata) { + _selectedStaticProblemSet.value = null + return + } + _selectedStaticProblemSet.value = setMetadata + } } diff --git a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/contest/details/ContestDetailViewModel.kt b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/contest/details/ContestDetailViewModel.kt index f74460c..cedf8f5 100644 --- a/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/contest/details/ContestDetailViewModel.kt +++ b/app/src/main/kotlin/com/byteutility/dev/leetcode/plus/ui/screens/contest/details/ContestDetailViewModel.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.time.OffsetDateTime import java.util.concurrent.TimeUnit import javax.inject.Inject