11package com.example.util.simpletimetracker.feature_categories.interactor
22
3- import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType
4- import com.example.util.simpletimetracker.feature_base_adapter.divider.DividerViewData
53import com.example.util.simpletimetracker.core.mapper.CategoryViewDataMapper
4+ import com.example.util.simpletimetracker.core.repo.ResourceRepo
65import com.example.util.simpletimetracker.domain.category.interactor.CategoryInteractor
6+ import com.example.util.simpletimetracker.domain.category.interactor.RecordTypeCategoryInteractor
7+ import com.example.util.simpletimetracker.domain.category.model.Category
78import com.example.util.simpletimetracker.domain.prefs.interactor.PrefsInteractor
89import com.example.util.simpletimetracker.domain.recordTag.interactor.RecordTagInteractor
10+ import com.example.util.simpletimetracker.domain.recordTag.interactor.RecordTypeToDefaultTagInteractor
11+ import com.example.util.simpletimetracker.domain.recordTag.interactor.RecordTypeToTagInteractor
12+ import com.example.util.simpletimetracker.domain.recordTag.model.RecordTag
913import com.example.util.simpletimetracker.domain.recordType.interactor.RecordTypeInteractor
14+ import com.example.util.simpletimetracker.domain.recordType.model.RecordType
15+ import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType
16+ import com.example.util.simpletimetracker.feature_base_adapter.divider.DividerViewData
17+ import com.example.util.simpletimetracker.feature_base_adapter.emptySpace.EmptySpaceViewData
18+ import com.example.util.simpletimetracker.feature_base_adapter.hint.HintViewData
19+ import com.example.util.simpletimetracker.feature_categories.R
1020import com.example.util.simpletimetracker.feature_categories.viewData.CategoriesViewData
21+ import kotlinx.coroutines.Dispatchers
1122import kotlinx.coroutines.async
1223import kotlinx.coroutines.coroutineScope
24+ import kotlinx.coroutines.withContext
1325import javax.inject.Inject
1426
1527class CategoriesViewDataInteractor @Inject constructor(
28+ private val resourceRepo : ResourceRepo ,
1629 private val prefsInteractor : PrefsInteractor ,
1730 private val categoryInteractor : CategoryInteractor ,
1831 private val recordTagInteractor : RecordTagInteractor ,
1932 private val recordTypeInteractor : RecordTypeInteractor ,
33+ private val recordTypeCategoryInteractor : RecordTypeCategoryInteractor ,
34+ private val recordTypeToDefaultTagInteractor : RecordTypeToDefaultTagInteractor ,
35+ private val recordTypeToTagInteractor : RecordTypeToTagInteractor ,
2036 private val categoryViewDataMapper : CategoryViewDataMapper ,
2137) {
2238
23- suspend fun getViewData (): CategoriesViewData = coroutineScope {
24- val typeTags = async { getRecordTypeTagViewData() }
25- val recordTags = async { getRecordTagViewData() }
39+ suspend fun getViewData (
40+ selectedTypeIds : List <Long >,
41+ searchEnabled : Boolean ,
42+ searchText : String ,
43+ navBarHeightDp : Int ,
44+ ): CategoriesViewData = coroutineScope {
45+ val isSearching: Boolean = searchEnabled && searchText.isNotEmpty()
46+
47+ val typeTags = async {
48+ getCategoriesViewData(
49+ selectedTypeIds = selectedTypeIds,
50+ searchText = searchText,
51+ isSearching = isSearching,
52+ )
53+ }
54+ val recordTags = async {
55+ getRecordTagViewData(
56+ selectedTypeIds = selectedTypeIds,
57+ searchText = searchText,
58+ isSearching = isSearching,
59+ )
60+ }
2661
2762 val items = typeTags.await().items +
2863 DividerViewData (1 ) +
29- recordTags.await().items
64+ recordTags.await().items +
65+ getBottomEmptySpace(navBarHeightDp)
66+
3067 val showHint = typeTags.await().showHint ||
3168 recordTags.await().showHint
3269
@@ -36,49 +73,176 @@ class CategoriesViewDataInteractor @Inject constructor(
3673 )
3774 }
3875
39- private suspend fun getRecordTypeTagViewData (): CategoriesViewData {
40- val categories = categoryInteractor.getAll()
41- val isDarkTheme = prefsInteractor.getDarkMode()
76+ private suspend fun getCategoriesViewData (
77+ selectedTypeIds : List <Long >,
78+ searchText : String ,
79+ isSearching : Boolean ,
80+ ): CategoriesViewData = withContext(Dispatchers .Default ) {
4281 val result: MutableList <ViewHolderType > = mutableListOf ()
4382
44- categoryViewDataMapper.mapToCategoryHint().let (result::add)
83+ val isDarkTheme = prefsInteractor.getDarkMode()
84+ val categories = categoryInteractor.getAll()
85+ val filteredCategories = filterCategories(
86+ selectedTypeIds = selectedTypeIds,
87+ categories = categories,
88+ ).let {
89+ searchCategories(
90+ categories = it,
91+ isSearching = isSearching,
92+ searchText = searchText,
93+ )
94+ }
4595
46- categories.map { category ->
96+ result + = if (filteredCategories.isEmpty() && isSearching) {
97+ mapSearchEmpty()
98+ } else {
99+ categoryViewDataMapper.mapToCategoryHint()
100+ }
101+
102+ result + = filteredCategories.map { category ->
47103 categoryViewDataMapper.mapCategory(
48104 category = category,
49105 isDarkTheme = isDarkTheme,
50106 )
51- }. let (result::addAll)
107+ }
52108
53- categoryViewDataMapper.mapToTypeTagAddItem(isDarkTheme). let (result::add )
109+ result + = categoryViewDataMapper.mapToTypeTagAddItem(isDarkTheme)
54110
55- return CategoriesViewData (
111+ return @withContext CategoriesViewData (
56112 items = result,
57113 showHint = categories.isNotEmpty(),
58114 )
59115 }
60116
61- private suspend fun getRecordTagViewData (): CategoriesViewData {
62- val tags = recordTagInteractor.getAll().filterNot { it.archived }
63- val types = recordTypeInteractor.getAll().associateBy { it.id }
64- val isDarkTheme = prefsInteractor.getDarkMode()
117+ private suspend fun getRecordTagViewData (
118+ selectedTypeIds : List <Long >,
119+ searchText : String ,
120+ isSearching : Boolean ,
121+ ): CategoriesViewData = withContext(Dispatchers .Default ) {
65122 val result: MutableList <ViewHolderType > = mutableListOf ()
66123
67- categoryViewDataMapper.mapToRecordTagHint().let (result::add)
124+ val isDarkTheme = prefsInteractor.getDarkMode()
125+ val tags = recordTagInteractor.getAll().filterNot { it.archived }
126+ val types = recordTypeInteractor.getAll()
127+ val typesMap = types.associateBy(RecordType ::id)
128+ val filteredTags = filterTags(
129+ selectedTypeIds = selectedTypeIds,
130+ tags = tags,
131+ types = types,
132+ ).let {
133+ searchTags(
134+ tags = it,
135+ isSearching = isSearching,
136+ searchText = searchText,
137+ )
138+ }
139+
140+ result + = if (filteredTags.isEmpty() && isSearching) {
141+ mapSearchEmpty()
142+ } else {
143+ categoryViewDataMapper.mapToRecordTagHint()
144+ }
68145
69- tags .map { tag ->
146+ result + = filteredTags .map { tag ->
70147 categoryViewDataMapper.mapRecordTag(
71148 tag = tag,
72- type = types [tag.iconColorSource],
149+ type = typesMap [tag.iconColorSource],
73150 isDarkTheme = isDarkTheme,
74151 )
75- }. let (result::addAll)
152+ }
76153
77- categoryViewDataMapper.mapToRecordTagAddItem(isDarkTheme). let (result::add )
154+ result + = categoryViewDataMapper.mapToRecordTagAddItem(isDarkTheme)
78155
79- return CategoriesViewData (
156+ return @withContext CategoriesViewData (
80157 items = result,
81158 showHint = tags.isNotEmpty(),
82159 )
83160 }
161+
162+ private fun getBottomEmptySpace (
163+ navBarHeightDp : Int ,
164+ ): ViewHolderType {
165+ val optionsButtonHeight = resourceRepo.getDimenInDp(R .dimen.button_height)
166+ val size = optionsButtonHeight + navBarHeightDp
167+ return EmptySpaceViewData (
168+ id = " categories_bottom_space" .hashCode().toLong(),
169+ height = EmptySpaceViewData .ViewDimension .ExactSizeDp (size),
170+ wrapBefore = true ,
171+ )
172+ }
173+
174+ private fun mapSearchEmpty (): ViewHolderType {
175+ return HintViewData (text = resourceRepo.getString(R .string.widget_load_error))
176+ }
177+
178+ private suspend fun filterCategories (
179+ selectedTypeIds : List <Long >,
180+ categories : List <Category >,
181+ ): List <Category > {
182+ if (selectedTypeIds.isEmpty()) return categories
183+
184+ val types = recordTypeInteractor.getAll()
185+ val archivedTypeIds = types.filter { it.hidden }.map { it.id }
186+ val recordTypeCategories = recordTypeCategoryInteractor.getAll()
187+ val categoriesToTypeIds = recordTypeCategories
188+ .groupBy { it.categoryId }
189+ .mapValues { (_, value) -> value.map { it.recordTypeId } }
190+
191+ return categories.filter { category ->
192+ val assignedTypes = categoriesToTypeIds[category.id].orEmpty()
193+ // Archived is hidden.
194+ assignedTypes.any { it !in archivedTypeIds && it in selectedTypeIds }
195+ }
196+ }
197+
198+ private suspend fun filterTags (
199+ selectedTypeIds : List <Long >,
200+ tags : List <RecordTag >,
201+ types : List <RecordType >,
202+ ): List <RecordTag > {
203+ if (selectedTypeIds.isEmpty()) return tags
204+
205+ val archivedTypeIds = types.filter { it.hidden }.map { it.id }
206+ val tagsToTypes = recordTypeToTagInteractor.getAll()
207+ .groupBy { it.tagId }
208+ .mapValues { (_, value) -> value.map { it.recordTypeId } }
209+ val tagsToDefaultTypes = recordTypeToDefaultTagInteractor.getAll()
210+ .groupBy { it.tagId }
211+ .mapValues { (_, value) -> value.map { it.recordTypeId } }
212+
213+ return tags.filter { tag ->
214+ val hasIconColorSource = tag.iconColorSource in selectedTypeIds
215+ val hasAssignedType = tagsToTypes[tag.id].orEmpty()
216+ .any { it in selectedTypeIds }
217+ // Archived is hidden.
218+ val hasAssignedDefaultType = tagsToDefaultTypes[tag.id].orEmpty()
219+ .any { it !in archivedTypeIds && it in selectedTypeIds }
220+
221+ hasIconColorSource || hasAssignedType || hasAssignedDefaultType
222+ }
223+ }
224+
225+ private fun searchCategories (
226+ categories : List <Category >,
227+ isSearching : Boolean ,
228+ searchText : String ,
229+ ): List <Category > {
230+ return if (isSearching) {
231+ categories.filter { it.name.lowercase().contains(searchText) }
232+ } else {
233+ categories
234+ }
235+ }
236+
237+ private fun searchTags (
238+ tags : List <RecordTag >,
239+ isSearching : Boolean ,
240+ searchText : String ,
241+ ): List <RecordTag > {
242+ return if (isSearching) {
243+ tags.filter { it.name.lowercase().contains(searchText) }
244+ } else {
245+ tags
246+ }
247+ }
84248}
0 commit comments