refactor: introduce pagination manager for Explore (#1185)

This commit is contained in:
Diego Beraldin 2024-07-29 22:16:44 +02:00 committed by GitHub
parent 27c70317a4
commit a987560626
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 520 additions and 181 deletions

View File

@ -9,3 +9,12 @@ sealed interface SearchResult {
data class Community(val model: CommunityModel) : SearchResult
}
val SearchResult.uniqueIdentifier: String
get() =
when (this) {
is SearchResult.Post -> "post" + model.id.toString() + model.updateDate
is SearchResult.Comment -> "comment" + model.id.toString() + model.updateDate
is SearchResult.User -> "user" + model.id.toString()
is SearchResult.Community -> "community" + model.id.toString()
}

View File

@ -0,0 +1,221 @@
package com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DomainBlocklistRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.StopWordRepository
import com.github.diegoberaldin.raccoonforlemmy.core.testutils.DispatcherTestRule
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SearchResult
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.coVerifySequence
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
class DefaultExplorePaginationManagerTest {
@get:Rule
val dispatcherTestRule = DispatcherTestRule()
private val identityRepository: IdentityRepository =
mockk {
every { authToken } returns MutableStateFlow(AUTH_TOKEN)
every { cachedUser } returns UserModel(id = 1)
}
private val communityRepository: CommunityRepository = mockk(relaxUnitFun = true)
private val userRepository: UserRepository = mockk(relaxUnitFun = true)
private val accountRepository =
mockk<AccountRepository>(relaxUnitFun = true) {
coEvery { getActive() } returns null
}
private val domainBlocklistRepository =
mockk<DomainBlocklistRepository>(relaxUnitFun = true) {
coEvery { get(accountId = any()) } returns emptyList()
}
private val stopWordRepository =
mockk<StopWordRepository>(relaxUnitFun = true) {
coEvery { get(accountId = any()) } returns emptyList()
}
private val sut =
DefaultExplorePaginationManager(
identityRepository = identityRepository,
accountRepository = accountRepository,
communityRepository = communityRepository,
userRepository = userRepository,
domainBlocklistRepository = domainBlocklistRepository,
stopWordRepository = stopWordRepository,
)
@Test
fun whenReset_thenCanFetchMore() =
runTest {
val specification = ExplorePaginationSpecification()
sut.reset(specification)
assertTrue(sut.canFetchMore)
}
@Test
fun givenNoResults_whenLoadNextPage_thenResultIsAsExpected() =
runTest {
coEvery {
communityRepository.search(
query = any(),
auth = any(),
page = any(),
limit = any(),
sortType = any(),
listingType = any(),
resultType = any(),
instance = any(),
communityId = any(),
)
} returns emptyList()
val specification = ExplorePaginationSpecification()
sut.reset(specification)
val items = sut.loadNextPage()
assertTrue(items.isEmpty())
coVerify {
communityRepository.search(
auth = AUTH_TOKEN,
page = 1,
limit = 20,
listingType = specification.listingType,
sortType = specification.sortType,
communityId = null,
instance = null,
resultType = specification.resultType,
query = "",
)
}
}
@Test
fun givenResults_whenLoadNextPage_thenResultIsAsExpected() =
runTest {
val page = slot<Int>()
coEvery {
communityRepository.search(
query = any(),
auth = any(),
page = capture(page),
limit = any(),
sortType = any(),
listingType = any(),
resultType = any(),
instance = any(),
communityId = any(),
)
} answers {
val pageNumber = page.captured
if (pageNumber == 1) {
(0..<20).map { idx ->
SearchResult.Post(PostModel(id = idx.toLong()))
}
} else {
emptyList()
}
}
val specification = ExplorePaginationSpecification()
sut.reset(specification)
val items = sut.loadNextPage()
assertEquals(20, items.size)
assertTrue(sut.canFetchMore)
coVerify {
communityRepository.search(
auth = AUTH_TOKEN,
page = 1,
limit = 20,
listingType = specification.listingType,
sortType = specification.sortType,
communityId = null,
instance = null,
resultType = specification.resultType,
query = "",
)
}
}
@Test
fun givenResults_whenSecondLoadNextPage_thenResultIsAsExpected() =
runTest {
val page = slot<Int>()
coEvery {
communityRepository.search(
query = any(),
auth = any(),
page = capture(page),
limit = any(),
sortType = any(),
listingType = any(),
resultType = any(),
instance = any(),
communityId = any(),
)
} answers {
val pageNumber = page.captured
if (pageNumber == 1) {
(0..<20).map { idx ->
SearchResult.Post(PostModel(id = idx.toLong()))
}
} else {
emptyList()
}
}
val specification = ExplorePaginationSpecification()
sut.reset(specification)
sut.loadNextPage()
val items = sut.loadNextPage()
assertEquals(20, items.size)
assertFalse(sut.canFetchMore)
coVerifySequence {
communityRepository.search(
auth = AUTH_TOKEN,
page = 1,
limit = 20,
listingType = specification.listingType,
sortType = specification.sortType,
communityId = null,
instance = null,
resultType = specification.resultType,
query = "",
)
communityRepository.search(
auth = AUTH_TOKEN,
page = 2,
limit = 20,
listingType = specification.listingType,
sortType = specification.sortType,
communityId = null,
instance = null,
resultType = specification.resultType,
query = "",
)
}
}
companion object {
private const val AUTH_TOKEN = "fake-token"
}
}

View File

@ -24,7 +24,6 @@ internal class DefaultCommentPaginationManager(
dispatcher: CoroutineDispatcher = Dispatchers.IO,
) : CommentPaginationManager {
override var canFetchMore: Boolean = true
private set
private var specification: CommentPaginationSpecification? = null
private var currentPage: Int = 1

View File

@ -17,7 +17,6 @@ internal class DefaultCommunityPaginationManager(
private val communityRepository: CommunityRepository,
) : CommunityPaginationManager {
override var canFetchMore: Boolean = true
private set
override val history: MutableList<CommunityModel> = mutableListOf()
private var specification: CommunityPaginationSpecification? = null

View File

@ -0,0 +1,216 @@
package com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DomainBlocklistRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.StopWordRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SearchResult
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SearchResultType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.uniqueIdentifier
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.withContext
class DefaultExplorePaginationManager(
private val identityRepository: IdentityRepository,
private val accountRepository: AccountRepository,
private val communityRepository: CommunityRepository,
private val userRepository: UserRepository,
private val domainBlocklistRepository: DomainBlocklistRepository,
private val stopWordRepository: StopWordRepository,
) : ExplorePaginationManager {
override var canFetchMore: Boolean = true
private var specification: ExplorePaginationSpecification? = null
private var currentPage: Int = 1
private val history: MutableList<SearchResult> = mutableListOf()
private var blockedDomains: List<String>? = null
private var stopWords: List<String>? = null
override fun reset(specification: ExplorePaginationSpecification) {
this.specification = specification
canFetchMore = true
currentPage = 1
history.clear()
blockedDomains = null
stopWords = null
}
override suspend fun loadNextPage(): List<SearchResult> =
withContext(Dispatchers.IO) {
val specification = specification ?: return@withContext emptyList()
val auth = identityRepository.authToken.value.orEmpty()
val accountId = accountRepository.getActive()?.id
if (blockedDomains == null) {
blockedDomains = domainBlocklistRepository.get(accountId)
}
if (stopWords == null) {
stopWords = stopWordRepository.get(accountId)
}
val searchText = specification.query.orEmpty()
val resultType = specification.resultType
val itemList: List<SearchResult> =
communityRepository.search(
query = searchText,
auth = auth,
resultType = resultType,
page = currentPage,
listingType = specification.listingType,
sortType = specification.sortType,
instance = specification.otherInstance,
)
val additionalResolvedCommunity =
if (resultType == SearchResultType.All ||
resultType == SearchResultType.Communities &&
currentPage == 1 &&
searchText.isNotEmpty()
) {
communityRepository.getResolved(
query = searchText,
auth = auth,
)
} else {
null
}
val additionalResolvedUser =
if (resultType == SearchResultType.All ||
resultType == SearchResultType.Users &&
currentPage == 1 &&
searchText.isNotEmpty()
) {
userRepository.getResolved(
query = searchText,
auth = auth,
)
} else {
null
}
if (itemList.isNotEmpty()) {
currentPage++
}
canFetchMore = itemList.isNotEmpty()
val result =
itemList
.deduplicate()
.filterNsfw(specification.includeNsfw)
.filterDeleted()
.filterByUrlDomain()
.filterByStopWords()
.let {
when (resultType) {
SearchResultType.Communities -> {
if (additionalResolvedCommunity != null &&
it.none { r ->
r is SearchResult.Community && r.model.id == additionalResolvedCommunity.id
}
) {
it + SearchResult.Community(additionalResolvedCommunity)
} else {
it
}
}
SearchResultType.Users -> {
if (additionalResolvedUser != null &&
it.none { r ->
r is SearchResult.User && r.model.id == additionalResolvedUser.id
}
) {
it + SearchResult.User(additionalResolvedUser)
} else {
it
}
}
SearchResultType.Posts -> {
if (specification.searchPostTitleOnly && searchText.isNotEmpty()) {
// apply the more restrictive title-only search
it
.filterIsInstance<SearchResult.Post>()
.filter { r ->
r.model.title.contains(
other = searchText,
ignoreCase = true,
)
}
} else {
it
}
}
else -> it
}
}
history.addAll(result)
// returns a copy of the whole history
history.map { it }
}
private fun List<SearchResult>.deduplicate(): List<SearchResult> =
filter { c1 ->
// prevents accidental duplication
history.none { c2 -> c2.uniqueIdentifier == c1.uniqueIdentifier }
}
private fun List<SearchResult>.filterNsfw(includeNsfw: Boolean): List<SearchResult> =
if (includeNsfw) {
this
} else {
filter { res ->
when (res) {
is SearchResult.Community -> !res.model.nsfw
is SearchResult.Post -> !res.model.nsfw
is SearchResult.Comment -> true
is SearchResult.User -> true
else -> false
}
}
}
private fun List<SearchResult>.filterDeleted(): List<SearchResult> {
return filter {
when (it) {
is SearchResult.Post -> !it.model.deleted
is SearchResult.Comment -> !it.model.deleted
else -> true
}
}
}
private fun List<SearchResult>.filterByUrlDomain(): List<SearchResult> =
filter {
when (it) {
is SearchResult.Post -> {
blockedDomains?.takeIf { l -> l.isNotEmpty() }?.let { blockList ->
blockList.none { domain -> it.model.url?.contains(domain) ?: true }
} ?: true
}
else -> true
}
}
private fun List<SearchResult>.filterByStopWords(): List<SearchResult> =
filter {
when (it) {
is SearchResult.Post -> {
stopWords?.takeIf { l -> l.isNotEmpty() }?.let { stopWordList ->
stopWordList.none { domain ->
it.model.title.contains(
other = domain,
ignoreCase = true,
)
}
} ?: true
}
else -> true
}
}
}

View File

@ -35,7 +35,6 @@ internal class DefaultPostPaginationManager(
notificationCenter: NotificationCenter,
) : PostPaginationManager {
override var canFetchMore: Boolean = true
private set
override val history: MutableList<PostModel> = mutableListOf()
private var specification: PostPaginationSpecification? = null
@ -301,16 +300,15 @@ internal class DefaultPostPaginationManager(
}
}
private fun List<PostModel>.filterByUrlDomain(): List<PostModel> {
return filter { post ->
private fun List<PostModel>.filterByUrlDomain(): List<PostModel> =
filter { post ->
blockedDomains?.takeIf { it.isNotEmpty() }?.let { blockList ->
blockList.none { domain -> post.url?.contains(domain) ?: true }
} ?: true
}
}
private fun List<PostModel>.filterByStopWords(): List<PostModel> {
return filter { post ->
private fun List<PostModel>.filterByStopWords(): List<PostModel> =
filter { post ->
stopWords?.takeIf { it.isNotEmpty() }?.let { stopWordList ->
stopWordList.none { domain ->
post.title.contains(
@ -320,7 +318,6 @@ internal class DefaultPostPaginationManager(
}
} ?: true
}
}
private fun handlePostUpdate(post: PostModel) {
val index = history.indexOfFirst { it.id == post.id }.takeIf { it >= 0 } ?: return

View File

@ -0,0 +1,11 @@
package com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SearchResult
interface ExplorePaginationManager {
val canFetchMore: Boolean
fun reset(specification: ExplorePaginationSpecification)
suspend fun loadNextPage(): List<SearchResult>
}

View File

@ -0,0 +1,15 @@
package com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.ListingType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SearchResultType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
data class ExplorePaginationSpecification(
val resultType: SearchResultType = SearchResultType.Communities,
val listingType: ListingType = ListingType.All,
val sortType: SortType = SortType.Active,
val includeNsfw: Boolean = true,
val searchPostTitleOnly: Boolean = false,
val otherInstance: String? = null,
val query: String? = null,
)

View File

@ -4,9 +4,11 @@ import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.CommentPaginati
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.CommunityPaginationManager
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.DefaultCommentPaginationManager
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.DefaultCommunityPaginationManager
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.DefaultExplorePaginationManager
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.DefaultMultiCommunityPaginator
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.DefaultPostNavigationManager
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.DefaultPostPaginationManager
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.ExplorePaginationManager
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.MultiCommunityPaginator
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.PostNavigationManager
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.PostPaginationManager
@ -51,4 +53,14 @@ val paginationModule =
communityRepository = get(),
)
}
factory<ExplorePaginationManager> {
DefaultExplorePaginationManager(
identityRepository = get(),
accountRepository = get(),
communityRepository = get(),
userRepository = get(),
domainBlocklistRepository = get(),
stopWordRepository = get(),
)
}
}

View File

@ -82,6 +82,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SearchResult
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.readableHandle
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toInt
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.uniqueIdentifier
import com.github.diegoberaldin.raccoonforlemmy.unit.explore.components.ExploreTopBar
import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.zoomableimage.ZoomableImageScreen
@ -307,7 +308,7 @@ class ExploreScreen(
}
}
}
items(uiState.results, key = { getItemKey(it) }) { result ->
items(uiState.results, key = { it.uniqueIdentifier }) { result ->
when (result) {
is SearchResult.Community -> {
CommunityItem(

View File

@ -1,14 +1,14 @@
package com.github.diegoberaldin.raccoonforlemmy.unit.explore
import cafe.adriel.voyager.core.model.screenModelScope
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.ExplorePaginationManager
import com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination.ExplorePaginationSpecification
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.repository.ThemeRepository
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DomainBlocklistRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.StopWordRepository
import com.github.diegoberaldin.raccoonforlemmy.core.utils.imagepreload.ImagePreloadManager
import com.github.diegoberaldin.raccoonforlemmy.core.utils.vibrate.HapticFeedback
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
@ -19,6 +19,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SearchResult
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SearchResultType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.imageUrl
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toListingType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toSearchResultType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toSortType
@ -27,7 +28,6 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.Communit
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.GetSortTypesUseCase
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyValueCache
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
@ -45,7 +45,7 @@ class ExploreViewModel(
private val apiConfigRepository: ApiConfigurationRepository,
private val identityRepository: IdentityRepository,
private val communityRepository: CommunityRepository,
private val userRepository: UserRepository,
private val paginationManager: ExplorePaginationManager,
private val postRepository: PostRepository,
private val commentRepository: CommentRepository,
private val themeRepository: ThemeRepository,
@ -53,15 +53,12 @@ class ExploreViewModel(
private val notificationCenter: NotificationCenter,
private val hapticFeedback: HapticFeedback,
private val getSortTypesUseCase: GetSortTypesUseCase,
private val imagePreloadManager: ImagePreloadManager,
private val lemmyValueCache: LemmyValueCache,
private val accountRepository: AccountRepository,
private val domainBlocklistRepository: DomainBlocklistRepository,
private val stopWordRepository: StopWordRepository,
) : DefaultMviModel<ExploreMviModel.Intent, ExploreMviModel.UiState, ExploreMviModel.Effect>(
initialState = ExploreMviModel.UiState(),
),
ExploreMviModel {
private var currentPage: Int = 1
private val isOnOtherInstance: Boolean get() = otherInstance.isNotEmpty()
private val notificationEventKey: String
get() =
@ -72,8 +69,6 @@ class ExploreViewModel(
append(otherInstance)
}
}
private var blockedDomains: List<String>? = null
private var stopWords: List<String>? = null
init {
screenModelScope.launch {
@ -362,16 +357,23 @@ class ExploreViewModel(
}
private suspend fun refresh(initial: Boolean = false) {
currentPage = 1
val accountId = accountRepository.getActive()?.id
blockedDomains = domainBlocklistRepository.get(accountId)
stopWords = stopWordRepository.get(accountId)
paginationManager.reset(
ExplorePaginationSpecification(
listingType = uiState.value.listingType,
sortType = uiState.value.sortType,
query = uiState.value.searchText,
includeNsfw = settingsRepository.currentSettings.value.includeNsfw,
searchPostTitleOnly = settingsRepository.currentSettings.value.searchPostTitleOnly,
otherInstance = otherInstance,
resultType = uiState.value.resultType,
),
)
updateState {
it.copy(
initial = initial,
canFetchMore = true,
refreshing = !initial,
loading = false,
initial = initial,
)
}
loadNextPage()
@ -384,161 +386,28 @@ class ExploreViewModel(
return
}
updateState { it.copy(loading = true) }
val searchText = uiState.value.searchText
val auth = identityRepository.authToken.value
val refreshing = currentState.refreshing
val listingType = currentState.listingType
val sortType = currentState.sortType
val resultType = currentState.resultType
val settings = settingsRepository.currentSettings.value
val itemList =
communityRepository.search(
query = searchText,
auth = auth,
resultType = resultType,
page = currentPage,
listingType = listingType,
sortType = sortType,
instance = otherInstance,
)
val additionalResolvedCommunity =
if (resultType == SearchResultType.All ||
resultType == SearchResultType.Communities &&
currentPage == 1 &&
searchText.isNotEmpty()
) {
communityRepository.getResolved(
query = searchText,
auth = auth,
)
} else {
null
val results = paginationManager.loadNextPage()
if (uiState.value.autoLoadImages) {
results.forEach { res ->
(res as? SearchResult.Post)?.model?.imageUrl?.takeIf { it.isNotEmpty() }
?.also { url ->
imagePreloadManager.preload(url)
}
}
val additionalResolvedUser =
if (resultType == SearchResultType.All ||
resultType == SearchResultType.Users &&
currentPage == 1 &&
searchText.isNotEmpty()
) {
userRepository.getResolved(
query = searchText,
auth = auth,
)
} else {
null
}
if (itemList.isNotEmpty()) {
currentPage++
}
val itemsToAdd =
itemList
.filter { item ->
if (settings.includeNsfw) {
true
} else {
isSafeForWork(item)
}
}.filter {
when (it) {
is SearchResult.Post -> {
val filteredByDomain =
blockedDomains?.takeIf { l -> l.isNotEmpty() }?.let { blockList ->
blockList.none { domain ->
it.model.url?.contains(domain) ?: true
}
} ?: true
val filteredByStopWord =
stopWords?.takeIf { l -> l.isNotEmpty() }?.let { stopWordList ->
stopWordList.none { domain ->
it.model.title.contains(other = domain, ignoreCase = true)
}
} ?: true
filteredByDomain && filteredByStopWord
}
else -> true
}
}
.let {
when (resultType) {
SearchResultType.Communities -> {
if (additionalResolvedCommunity != null &&
it.none { r ->
r is SearchResult.Community && r.model.id == additionalResolvedCommunity.id
}
) {
it + SearchResult.Community(additionalResolvedCommunity)
} else {
it
}
}
SearchResultType.Users -> {
if (additionalResolvedUser != null &&
it.none { r ->
r is SearchResult.User && r.model.id == additionalResolvedUser.id
}
) {
it + SearchResult.User(additionalResolvedUser)
} else {
it
}
}
SearchResultType.Posts -> {
if (settings.searchPostTitleOnly && searchText.isNotEmpty()) {
// apply the more restrictive title-only search
it
.filterIsInstance<SearchResult.Post>()
.filter { r ->
r.model.title.contains(
other = searchText,
ignoreCase = true,
)
}
} else {
it
}
}
else -> it
}
}.filter { item ->
if (refreshing) {
true
} else {
// prevents accidental duplication
currentState.results.none { other -> getItemKey(item) == getItemKey(other) }
}
}
updateState {
val newItems =
if (refreshing) {
itemsToAdd
} else {
it.results + itemsToAdd
}
it.copy(
results = newItems,
results = results,
loading = false,
canFetchMore = itemList.isNotEmpty(),
canFetchMore = paginationManager.canFetchMore,
refreshing = false,
)
}
}
private fun isSafeForWork(element: SearchResult): Boolean =
when (element) {
is SearchResult.Community -> !element.model.nsfw
is SearchResult.Post -> !element.model.nsfw
is SearchResult.Comment -> true
is SearchResult.User -> true
else -> false
}
private fun handleLogout() {
screenModelScope.launch {
currentPage = 1
updateState {
it.copy(
listingType = ListingType.Local,
@ -914,11 +783,3 @@ class ExploreViewModel(
}
}
}
internal fun getItemKey(result: SearchResult): String =
when (result) {
is SearchResult.Post -> "post" + result.model.id.toString() + result.model.updateDate
is SearchResult.Comment -> "comment" + result.model.id.toString() + result.model.updateDate
is SearchResult.User -> "user" + result.model.id.toString()
is SearchResult.Community -> "community" + result.model.id.toString()
}

View File

@ -11,9 +11,8 @@ val exploreModule =
otherInstance = params[0],
apiConfigRepository = get(),
identityRepository = get(),
accountRepository = get(),
paginationManager = get(),
communityRepository = get(),
userRepository = get(),
postRepository = get(),
commentRepository = get(),
themeRepository = get(),
@ -22,8 +21,7 @@ val exploreModule =
hapticFeedback = get(),
getSortTypesUseCase = get(),
lemmyValueCache = get(),
domainBlocklistRepository = get(),
stopWordRepository = get(),
imagePreloadManager = get(),
)
}
}