From f8a6e0d8b801637a53072b65b501ea2ef958bff5 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Wed, 5 Jul 2023 20:09:16 +0200 Subject: [PATCH] introduce DomainBlocksRepository --- .../domainblocks/DomainBlocksFragment.kt | 2 +- .../domainblocks/DomainBlocksPagingSource.kt | 7 +- .../DomainBlocksRemoteMediator.kt | 17 ++--- .../domainblocks/DomainBlocksRepository.kt | 69 +++++++++++++++++++ .../domainblocks/DomainBlocksViewModel.kt | 37 +++------- .../NotificationsViewModelTestBase.kt | 1 + 6 files changed, 93 insertions(+), 40 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRepository.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt index 66b59bfab..4d689b3e4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt @@ -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) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt index a0c1c5b54..0438a268f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt @@ -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() { +class DomainBlocksPagingSource( + private val domains: List, + private val nextKey: String? +) : PagingSource() { override fun getRefreshKey(state: PagingState): String? = null override suspend fun load(params: LoadParams): LoadResult { 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) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt index ecbb61104..09f99044e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt @@ -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() { + override suspend fun load( loadType: LoadType, state: PagingState @@ -31,10 +32,10 @@ class DomainBlocksRemoteMediator( private suspend fun request(loadType: LoadType): Response>? { 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) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRepository.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRepository.kt new file mode 100644 index 000000000..bdc9b9367 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRepository.kt @@ -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 . + */ + +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 = 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 { + return api.blockDomain(domain).onSuccess { + domains.add(domain) + factory.invalidate() + } + } + + suspend fun unblock(domain: String): NetworkResult { + return api.unblockDomain(domain).onSuccess { + domains.remove(domain) + factory.invalidate() + } + } + + companion object { + private const val PAGE_SIZE = 20 + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt index cf0d42578..6458977f0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt @@ -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 = mutableListOf() - val uiEvents = MutableSharedFlow() - 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() 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, diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt index 773c67662..6574e5cc2 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt @@ -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