mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-08 19:58:43 +01:00
feat: URL based filter for posts (#1177)
This commit is contained in:
parent
40d53b0f05
commit
fed21d7fe7
@ -4,6 +4,7 @@ import androidx.compose.foundation.gestures.DraggableState
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.draggable
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ScrollableTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
@ -13,11 +14,13 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
|
||||
@Composable
|
||||
fun SectionSelector(
|
||||
modifier: Modifier = Modifier,
|
||||
draggable: Boolean = true,
|
||||
scrollable: Boolean = false,
|
||||
titles: List<String> = emptyList(),
|
||||
currentSection: Int,
|
||||
onSectionSelected: (Int) -> Unit,
|
||||
@ -29,14 +32,7 @@ fun SectionSelector(
|
||||
isTowardsStart = delta > 0
|
||||
}
|
||||
}
|
||||
TabRow(
|
||||
modifier = modifier,
|
||||
selectedTabIndex = currentSection,
|
||||
tabs = {
|
||||
titles.forEachIndexed { i, title ->
|
||||
Tab(
|
||||
modifier =
|
||||
Modifier.then(
|
||||
val draggableModifier =
|
||||
if (draggable) {
|
||||
Modifier.draggable(
|
||||
state = draggableState,
|
||||
@ -45,14 +41,49 @@ fun SectionSelector(
|
||||
if (isTowardsStart) {
|
||||
onSectionSelected((currentSection - 1).coerceAtLeast(0))
|
||||
} else {
|
||||
onSectionSelected((currentSection + 1).coerceAtMost(titles.lastIndex))
|
||||
onSectionSelected(
|
||||
(currentSection + 1).coerceAtMost(
|
||||
titles.lastIndex,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
if (scrollable) {
|
||||
ScrollableTabRow(
|
||||
modifier = modifier,
|
||||
selectedTabIndex = currentSection,
|
||||
edgePadding = Spacing.xs,
|
||||
tabs = {
|
||||
titles.forEachIndexed { i, title ->
|
||||
Tab(
|
||||
modifier = draggableModifier,
|
||||
selected = i == currentSection,
|
||||
text = {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
},
|
||||
),
|
||||
onClick = {
|
||||
onSectionSelected(i)
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
TabRow(
|
||||
modifier = modifier,
|
||||
selectedTabIndex = currentSection,
|
||||
tabs = {
|
||||
titles.forEachIndexed { i, title ->
|
||||
Tab(
|
||||
modifier = draggableModifier,
|
||||
selected = i == currentSection,
|
||||
text = {
|
||||
Text(
|
||||
@ -69,3 +100,4 @@ fun SectionSelector(
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -436,4 +436,6 @@ internal open class DefaultStrings : Strings {
|
||||
override val messageAuthIssueSegue1 = "force refresh"
|
||||
override val messageAuthIssueSegue2 = "log in again"
|
||||
override val messageAuthIssueSegue3 = "clear the application data"
|
||||
override val settingsManageBanSectionDomains = "Domains"
|
||||
override val settingsManageBanDomainPlaceholder = "Substring of URL to exclude"
|
||||
}
|
||||
|
@ -450,4 +450,6 @@ internal val ItStrings =
|
||||
override val messageAuthIssueSegue1 = "forzare l\'aggiornamento"
|
||||
override val messageAuthIssueSegue2 = "effettuare nuovamente l\'accesso"
|
||||
override val messageAuthIssueSegue3 = " cancellare i dati dell\'applicazione"
|
||||
override val settingsManageBanSectionDomains = "Domini"
|
||||
override val settingsManageBanDomainPlaceholder = "Sottostringa URL da escludere"
|
||||
}
|
||||
|
@ -433,6 +433,8 @@ interface Strings {
|
||||
val messageAuthIssueSegue1: String
|
||||
val messageAuthIssueSegue2: String
|
||||
val messageAuthIssueSegue3: String
|
||||
val settingsManageBanSectionDomains: String
|
||||
val settingsManageBanDomainPlaceholder: String
|
||||
}
|
||||
|
||||
object Locales {
|
||||
|
@ -29,7 +29,7 @@ class DefaultBottomNavItemsRepositoryTest {
|
||||
|
||||
assertEquals(BottomNavItemsRepository.DEFAULT_ITEMS, res)
|
||||
coVerify {
|
||||
keyStore.get("BottomNavItemsRepository.items", emptyList())
|
||||
keyStore.get("$KEY_PREFIX.items", emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ class DefaultBottomNavItemsRepositoryTest {
|
||||
|
||||
assertEquals(ITEMS, res)
|
||||
coVerify {
|
||||
keyStore.get("BottomNavItemsRepository.items", emptyList())
|
||||
keyStore.get("$KEY_PREFIX.items", emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ class DefaultBottomNavItemsRepositoryTest {
|
||||
|
||||
assertEquals(BottomNavItemsRepository.DEFAULT_ITEMS, res)
|
||||
coVerify {
|
||||
keyStore.get("BottomNavItemsRepository.$accountId.items", emptyList())
|
||||
keyStore.get("$KEY_PREFIX.$accountId.items", emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ class DefaultBottomNavItemsRepositoryTest {
|
||||
|
||||
assertEquals(ITEMS, res)
|
||||
coVerify {
|
||||
keyStore.get("BottomNavItemsRepository.$accountId.items", emptyList())
|
||||
keyStore.get("$KEY_PREFIX.$accountId.items", emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,13 +81,13 @@ class DefaultBottomNavItemsRepositoryTest {
|
||||
val accountId = 2L
|
||||
every {
|
||||
keyStore.get(
|
||||
"BottomNavItemsRepository.$otherAccountId.items",
|
||||
"$KEY_PREFIX.$otherAccountId.items",
|
||||
any<List<String>>(),
|
||||
)
|
||||
} returns ITEMS_IDS
|
||||
every {
|
||||
keyStore.get(
|
||||
"BottomNavItemsRepository.$accountId.items",
|
||||
"$KEY_PREFIX.$accountId.items",
|
||||
any<List<String>>(),
|
||||
)
|
||||
} returns emptyList()
|
||||
@ -96,7 +96,7 @@ class DefaultBottomNavItemsRepositoryTest {
|
||||
|
||||
assertEquals(BottomNavItemsRepository.DEFAULT_ITEMS, res)
|
||||
coVerify {
|
||||
keyStore.get("BottomNavItemsRepository.$accountId.items", emptyList())
|
||||
keyStore.get("$KEY_PREFIX.$accountId.items", emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,13 +106,13 @@ class DefaultBottomNavItemsRepositoryTest {
|
||||
val otherAccountId = 1
|
||||
every {
|
||||
keyStore.get(
|
||||
"BottomNavItemsRepository.$otherAccountId.items",
|
||||
"$KEY_PREFIX.$otherAccountId.items",
|
||||
any<List<String>>(),
|
||||
)
|
||||
} returns ITEMS_IDS
|
||||
every {
|
||||
keyStore.get(
|
||||
"BottomNavItemsRepository.items",
|
||||
"$KEY_PREFIX.items",
|
||||
any<List<String>>(),
|
||||
)
|
||||
} returns emptyList()
|
||||
@ -121,7 +121,7 @@ class DefaultBottomNavItemsRepositoryTest {
|
||||
|
||||
assertEquals(BottomNavItemsRepository.DEFAULT_ITEMS, res)
|
||||
coVerify {
|
||||
keyStore.get("BottomNavItemsRepository.items", emptyList())
|
||||
keyStore.get("$KEY_PREFIX.items", emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,7 +131,7 @@ class DefaultBottomNavItemsRepositoryTest {
|
||||
sut.update(accountId = null, items = ITEMS)
|
||||
|
||||
coVerify {
|
||||
keyStore.save("BottomNavItemsRepository.items", ITEMS_IDS)
|
||||
keyStore.save("$KEY_PREFIX.items", ITEMS_IDS)
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +143,7 @@ class DefaultBottomNavItemsRepositoryTest {
|
||||
sut.update(accountId = accountId, items = ITEMS)
|
||||
|
||||
coVerify {
|
||||
keyStore.save("BottomNavItemsRepository.1.items", ITEMS_IDS)
|
||||
keyStore.save("$KEY_PREFIX.$accountId.items", ITEMS_IDS)
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,5 +157,6 @@ class DefaultBottomNavItemsRepositoryTest {
|
||||
TabNavigationSection.Inbox,
|
||||
TabNavigationSection.Settings,
|
||||
)
|
||||
private const val KEY_PREFIX = "BottomNavItemsRepository"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,159 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.testutils.DispatcherTestRule
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DefaultDomainBlocklistRepositoryTest {
|
||||
@get:Rule
|
||||
val dispatcherRule = DispatcherTestRule()
|
||||
|
||||
private val keyStore = mockk<TemporaryKeyStore>(relaxUnitFun = true)
|
||||
private val sut =
|
||||
DefaultDomainBlocklistRepository(
|
||||
keyStore = keyStore,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun givenNoData_whenGetForAnonymousUser_thenResultAndInteractionsIsAsExpected() =
|
||||
runTest {
|
||||
every { keyStore.get(any(), any<List<String>>()) } returns emptyList()
|
||||
|
||||
val res = sut.get(null)
|
||||
|
||||
assertEquals(emptyList(), res)
|
||||
coVerify {
|
||||
keyStore.get("$KEY_PREFIX.items", emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenData_whenGetForAnonymousUser_thenResultAndInteractionsIsAsExpected() =
|
||||
runTest {
|
||||
val fakeList = listOf("example.org")
|
||||
every { keyStore.get(any(), any<List<String>>()) } returns fakeList
|
||||
|
||||
val res = sut.get(null)
|
||||
|
||||
assertEquals(fakeList, res)
|
||||
coVerify {
|
||||
keyStore.get("$KEY_PREFIX.items", emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenNoData_whenGetForLoggedUser_thenResultAndInteractionsIsAsExpected() =
|
||||
runTest {
|
||||
val accountId = 1L
|
||||
every { keyStore.get(any(), any<List<String>>()) } returns emptyList()
|
||||
|
||||
val res = sut.get(accountId)
|
||||
|
||||
assertEquals(emptyList(), res)
|
||||
coVerify {
|
||||
keyStore.get("$KEY_PREFIX.$accountId.items", emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenData_whenGetForLoggedUser_thenResultAndInteractionsIsAsExpected() =
|
||||
runTest {
|
||||
val accountId = 1L
|
||||
val fakeList = listOf("example.org")
|
||||
every { keyStore.get(any(), any<List<String>>()) } returns fakeList
|
||||
|
||||
val res = sut.get(accountId)
|
||||
|
||||
assertEquals(fakeList, res)
|
||||
coVerify {
|
||||
keyStore.get("$KEY_PREFIX.$accountId.items", emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenDataForOtherUser_whenGetForLoggedAccount_thenResultAndInteractionsIsAsExpected() =
|
||||
runTest {
|
||||
val otherAccountId = 1L
|
||||
val accountId = 2L
|
||||
val fakeList = listOf("example.org")
|
||||
every {
|
||||
keyStore.get(
|
||||
"$KEY_PREFIX.$otherAccountId.items",
|
||||
any<List<String>>(),
|
||||
)
|
||||
} returns fakeList
|
||||
every {
|
||||
keyStore.get(
|
||||
"$KEY_PREFIX.$accountId.items",
|
||||
any<List<String>>(),
|
||||
)
|
||||
} returns emptyList()
|
||||
|
||||
val res = sut.get(accountId)
|
||||
|
||||
assertEquals(emptyList(), res)
|
||||
coVerify {
|
||||
keyStore.get("$KEY_PREFIX.$accountId.items", emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenDataForOtherUser_whenGetForAnonymousAccount_thenResultAndInteractionsIsAsExpected() =
|
||||
runTest {
|
||||
val otherAccountId = 1
|
||||
val fakeList = listOf("example.org")
|
||||
every {
|
||||
keyStore.get(
|
||||
"$KEY_PREFIX.$otherAccountId.items",
|
||||
any<List<String>>(),
|
||||
)
|
||||
} returns fakeList
|
||||
every {
|
||||
keyStore.get(
|
||||
"$KEY_PREFIX.items",
|
||||
any<List<String>>(),
|
||||
)
|
||||
} returns emptyList()
|
||||
|
||||
val res = sut.get(null)
|
||||
|
||||
assertEquals(emptyList(), res)
|
||||
coVerify {
|
||||
keyStore.get("$KEY_PREFIX.items", emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenUpdateAnonymousUser_thenInteractionsAreAsExpected() =
|
||||
runTest {
|
||||
val fakeList = listOf("example.org")
|
||||
sut.update(accountId = null, items = fakeList)
|
||||
|
||||
coVerify {
|
||||
keyStore.save("$KEY_PREFIX.items", fakeList)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenUpdateLoggedUser_thenInteractionsAreAsExpected() =
|
||||
runTest {
|
||||
val accountId = 1L
|
||||
val fakeList = listOf("example.org")
|
||||
|
||||
sut.update(accountId = accountId, items = fakeList)
|
||||
|
||||
coVerify {
|
||||
keyStore.save("$KEY_PREFIX.$accountId.items", fakeList)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_PREFIX = "DomainBlocklistRepository"
|
||||
}
|
||||
}
|
@ -8,11 +8,13 @@ import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.Comm
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultAccountRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultCommunityPreferredLanguageRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultCommunitySortRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultDomainBlocklistRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultDraftRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultFavoriteCommunityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultInstanceSelectionRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultMultiCommunityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultSettingsRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DomainBlocklistRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DraftRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.FavoriteCommunityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.InstanceSelectionRepository
|
||||
@ -84,4 +86,9 @@ val corePersistenceModule =
|
||||
settingsRepository = get(),
|
||||
)
|
||||
}
|
||||
single<DomainBlocklistRepository> {
|
||||
DefaultDomainBlocklistRepository(
|
||||
keyStore = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultDomainBlocklistRepository(
|
||||
private val keyStore: TemporaryKeyStore,
|
||||
) : DomainBlocklistRepository {
|
||||
override suspend fun get(accountId: Long?): List<String> =
|
||||
withContext(Dispatchers.IO) {
|
||||
val key = getKey(accountId)
|
||||
val res = keyStore.get(key, emptyList())
|
||||
res.filter { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
accountId: Long?,
|
||||
items: List<String>,
|
||||
) = withContext(Dispatchers.IO) {
|
||||
val key = getKey(accountId)
|
||||
keyStore.save(key, items)
|
||||
}
|
||||
|
||||
private fun getKey(accountId: Long?): String =
|
||||
buildString {
|
||||
append("DomainBlocklistRepository")
|
||||
if (accountId != null) {
|
||||
append(".")
|
||||
append(accountId)
|
||||
}
|
||||
append(".items")
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository
|
||||
|
||||
interface DomainBlocklistRepository {
|
||||
suspend fun get(accountId: Long?): List<String>
|
||||
|
||||
suspend fun update(
|
||||
accountId: Long?,
|
||||
items: List<String>,
|
||||
)
|
||||
}
|
@ -40,6 +40,7 @@ kotlin {
|
||||
|
||||
implementation(projects.core.notifications)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.persistence)
|
||||
|
||||
implementation(projects.domain.identity)
|
||||
implementation(projects.domain.lemmy.data)
|
||||
|
@ -2,6 +2,8 @@ package com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination
|
||||
|
||||
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.testutils.DispatcherTestRule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
|
||||
@ -45,16 +47,26 @@ class DefaultPostPaginationManagerTest {
|
||||
val slot = slot<KClass<NotificationCenterEvent>>()
|
||||
every { subscribe(capture(slot)) } answers { MutableSharedFlow() }
|
||||
}
|
||||
private val accountRepository =
|
||||
mockk<AccountRepository>(relaxUnitFun = true) {
|
||||
coEvery { getActive() } returns null
|
||||
}
|
||||
private val domainBlocklistRepository =
|
||||
mockk<DomainBlocklistRepository>(relaxUnitFun = true) {
|
||||
coEvery { get(accountId = any()) } returns emptyList<String>()
|
||||
}
|
||||
|
||||
private val sut =
|
||||
DefaultPostPaginationManager(
|
||||
identityRepository = identityRepository,
|
||||
accountRepository = accountRepository,
|
||||
postRepository = postRepository,
|
||||
communityRepository = communityRepository,
|
||||
userRepository = userRepository,
|
||||
multiCommunityPaginator = multiCommunityPaginator,
|
||||
notificationCenter = notificationCenter,
|
||||
dispatcher = dispatcherTestRule.dispatcher,
|
||||
domainBlocklistRepository = domainBlocklistRepository,
|
||||
)
|
||||
|
||||
@Test
|
||||
|
@ -2,6 +2,8 @@ package com.diegoberaldin.raccoonforlemmy.domain.lemmy.pagination
|
||||
|
||||
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.domain.identity.repository.IdentityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SearchResult
|
||||
@ -21,10 +23,12 @@ import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultPostPaginationManager(
|
||||
private val identityRepository: IdentityRepository,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val postRepository: PostRepository,
|
||||
private val communityRepository: CommunityRepository,
|
||||
private val userRepository: UserRepository,
|
||||
private val multiCommunityPaginator: MultiCommunityPaginator,
|
||||
private val domainBlocklistRepository: DomainBlocklistRepository,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||
notificationCenter: NotificationCenter,
|
||||
) : PostPaginationManager {
|
||||
@ -36,6 +40,7 @@ internal class DefaultPostPaginationManager(
|
||||
private var currentPage: Int = 1
|
||||
private var pageCursor: String? = null
|
||||
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
|
||||
private var blockedDomains: List<String>? = null
|
||||
|
||||
init {
|
||||
notificationCenter
|
||||
@ -52,6 +57,7 @@ internal class DefaultPostPaginationManager(
|
||||
currentPage = 1
|
||||
pageCursor = null
|
||||
multiCommunityPaginator.reset()
|
||||
blockedDomains = null
|
||||
(specification as? PostPaginationSpecification.MultiCommunity)?.also {
|
||||
multiCommunityPaginator.setCommunities(it.communityIds)
|
||||
}
|
||||
@ -61,6 +67,10 @@ internal class DefaultPostPaginationManager(
|
||||
withContext(Dispatchers.IO) {
|
||||
val specification = specification ?: return@withContext emptyList()
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
if (blockedDomains == null) {
|
||||
val accountId = accountRepository.getActive()?.id
|
||||
blockedDomains = domainBlocklistRepository.get(accountId)
|
||||
}
|
||||
|
||||
val result =
|
||||
when (specification) {
|
||||
@ -85,6 +95,7 @@ internal class DefaultPostPaginationManager(
|
||||
.deduplicate()
|
||||
.filterNsfw(specification.includeNsfw)
|
||||
.filterDeleted()
|
||||
.filterByUrlDomain()
|
||||
}
|
||||
|
||||
is PostPaginationSpecification.Community -> {
|
||||
@ -125,6 +136,7 @@ internal class DefaultPostPaginationManager(
|
||||
.deduplicate()
|
||||
.filterNsfw(specification.includeNsfw)
|
||||
.filterDeleted(includeCurrentCreator = true)
|
||||
.filterByUrlDomain()
|
||||
}
|
||||
|
||||
is PostPaginationSpecification.MultiCommunity -> {
|
||||
@ -138,6 +150,7 @@ internal class DefaultPostPaginationManager(
|
||||
.deduplicate()
|
||||
.filterNsfw(specification.includeNsfw)
|
||||
.filterDeleted(includeCurrentCreator = true)
|
||||
.filterByUrlDomain()
|
||||
}
|
||||
|
||||
is PostPaginationSpecification.User -> {
|
||||
@ -159,6 +172,7 @@ internal class DefaultPostPaginationManager(
|
||||
.deduplicate()
|
||||
.filterNsfw(specification.includeNsfw)
|
||||
.filterDeleted(includeCurrentCreator = specification.includeDeleted)
|
||||
.filterByUrlDomain()
|
||||
}
|
||||
|
||||
is PostPaginationSpecification.Votes -> {
|
||||
@ -181,6 +195,7 @@ internal class DefaultPostPaginationManager(
|
||||
.orEmpty()
|
||||
.deduplicate()
|
||||
.filterDeleted(includeCurrentCreator = true)
|
||||
.filterByUrlDomain()
|
||||
}
|
||||
|
||||
is PostPaginationSpecification.Saved -> {
|
||||
@ -199,6 +214,7 @@ internal class DefaultPostPaginationManager(
|
||||
.orEmpty()
|
||||
.deduplicate()
|
||||
.filterDeleted()
|
||||
.filterByUrlDomain()
|
||||
}
|
||||
|
||||
is PostPaginationSpecification.Hidden -> {
|
||||
@ -220,6 +236,7 @@ internal class DefaultPostPaginationManager(
|
||||
.orEmpty()
|
||||
.deduplicate()
|
||||
.filterDeleted()
|
||||
.filterByUrlDomain()
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,6 +250,7 @@ internal class DefaultPostPaginationManager(
|
||||
specification = specification,
|
||||
currentPage = currentPage,
|
||||
pageCursor = pageCursor,
|
||||
blockedDomains = blockedDomains,
|
||||
history = history,
|
||||
)
|
||||
|
||||
@ -242,6 +260,7 @@ internal class DefaultPostPaginationManager(
|
||||
specification = it.specification
|
||||
pageCursor = it.pageCursor
|
||||
history.clear()
|
||||
blockedDomains = it.blockedDomains
|
||||
history.addAll(it.history)
|
||||
}
|
||||
}
|
||||
@ -266,6 +285,14 @@ internal class DefaultPostPaginationManager(
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<PostModel>.filterByUrlDomain(): List<PostModel> {
|
||||
return filter { post ->
|
||||
blockedDomains?.takeIf { it.isNotEmpty() }?.let { blockList ->
|
||||
blockList.none { domain -> post.url?.contains(domain) ?: true }
|
||||
} ?: true
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePostUpdate(post: PostModel) {
|
||||
val index = history.indexOfFirst { it.id == post.id }.takeIf { it >= 0 } ?: return
|
||||
history.removeAt(index)
|
||||
|
@ -7,4 +7,5 @@ internal data class DefaultPostPaginationManagerState(
|
||||
val currentPage: Int = 1,
|
||||
val pageCursor: String? = null,
|
||||
val history: List<PostModel> = emptyList(),
|
||||
val blockedDomains: List<String>? = null,
|
||||
) : PostPaginationManagerState
|
||||
|
@ -22,11 +22,13 @@ val paginationModule =
|
||||
factory<PostPaginationManager> {
|
||||
DefaultPostPaginationManager(
|
||||
identityRepository = get(),
|
||||
accountRepository = get(),
|
||||
postRepository = get(),
|
||||
communityRepository = get(),
|
||||
userRepository = get(),
|
||||
multiCommunityPaginator = get(),
|
||||
notificationCenter = get(),
|
||||
domainBlocklistRepository = get(),
|
||||
)
|
||||
}
|
||||
factory<CommentPaginationManager> {
|
||||
|
@ -5,6 +5,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.repository.Theme
|
||||
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.utils.vibrate.HapticFeedback
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository
|
||||
@ -51,6 +53,8 @@ class ExploreViewModel(
|
||||
private val hapticFeedback: HapticFeedback,
|
||||
private val getSortTypesUseCase: GetSortTypesUseCase,
|
||||
private val lemmyValueCache: LemmyValueCache,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val domainBlocklistRepository: DomainBlocklistRepository,
|
||||
) : DefaultMviModel<ExploreMviModel.Intent, ExploreMviModel.UiState, ExploreMviModel.Effect>(
|
||||
initialState = ExploreMviModel.UiState(),
|
||||
),
|
||||
@ -66,6 +70,7 @@ class ExploreViewModel(
|
||||
append(otherInstance)
|
||||
}
|
||||
}
|
||||
private var blockedDomains: List<String>? = null
|
||||
|
||||
init {
|
||||
screenModelScope.launch {
|
||||
@ -355,6 +360,8 @@ class ExploreViewModel(
|
||||
|
||||
private suspend fun refresh(initial: Boolean = false) {
|
||||
currentPage = 1
|
||||
val accountId = accountRepository.getActive()?.id
|
||||
blockedDomains = domainBlocklistRepository.get(accountId)
|
||||
updateState {
|
||||
it.copy(
|
||||
canFetchMore = true,
|
||||
@ -427,7 +434,18 @@ class ExploreViewModel(
|
||||
} else {
|
||||
isSafeForWork(item)
|
||||
}
|
||||
}.let {
|
||||
}.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
|
||||
}
|
||||
}
|
||||
.let {
|
||||
when (resultType) {
|
||||
SearchResultType.Communities -> {
|
||||
if (additionalResolvedCommunity != null &&
|
||||
|
@ -11,6 +11,7 @@ val exploreModule =
|
||||
otherInstance = params[0],
|
||||
apiConfigRepository = get(),
|
||||
identityRepository = get(),
|
||||
accountRepository = get(),
|
||||
communityRepository = get(),
|
||||
userRepository = get(),
|
||||
postRepository = get(),
|
||||
@ -21,6 +22,7 @@ val exploreModule =
|
||||
hapticFeedback = get(),
|
||||
getSortTypesUseCase = get(),
|
||||
lemmyValueCache = get(),
|
||||
domainBlocklistRepository = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -7,12 +7,6 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.InstanceModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
|
||||
|
||||
enum class ManageBanSection {
|
||||
Users,
|
||||
Communities,
|
||||
Instances,
|
||||
}
|
||||
|
||||
@Stable
|
||||
interface ManageBanMviModel :
|
||||
MviModel<ManageBanMviModel.Intent, ManageBanMviModel.UiState, ManageBanMviModel.Effect>,
|
||||
@ -39,6 +33,14 @@ interface ManageBanMviModel :
|
||||
data class SetSearch(
|
||||
val value: String,
|
||||
) : Intent
|
||||
|
||||
data class BlockDomain(
|
||||
val value: String,
|
||||
) : Intent
|
||||
|
||||
data class UnblockDomain(
|
||||
val value: String,
|
||||
) : Intent
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
@ -50,6 +52,7 @@ interface ManageBanMviModel :
|
||||
val bannedUsers: List<UserModel> = emptyList(),
|
||||
val bannedCommunities: List<CommunityModel> = emptyList(),
|
||||
val bannedInstances: List<InstanceModel> = emptyList(),
|
||||
val blockedDomains: List<String> = emptyList(),
|
||||
val searchText: String = "",
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.unit.manageban
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@ -7,6 +10,7 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
@ -14,6 +18,8 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.AddCircle
|
||||
import androidx.compose.material.icons.filled.ExpandLess
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
@ -31,7 +37,10 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
@ -44,6 +53,8 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.koin.getScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenu
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenuItem
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SearchField
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SectionSelector
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.CommunityItem
|
||||
@ -51,15 +62,19 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.CommunityI
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.Option
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.OptionId
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.UserItem
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.di.getFabNestedScrollConnection
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.EditTextualInfoDialog
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.l10n.messages.LocalStrings
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.di.getSettingsRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
|
||||
import com.github.diegoberaldin.raccoonforlemmy.unit.manageban.components.InstanceItem
|
||||
import com.github.diegoberaldin.raccoonforlemmy.unit.manageban.components.ManageBanItem
|
||||
import com.github.diegoberaldin.raccoonforlemmy.unit.manageban.components.ManageBanItemPlaceholder
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ManageBanScreen : Screen {
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||
@ -70,6 +85,8 @@ class ManageBanScreen : Screen {
|
||||
val navigationCoordinator = remember { getNavigationCoordinator() }
|
||||
val topAppBarState = rememberTopAppBarState()
|
||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topAppBarState)
|
||||
val fabNestedScrollConnection = remember { getFabNestedScrollConnection() }
|
||||
val isFabVisible by fabNestedScrollConnection.isFabVisible.collectAsState()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val settingsRepository = remember { getSettingsRepository() }
|
||||
val settings by settingsRepository.currentSettings.collectAsState()
|
||||
@ -89,6 +106,8 @@ class ManageBanScreen : Screen {
|
||||
}
|
||||
val successMessage = LocalStrings.current.messageOperationSuccessful
|
||||
val errorMessage = LocalStrings.current.messageGenericError
|
||||
val scope = rememberCoroutineScope()
|
||||
var addDomainDialogOpen by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(model) {
|
||||
model.effects
|
||||
@ -151,6 +170,51 @@ class ManageBanScreen : Screen {
|
||||
)
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
AnimatedVisibility(
|
||||
visible = isFabVisible,
|
||||
enter =
|
||||
slideInVertically(
|
||||
initialOffsetY = { it * 2 },
|
||||
),
|
||||
exit =
|
||||
slideOutVertically(
|
||||
targetOffsetY = { it * 2 },
|
||||
),
|
||||
) {
|
||||
FloatingActionButtonMenu(
|
||||
items =
|
||||
buildList {
|
||||
this +=
|
||||
FloatingActionButtonMenuItem(
|
||||
icon = Icons.Default.ExpandLess,
|
||||
text = LocalStrings.current.actionBackToTop,
|
||||
onSelected =
|
||||
rememberCallback {
|
||||
scope.launch {
|
||||
runCatching {
|
||||
lazyListState.scrollToItem(0)
|
||||
topAppBarState.heightOffset = 0f
|
||||
topAppBarState.contentOffset = 0f
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
if (uiState.section == ManageBanSection.Domains) {
|
||||
this +=
|
||||
FloatingActionButtonMenuItem(
|
||||
icon = Icons.Default.AddCircle,
|
||||
text = LocalStrings.current.buttonAdd,
|
||||
onSelected =
|
||||
rememberCallback {
|
||||
addDomainDialogOpen = true
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
) { padding ->
|
||||
val pullRefreshState =
|
||||
rememberPullRefreshState(
|
||||
@ -165,7 +229,8 @@ class ManageBanScreen : Screen {
|
||||
Modifier
|
||||
.padding(
|
||||
top = padding.calculateTopPadding(),
|
||||
).then(
|
||||
).navigationBarsPadding()
|
||||
.then(
|
||||
if (settings.hideNavigationBarWhileScrolling) {
|
||||
Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
} else {
|
||||
@ -198,20 +263,12 @@ class ManageBanScreen : Screen {
|
||||
LocalStrings.current.exploreResultTypeUsers,
|
||||
LocalStrings.current.exploreResultTypeCommunities,
|
||||
LocalStrings.current.settingsManageBanSectionInstances,
|
||||
LocalStrings.current.settingsManageBanSectionDomains,
|
||||
),
|
||||
currentSection =
|
||||
when (uiState.section) {
|
||||
ManageBanSection.Instances -> 2
|
||||
ManageBanSection.Communities -> 1
|
||||
else -> 0
|
||||
},
|
||||
onSectionSelected = {
|
||||
val section =
|
||||
when (it) {
|
||||
2 -> ManageBanSection.Instances
|
||||
1 -> ManageBanSection.Communities
|
||||
else -> ManageBanSection.Users
|
||||
}
|
||||
scrollable = true,
|
||||
currentSection = uiState.section.toInt(),
|
||||
onSectionSelected = { idx ->
|
||||
val section = idx.toManageBanSection()
|
||||
model.reduce(ManageBanMviModel.Intent.ChangeSection(section))
|
||||
},
|
||||
)
|
||||
@ -255,7 +312,10 @@ class ManageBanScreen : Screen {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
items(uiState.bannedUsers) { user ->
|
||||
items(
|
||||
items = uiState.bannedUsers,
|
||||
key = { it.id },
|
||||
) { user ->
|
||||
UserItem(
|
||||
user = user,
|
||||
autoLoadImages = uiState.autoLoadImages,
|
||||
@ -308,7 +368,10 @@ class ManageBanScreen : Screen {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
items(uiState.bannedCommunities) { community ->
|
||||
items(
|
||||
items = uiState.bannedCommunities,
|
||||
key = { it.id },
|
||||
) { community ->
|
||||
CommunityItem(
|
||||
community = community,
|
||||
autoLoadImages = uiState.autoLoadImages,
|
||||
@ -344,7 +407,7 @@ class ManageBanScreen : Screen {
|
||||
if (uiState.bannedInstances.isEmpty()) {
|
||||
if (uiState.initial) {
|
||||
items(5) {
|
||||
CommunityItemPlaceholder()
|
||||
ManageBanItemPlaceholder()
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
@ -361,9 +424,12 @@ class ManageBanScreen : Screen {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
items(uiState.bannedInstances) { instance ->
|
||||
InstanceItem(
|
||||
instance = instance,
|
||||
items(
|
||||
items = uiState.bannedInstances,
|
||||
key = { it.id },
|
||||
) { instance ->
|
||||
ManageBanItem(
|
||||
title = instance.domain,
|
||||
options =
|
||||
buildList {
|
||||
this +=
|
||||
@ -390,6 +456,60 @@ class ManageBanScreen : Screen {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ManageBanSection.Domains -> {
|
||||
if (uiState.blockedDomains.isEmpty()) {
|
||||
if (uiState.initial) {
|
||||
items(5) {
|
||||
ManageBanItemPlaceholder()
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
Text(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = Spacing.xs),
|
||||
textAlign = TextAlign.Center,
|
||||
text = LocalStrings.current.messageEmptyList,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
items(
|
||||
items = uiState.blockedDomains,
|
||||
key = { it },
|
||||
) { domain ->
|
||||
ManageBanItem(
|
||||
title = domain,
|
||||
options =
|
||||
buildList {
|
||||
this +=
|
||||
Option(
|
||||
OptionId.Unban,
|
||||
LocalStrings.current.settingsManageBanActionUnban,
|
||||
)
|
||||
},
|
||||
onOptionSelected =
|
||||
rememberCallbackArgs(domain) { optionId ->
|
||||
when (optionId) {
|
||||
OptionId.Unban -> {
|
||||
model.reduce(
|
||||
ManageBanMviModel.Intent.UnblockDomain(
|
||||
domain,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,5 +523,19 @@ class ManageBanScreen : Screen {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addDomainDialogOpen) {
|
||||
EditTextualInfoDialog(
|
||||
title = LocalStrings.current.settingsManageBanDomainPlaceholder,
|
||||
value = "",
|
||||
onClose =
|
||||
rememberCallbackArgs(model) { newValue ->
|
||||
addDomainDialogOpen = false
|
||||
newValue?.also {
|
||||
model.reduce(ManageBanMviModel.Intent.BlockDomain(it))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.unit.manageban
|
||||
|
||||
sealed interface ManageBanSection {
|
||||
data object Users : ManageBanSection
|
||||
|
||||
data object Communities : ManageBanSection
|
||||
|
||||
data object Instances : ManageBanSection
|
||||
|
||||
data object Domains : ManageBanSection
|
||||
}
|
||||
|
||||
fun ManageBanSection.toInt(): Int =
|
||||
when (this) {
|
||||
ManageBanSection.Communities -> 1
|
||||
ManageBanSection.Domains -> 3
|
||||
ManageBanSection.Instances -> 2
|
||||
ManageBanSection.Users -> 0
|
||||
}
|
||||
|
||||
fun Int.toManageBanSection(): ManageBanSection =
|
||||
when (this) {
|
||||
3 -> ManageBanSection.Domains
|
||||
2 -> ManageBanSection.Instances
|
||||
1 -> ManageBanSection.Communities
|
||||
else -> ManageBanSection.Users
|
||||
}
|
@ -2,6 +2,8 @@ package com.github.diegoberaldin.raccoonforlemmy.unit.manageban
|
||||
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||
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.domain.identity.repository.IdentityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.AccountBansModel
|
||||
@ -25,15 +27,18 @@ import kotlinx.coroutines.launch
|
||||
@OptIn(FlowPreview::class)
|
||||
class ManageBanViewModel(
|
||||
private val identityRepository: IdentityRepository,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val siteRepository: SiteRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val userRepository: UserRepository,
|
||||
private val communityRepository: CommunityRepository,
|
||||
private val blocklistRepository: DomainBlocklistRepository,
|
||||
) : DefaultMviModel<ManageBanMviModel.Intent, ManageBanMviModel.UiState, ManageBanMviModel.Effect>(
|
||||
initialState = ManageBanMviModel.UiState(),
|
||||
),
|
||||
ManageBanMviModel {
|
||||
private var originalBans: AccountBansModel? = null
|
||||
private var originalBlockedDomains: List<String> = emptyList()
|
||||
|
||||
init {
|
||||
screenModelScope.launch {
|
||||
@ -83,6 +88,8 @@ class ManageBanViewModel(
|
||||
is ManageBanMviModel.Intent.UnblockCommunity -> unbanCommunity(intent.id)
|
||||
is ManageBanMviModel.Intent.UnblockInstance -> unbanInstance(intent.id)
|
||||
is ManageBanMviModel.Intent.UnblockUser -> unbanUser(intent.id)
|
||||
is ManageBanMviModel.Intent.BlockDomain -> blockDomain(intent.value)
|
||||
is ManageBanMviModel.Intent.UnblockDomain -> unblockDomain(intent.value)
|
||||
is ManageBanMviModel.Intent.SetSearch -> updateSearchText(intent.value)
|
||||
}
|
||||
}
|
||||
@ -90,6 +97,8 @@ class ManageBanViewModel(
|
||||
private suspend fun refresh() {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
originalBans = siteRepository.getBans(auth)
|
||||
val accountId = accountRepository.getActive()?.id
|
||||
originalBlockedDomains = blocklistRepository.get(accountId)
|
||||
val query = uiState.value.searchText
|
||||
filterResults(query)
|
||||
}
|
||||
@ -164,10 +173,38 @@ class ManageBanViewModel(
|
||||
bannedUsers = bans.users.filterUsersBy(query),
|
||||
bannedCommunities = bans.communities.filterCommunitiesBy(query),
|
||||
bannedInstances = bans.instances.filterInstancesBy(query),
|
||||
blockedDomains = originalBlockedDomains.filterBy(query),
|
||||
initial = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun blockDomain(domain: String) {
|
||||
val newValues =
|
||||
if (originalBlockedDomains.contains(domain)) {
|
||||
originalBlockedDomains
|
||||
} else {
|
||||
originalBlockedDomains + domain
|
||||
}
|
||||
screenModelScope.launch {
|
||||
val accountId = accountRepository.getActive()?.id
|
||||
originalBlockedDomains = newValues
|
||||
blocklistRepository.update(accountId, newValues)
|
||||
val query = uiState.value.searchText
|
||||
filterResults(query)
|
||||
}
|
||||
}
|
||||
|
||||
private fun unblockDomain(domain: String) {
|
||||
val newValues = originalBlockedDomains - domain
|
||||
screenModelScope.launch {
|
||||
val accountId = accountRepository.getActive()?.id
|
||||
originalBlockedDomains = newValues
|
||||
blocklistRepository.update(accountId, newValues)
|
||||
val query = uiState.value.searchText
|
||||
filterResults(query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<UserModel>.filterUsersBy(query: String): List<UserModel> =
|
||||
@ -188,5 +225,12 @@ private fun List<InstanceModel>.filterInstancesBy(query: String): List<InstanceM
|
||||
if (query.isEmpty()) {
|
||||
this
|
||||
} else {
|
||||
filter { it.domain.contains(query) }
|
||||
filter { it.domain.contains(query, ignoreCase = true) }
|
||||
}
|
||||
|
||||
private fun List<String>.filterBy(query: String): List<String> =
|
||||
if (query.isEmpty()) {
|
||||
this
|
||||
} else {
|
||||
filter { it.contains(query, ignoreCase = true) }
|
||||
}
|
||||
|
@ -32,16 +32,14 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.Option
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.OptionId
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.InstanceModel
|
||||
|
||||
@Composable
|
||||
fun InstanceItem(
|
||||
instance: InstanceModel,
|
||||
internal fun ManageBanItem(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
options: List<Option> = emptyList(),
|
||||
onOptionSelected: ((OptionId) -> Unit)? = null,
|
||||
) {
|
||||
val name = instance.domain
|
||||
val iconSize = 30.dp
|
||||
val fullColor = MaterialTheme.colorScheme.onBackground
|
||||
val ancillaryColor = MaterialTheme.colorScheme.onBackground.copy(alpha = ancillaryTextAlpha)
|
||||
@ -59,12 +57,12 @@ fun InstanceItem(
|
||||
) {
|
||||
PlaceholderImage(
|
||||
size = iconSize,
|
||||
title = name,
|
||||
title = title,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = name,
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = fullColor,
|
||||
)
|
@ -0,0 +1,51 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.unit.manageban.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.CornerSize
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.IconSize
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.shimmerEffect
|
||||
|
||||
@Composable
|
||||
internal fun ManageBanItemPlaceholder() {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
vertical = Spacing.xs,
|
||||
horizontal = Spacing.s,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(Spacing.xxxs)
|
||||
.size(IconSize.l)
|
||||
.clip(CircleShape)
|
||||
.shimmerEffect(),
|
||||
)
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(start = Spacing.xs)
|
||||
.height(40.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(CornerSize.s))
|
||||
.shimmerEffect(),
|
||||
)
|
||||
}
|
||||
}
|
@ -9,10 +9,12 @@ val manageBanModule =
|
||||
factory<ManageBanMviModel> {
|
||||
ManageBanViewModel(
|
||||
identityRepository = get(),
|
||||
accountRepository = get(),
|
||||
siteRepository = get(),
|
||||
settingsRepository = get(),
|
||||
userRepository = get(),
|
||||
communityRepository = get(),
|
||||
blocklistRepository = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user