introduce DomainBlocksRepository

This commit is contained in:
Conny Duck 2023-07-05 20:09:16 +02:00
parent 8e56b5429b
commit f8a6e0d8b8
6 changed files with 93 additions and 40 deletions

View File

@ -46,7 +46,7 @@ class DomainBlocksFragment : Fragment(R.layout.fragment_domain_blocks), Injectab
}
lifecycleScope.launch {
viewModel.pager.collectLatest { pagingData ->
viewModel.domainPager.collectLatest { pagingData ->
adapter.submitData(pagingData)
}
}

View File

@ -3,12 +3,15 @@ package com.keylesspalace.tusky.components.domainblocks
import androidx.paging.PagingSource
import androidx.paging.PagingState
class DomainBlocksPagingSource(private val viewModel: DomainBlocksViewModel) : PagingSource<String, String>() {
class DomainBlocksPagingSource(
private val domains: List<String>,
private val nextKey: String?
) : PagingSource<String, String>() {
override fun getRefreshKey(state: PagingState<String, String>): String? = null
override suspend fun load(params: LoadParams<String>): LoadResult<String, String> {
return if (params is LoadParams.Refresh) {
LoadResult.Page(viewModel.domains.toList(), null, viewModel.nextKey)
LoadResult.Page(domains, null, nextKey)
} else {
LoadResult.Page(emptyList(), null, null)
}

View File

@ -12,8 +12,9 @@ import retrofit2.Response
@OptIn(ExperimentalPagingApi::class)
class DomainBlocksRemoteMediator(
private val api: MastodonApi,
private val viewModel: DomainBlocksViewModel
private val repository: DomainBlocksRepository
) : RemoteMediator<String, String>() {
override suspend fun load(
loadType: LoadType,
state: PagingState<String, String>
@ -31,10 +32,10 @@ class DomainBlocksRemoteMediator(
private suspend fun request(loadType: LoadType): Response<List<String>>? {
return when (loadType) {
LoadType.PREPEND -> null
LoadType.APPEND -> api.domainBlocks(maxId = viewModel.nextKey)
LoadType.APPEND -> api.domainBlocks(maxId = repository.nextKey)
LoadType.REFRESH -> {
viewModel.nextKey = null
viewModel.domains.clear()
repository.nextKey = null
repository.domains.clear()
api.domainBlocks()
}
}
@ -47,10 +48,10 @@ class DomainBlocksRemoteMediator(
}
val links = HttpHeaderLink.parse(response.headers()["Link"])
viewModel.nextKey = HttpHeaderLink.findByRelationType(links, "next")?.uri?.getQueryParameter("max_id")
viewModel.domains.addAll(tags)
viewModel.currentSource?.invalidate()
repository.nextKey = HttpHeaderLink.findByRelationType(links, "next")?.uri?.getQueryParameter("max_id")
repository.domains.addAll(tags)
repository.invalidate()
return MediatorResult.Success(endOfPaginationReached = viewModel.nextKey == null)
return MediatorResult.Success(endOfPaginationReached = repository.nextKey == null)
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2023 Tusky Contributors
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>.
*/
package com.keylesspalace.tusky.components.domainblocks
import androidx.paging.ExperimentalPagingApi
import androidx.paging.InvalidatingPagingSourceFactory
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingSource
import at.connyduck.calladapter.networkresult.NetworkResult
import at.connyduck.calladapter.networkresult.onSuccess
import com.keylesspalace.tusky.network.MastodonApi
import javax.inject.Inject
class DomainBlocksRepository @Inject constructor(
private val api: MastodonApi
) {
val domains: MutableList<String> = mutableListOf()
var nextKey: String? = null
private var factory = InvalidatingPagingSourceFactory {
DomainBlocksPagingSource(domains.toList(), nextKey)
}
@OptIn(ExperimentalPagingApi::class)
val domainPager = Pager(
config = PagingConfig(pageSize = PAGE_SIZE, initialLoadSize = PAGE_SIZE),
remoteMediator = DomainBlocksRemoteMediator(api, this),
pagingSourceFactory = factory
).flow
/** Invalidate the active paging source, see [PagingSource.invalidate] */
fun invalidate() {
factory.invalidate()
}
suspend fun block(domain: String): NetworkResult<Unit> {
return api.blockDomain(domain).onSuccess {
domains.add(domain)
factory.invalidate()
}
}
suspend fun unblock(domain: String): NetworkResult<Unit> {
return api.unblockDomain(domain).onSuccess {
domains.remove(domain)
factory.invalidate()
}
}
companion object {
private const val PAGE_SIZE = 20
}
}

View File

@ -4,44 +4,25 @@ import android.view.View
import androidx.annotation.StringRes
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import at.connyduck.calladapter.networkresult.fold
import at.connyduck.calladapter.networkresult.onFailure
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.network.MastodonApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
class DomainBlocksViewModel @Inject constructor(
private val api: MastodonApi
private val repo: DomainBlocksRepository
) : ViewModel() {
val domains: MutableList<String> = mutableListOf()
val uiEvents = MutableSharedFlow<SnackbarEvent>()
var nextKey: String? = null
var currentSource: DomainBlocksPagingSource? = null
@OptIn(ExperimentalPagingApi::class)
val pager = Pager(
config = PagingConfig(pageSize = 20),
remoteMediator = DomainBlocksRemoteMediator(api, this),
pagingSourceFactory = {
DomainBlocksPagingSource(
viewModel = this
).also { source ->
currentSource = source
}
}
).flow.cachedIn(viewModelScope)
val domainPager = repo.domainPager.cachedIn(viewModelScope)
val uiEvents = MutableSharedFlow<SnackbarEvent>()
fun block(domain: String) {
viewModelScope.launch {
api.blockDomain(domain).fold({
domains.add(domain)
currentSource?.invalidate()
}, { e ->
repo.block(domain).onFailure { e ->
uiEvents.emit(
SnackbarEvent(
message = R.string.error_blocking_domain,
@ -51,15 +32,13 @@ class DomainBlocksViewModel @Inject constructor(
action = { block(domain) }
)
)
})
}
}
}
fun unblock(domain: String) {
viewModelScope.launch {
api.unblockDomain(domain).fold({
domains.remove(domain)
currentSource?.invalidate()
repo.unblock(domain).fold({
uiEvents.emit(
SnackbarEvent(
message = R.string.confirmation_domain_unmuted,

View File

@ -21,6 +21,7 @@ import android.content.SharedPreferences
import android.os.Looper
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.components.domainblocks.NotificationsRepository
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.settings.PrefKeys