Skip to content

Commit 5e780e9

Browse files
dogiOkuro3499
andauthored
resources: smoother repository base recycling (fixes #12171) (#12144)
Co-authored-by: Gideon Okuro <gideonollonde@gmail.com>
1 parent 83b6d82 commit 5e780e9

7 files changed

Lines changed: 147 additions & 70 deletions

File tree

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ android {
1212
applicationId "org.ole.planet.myplanet"
1313
minSdk = 26
1414
targetSdk = 36
15-
versionCode = 4971
16-
versionName = "0.49.71"
15+
versionCode = 4972
16+
versionName = "0.49.72"
1717
ndkVersion = '26.3.11579264'
1818
vectorDrawables.useSupportLibrary = true
1919
}

app/src/main/java/org/ole/planet/myplanet/base/BaseRecyclerFragment.kt

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -224,37 +224,40 @@ abstract class BaseRecyclerFragment<LI> : BaseRecyclerParentFragment<Any?>(), On
224224
}
225225
}
226226

227-
private fun <LI : RealmModel> getData(s: String, c: Class<LI>): List<LI> {
228-
val query = requireRealmInstance().where(c)
229-
if (c == RealmMyLibrary::class.java) {
230-
query.equalTo("isPrivate", false)
227+
fun normalizeText(str: String): String {
228+
return Normalizer.normalize(str.lowercase(Locale.getDefault()), Normalizer.Form.NFD)
229+
.replace(DIACRITICS_REGEX, "")
230+
}
231+
232+
suspend fun filterCourseByTag(s: String, tags: List<RealmTag>): List<RealmMyCourse> {
233+
if (tags.isEmpty() && s.isEmpty()) {
234+
return applyCourseFilter(filterRealmMyCourseList(getList(RealmMyCourse::class.java) as List<RealmMyCourse>))
231235
}
232-
if (s.isEmpty()) return query.findAll()
233-
234-
val queryParts = s.split(" ").filterNot { it.isEmpty() }
235-
val normalizedQueryParts = queryParts.map { org.ole.planet.myplanet.utils.Utilities.normalizeText(it) }
236-
val data: RealmResults<LI> = query.findAll()
237-
val normalizedQuery = org.ole.planet.myplanet.utils.Utilities.normalizeText(s)
238-
val startsWithQuery = mutableListOf<LI>()
239-
val containsQuery = mutableListOf<LI>()
240-
241-
for (item in data) {
242-
val title = getTitle(item, c)?.let { org.ole.planet.myplanet.utils.Utilities.normalizeText(it) } ?: continue
243-
244-
if (title.startsWith(normalizedQuery, ignoreCase = true)) {
245-
startsWithQuery.add(item)
246-
} else if (normalizedQueryParts.all { title.contains(it, ignoreCase = true) }) {
247-
containsQuery.add(item)
248-
}
236+
var list = coursesRepository.search(s)
237+
list = if (isMyCourseLib) {
238+
coursesRepository.getMyCourses(model?.id, list)
239+
} else {
240+
RealmMyCourse.getAllCourses(model?.id, list)
241+
}
242+
if (tags.isEmpty()) {
243+
return list
249244
}
250-
return startsWithQuery + containsQuery
251-
}
252245

253-
private fun <LI : RealmModel> getTitle(item: LI, c: Class<LI>): String? {
254-
return when {
255-
c.isAssignableFrom(RealmMyLibrary::class.java) -> (item as RealmMyLibrary).title
256-
else -> (item as RealmMyCourse).courseTitle
246+
val tagIds = tags.mapNotNull { it.id }.toTypedArray()
247+
val linkedCourseIds = requireRealmInstance().where(RealmTag::class.java)
248+
.equalTo("db", "courses")
249+
.`in`("tagId", tagIds)
250+
.findAll()
251+
.mapNotNull { it.linkId }
252+
.toSet()
253+
254+
val courses = RealmList<RealmMyCourse>()
255+
list.forEach { course ->
256+
if (linkedCourseIds.contains(course.courseId) && !courses.contains(course)) {
257+
courses.add(course)
258+
}
257259
}
260+
return applyCourseFilter(courses)
258261
}
259262

260263
private fun filterRealmMyCourseList(items: List<Any?>): List<RealmMyCourse> {
@@ -332,6 +335,8 @@ abstract class BaseRecyclerFragment<LI> : BaseRecyclerParentFragment<Any?>(), On
332335
}
333336

334337
companion object {
338+
private val DIACRITICS_REGEX = Regex("\\p{InCombiningDiacriticalMarks}+")
339+
335340
private val noDataMessages = mapOf(
336341
"courses" to R.string.no_courses,
337342
"resources" to R.string.no_resources,

app/src/main/java/org/ole/planet/myplanet/repository/CoursesRepository.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface CoursesRepository {
2727
suspend fun joinCourse(courseId: String, userId: String): Result<Unit>
2828
suspend fun leaveCourse(courseId: String, userId: String): Result<Unit>
2929
suspend fun isMyCourse(userId: String?, courseId: String?): Boolean
30+
suspend fun search(query: String): List<RealmMyCourse>
3031
suspend fun filterCourses(
3132
searchText: String,
3233
gradeLevel: String,

app/src/main/java/org/ole/planet/myplanet/repository/CoursesRepositoryImpl.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package org.ole.planet.myplanet.repository
33
import com.google.gson.JsonArray
44
import java.util.Calendar
55
import java.util.UUID
6+
import java.text.Normalizer
7+
import java.util.Locale
68
import javax.inject.Inject
79
import kotlinx.coroutines.flow.Flow
810
import kotlinx.coroutines.withContext
@@ -181,6 +183,40 @@ class CoursesRepositoryImpl @Inject constructor(
181183
}
182184
}
183185

186+
private val DIACRITICS_REGEX = Regex("\\p{InCombiningDiacriticalMarks}+")
187+
188+
private fun normalizeText(str: String): String {
189+
return Normalizer.normalize(str.lowercase(Locale.getDefault()), Normalizer.Form.NFD)
190+
.replace(DIACRITICS_REGEX, "")
191+
}
192+
193+
override suspend fun search(query: String): List<RealmMyCourse> {
194+
return withRealm { realm ->
195+
val queryObj = realm.where(RealmMyCourse::class.java)
196+
if (query.isEmpty()) {
197+
return@withRealm realm.copyFromRealm(queryObj.findAll())
198+
}
199+
200+
val queryParts = query.split(" ").filterNot { it.isEmpty() }
201+
val normalizedQueryParts = queryParts.map { normalizeText(it) }
202+
val data = queryObj.findAll()
203+
val normalizedQuery = normalizeText(query)
204+
val startsWithQuery = mutableListOf<RealmMyCourse>()
205+
val containsQuery = mutableListOf<RealmMyCourse>()
206+
207+
for (item in data) {
208+
val title = item.courseTitle?.let { normalizeText(it) } ?: continue
209+
210+
if (title.startsWith(normalizedQuery, ignoreCase = true)) {
211+
startsWithQuery.add(item)
212+
} else if (normalizedQueryParts.all { title.contains(it, ignoreCase = true) }) {
213+
containsQuery.add(item)
214+
}
215+
}
216+
realm.copyFromRealm(startsWithQuery + containsQuery)
217+
}
218+
}
219+
184220
override suspend fun filterCourses(
185221
searchText: String,
186222
gradeLevel: String,

app/src/main/java/org/ole/planet/myplanet/repository/ResourcesRepository.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interface ResourcesRepository {
3131
suspend fun getAllLibraries(): List<RealmMyLibrary>
3232
suspend fun getAllLibraryItems(): List<RealmMyLibrary>
3333
suspend fun getLibraryItemById(id: String): RealmMyLibrary?
34+
suspend fun search(query: String, isMyCourseLib: Boolean, userId: String?): List<RealmMyLibrary>
3435
suspend fun getLibraryItemByResourceId(resourceId: String): RealmMyLibrary?
3536
suspend fun getLibraryItemsByIds(ids: Collection<String>): List<RealmMyLibrary>
3637
suspend fun getLibraryItemsByLocalAddress(localAddress: String): List<RealmMyLibrary>

app/src/main/java/org/ole/planet/myplanet/repository/ResourcesRepositoryImpl.kt

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import com.google.gson.JsonObject
88
import dagger.hilt.android.qualifiers.ApplicationContext
99
import io.realm.Sort
1010
import java.io.File
11+
import java.text.Normalizer
1112
import java.util.Calendar
12-
import javax.inject.Inject
13+
import java.util.Locale
1314
import java.util.UUID
15+
import javax.inject.Inject
1416
import kotlinx.coroutines.flow.Flow
1517
import kotlinx.coroutines.flow.flowOf
1618
import kotlinx.coroutines.flow.map
@@ -107,6 +109,45 @@ class ResourcesRepositoryImpl @Inject constructor(
107109
}
108110
}
109111

112+
private val DIACRITICS_REGEX = Regex("\\p{InCombiningDiacriticalMarks}+")
113+
114+
private fun normalizeText(str: String): String {
115+
return Normalizer.normalize(str.lowercase(Locale.getDefault()), Normalizer.Form.NFD)
116+
.replace(DIACRITICS_REGEX, "")
117+
}
118+
119+
override suspend fun search(query: String, isMyCourseLib: Boolean, userId: String?): List<RealmMyLibrary> {
120+
return withRealm { realm ->
121+
val queryObj = realm.where(RealmMyLibrary::class.java).equalTo("isPrivate", false)
122+
123+
val data = if (isMyCourseLib) {
124+
queryObj.findAll().filter { it.userId?.contains(userId) == true }
125+
} else {
126+
queryObj.findAll().filter { it.userId?.contains(userId) == false }
127+
}
128+
129+
if (query.isEmpty()) {
130+
return@withRealm realm.copyFromRealm(data)
131+
}
132+
133+
val queryParts = query.split(" ").filterNot { it.isEmpty() }
134+
val normalizedQueryParts = queryParts.map { normalizeText(it) }
135+
val normalizedQuery = normalizeText(query)
136+
val startsWithQuery = mutableListOf<RealmMyLibrary>()
137+
val containsQuery = mutableListOf<RealmMyLibrary>()
138+
139+
for (item in data) {
140+
val title = item.title?.let { normalizeText(it) } ?: continue
141+
if (title.startsWith(normalizedQuery, ignoreCase = true)) {
142+
startsWithQuery.add(item)
143+
} else if (normalizedQueryParts.all { title.contains(it, ignoreCase = true) }) {
144+
containsQuery.add(item)
145+
}
146+
}
147+
realm.copyFromRealm(startsWithQuery + containsQuery)
148+
}
149+
}
150+
110151
override suspend fun getLibraryItemById(id: String): RealmMyLibrary? {
111152
return findByField(RealmMyLibrary::class.java, "id", id, true)
112153
}

app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourcesFragment.kt

Lines changed: 33 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -491,10 +491,12 @@ class ResourcesFragment : BaseRecyclerFragment<RealmMyLibrary?>(), OnLibraryItem
491491
mediums.clear()
492492
subjects.clear()
493493
languages.clear()
494-
adapterLibrary.setLibraryList(applyFilter(filterLocalLibraryByTag("", searchTags)).map { it.toResourceItem() }) {
495-
recyclerView.scrollToPosition(0)
494+
viewLifecycleOwner.lifecycleScope.launch {
495+
adapterLibrary.setLibraryList(applyFilter(filterLocalLibraryByTag("", searchTags)).map { it.toResourceItem() }) {
496+
recyclerView.scrollToPosition(0)
497+
}
498+
showNoData(tvMessage, adapterLibrary.itemCount, "resources")
496499
}
497-
showNoData(tvMessage, adapterLibrary.itemCount, "resources")
498500
}
499501
}
500502

@@ -529,11 +531,13 @@ class ResourcesFragment : BaseRecyclerFragment<RealmMyLibrary?>(), OnLibraryItem
529531
chipCloud.setDeleteListener(this)
530532
if (!searchTags.any { it.name == tag.name }) searchTags.add(tag)
531533
chipCloud.addChips(searchTags)
532-
adapterLibrary.setLibraryList(applyFilter(filterLocalLibraryByTag(etSearch.text.toString(), searchTags)).map { it.toResourceItem() }) {
533-
recyclerView.scrollToPosition(0)
534+
viewLifecycleOwner.lifecycleScope.launch {
535+
adapterLibrary.setLibraryList(applyFilter(filterLocalLibraryByTag(etSearch.text.toString(), searchTags)).map { it.toResourceItem() }) {
536+
recyclerView.scrollToPosition(0)
537+
}
538+
showTagText(searchTags, tvSelected)
539+
showNoData(tvMessage, adapterLibrary.itemCount, "resources")
534540
}
535-
showTagText(searchTags, tvSelected)
536-
showNoData(tvMessage, adapterLibrary.itemCount, "resources")
537541
}
538542

539543
override fun onTagSelected(tag: RealmTag) {
@@ -542,19 +546,23 @@ class ResourcesFragment : BaseRecyclerFragment<RealmMyLibrary?>(), OnLibraryItem
542546
li.add(tag)
543547
searchTags = li
544548
tvSelected.text = getString(R.string.tag_selected, tag.name)
545-
adapterLibrary.setLibraryList(applyFilter(filterLocalLibraryByTag(etSearch.text.toString(), li)).map { it.toResourceItem() }) {
546-
recyclerView.scrollToPosition(0)
549+
viewLifecycleOwner.lifecycleScope.launch {
550+
adapterLibrary.setLibraryList(applyFilter(filterLocalLibraryByTag(etSearch.text.toString(), li)).map { it.toResourceItem() }) {
551+
recyclerView.scrollToPosition(0)
552+
}
553+
showNoData(tvMessage, adapterLibrary.itemCount, "resources")
547554
}
548-
showNoData(tvMessage, adapterLibrary.itemCount, "resources")
549555
}
550556

551557
override fun onOkClicked(list: List<RealmTag>?) {
552558
if (list?.isEmpty() == true) {
553559
searchTags.clear()
554-
adapterLibrary.setLibraryList(applyFilter(filterLocalLibraryByTag(etSearch.text.toString(), searchTags)).map { it.toResourceItem() }) {
555-
recyclerView.scrollToPosition(0)
560+
viewLifecycleOwner.lifecycleScope.launch {
561+
adapterLibrary.setLibraryList(applyFilter(filterLocalLibraryByTag(etSearch.text.toString(), searchTags)).map { it.toResourceItem() }) {
562+
recyclerView.scrollToPosition(0)
563+
}
564+
showNoData(tvMessage, adapterLibrary.itemCount, "resources")
556565
}
557-
showNoData(tvMessage, adapterLibrary.itemCount, "resources")
558566
} else {
559567
for (tag in list ?: emptyList()) {
560568
onTagClicked(tag)
@@ -575,21 +583,25 @@ class ResourcesFragment : BaseRecyclerFragment<RealmMyLibrary?>(), OnLibraryItem
575583

576584
override fun chipDeleted(i: Int, s: String) {
577585
searchTags.removeAt(i)
578-
adapterLibrary.setLibraryList(applyFilter(filterLocalLibraryByTag(etSearch.text.toString(), searchTags)).map { it.toResourceItem() }) {
579-
recyclerView.scrollToPosition(0)
586+
viewLifecycleOwner.lifecycleScope.launch {
587+
adapterLibrary.setLibraryList(applyFilter(filterLocalLibraryByTag(etSearch.text.toString(), searchTags)).map { it.toResourceItem() }) {
588+
recyclerView.scrollToPosition(0)
589+
}
590+
showNoData(tvMessage, adapterLibrary.itemCount, "resources")
580591
}
581-
showNoData(tvMessage, adapterLibrary.itemCount, "resources")
582592
}
583593

584594
override fun filter(subjects: MutableSet<String>, languages: MutableSet<String>, mediums: MutableSet<String>, levels: MutableSet<String>) {
585595
this.subjects = subjects
586596
this.languages = languages
587597
this.mediums = mediums
588598
this.levels = levels
589-
adapterLibrary.setLibraryList(applyFilter(filterLocalLibraryByTag(etSearch.text.toString().trim { it <= ' ' }, searchTags)).map { it.toResourceItem() }) {
590-
recyclerView.scrollToPosition(0)
599+
viewLifecycleOwner.lifecycleScope.launch {
600+
adapterLibrary.setLibraryList(applyFilter(filterLocalLibraryByTag(etSearch.text.toString().trim { it <= ' ' }, searchTags)).map { it.toResourceItem() }) {
601+
recyclerView.scrollToPosition(0)
602+
}
603+
showNoData(tvMessage, adapterLibrary.itemCount, "resources")
591604
}
592-
showNoData(tvMessage, adapterLibrary.itemCount, "resources")
593605
}
594606

595607
override suspend fun getData(): Map<String, Set<String>> {
@@ -714,27 +726,8 @@ class ResourcesFragment : BaseRecyclerFragment<RealmMyLibrary?>(), OnLibraryItem
714726
return if (::recyclerView.isInitialized) recyclerView else null
715727
}
716728

717-
private fun filterLocalLibraryByTag(s: String, tags: List<RealmTag>): List<RealmMyLibrary> {
718-
val normalizedSearchTerm = org.ole.planet.myplanet.utils.Utilities.normalizeText(s)
719-
720-
var filteredList = if (s.isEmpty()) {
721-
allLibraryItems
722-
} else {
723-
val queryParts = s.split(" ").filterNot { it.isEmpty() }
724-
val normalizedQueryParts = queryParts.map { org.ole.planet.myplanet.utils.Utilities.normalizeText(it) }
725-
val startsWithQuery = mutableListOf<RealmMyLibrary>()
726-
val containsQuery = mutableListOf<RealmMyLibrary>()
727-
728-
for (item in allLibraryItems) {
729-
val title = item.title?.let { org.ole.planet.myplanet.utils.Utilities.normalizeText(it) } ?: continue
730-
if (title.startsWith(normalizedSearchTerm, ignoreCase = true)) {
731-
startsWithQuery.add(item)
732-
} else if (normalizedQueryParts.all { title.contains(it, ignoreCase = true) }) {
733-
containsQuery.add(item)
734-
}
735-
}
736-
startsWithQuery + containsQuery
737-
}
729+
private suspend fun filterLocalLibraryByTag(s: String, tags: List<RealmTag>): List<RealmMyLibrary> {
730+
var filteredList = resourcesRepository.search(s, isMyCourseLib, model?.id)
738731

739732
if (tags.isNotEmpty()) {
740733
filteredList = filteredList.filter { library ->

0 commit comments

Comments
 (0)