From 626a8760ae7c1ef3791fd2d3b9f621a6f8514dfe Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 4 Jul 2023 19:30:57 +0200 Subject: [PATCH 1/8] refactor instance blocks to paging --- .../instancemute/InstanceMutePagingSource.kt | 16 ++ .../InstanceMuteRemoteMediator.kt | 56 ++++++ .../instancemute/InstanceMuteViewModel.kt | 71 +++++++ .../adapter/DomainMutesAdapter.kt | 47 +---- .../fragment/InstanceListFragment.kt | 178 +++++++----------- .../interfaces/InstanceActionListener.kt | 5 - .../tusky/di/ViewModelFactory.kt | 6 + .../tusky/network/MastodonApi.kt | 4 +- app/src/main/res/values/strings.xml | 2 + 9 files changed, 226 insertions(+), 159 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt delete mode 100644 app/src/main/java/com/keylesspalace/tusky/components/instancemute/interfaces/InstanceActionListener.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt new file mode 100644 index 000000000..e61617e6a --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt @@ -0,0 +1,16 @@ +package com.keylesspalace.tusky.components.instancemute + +import androidx.paging.PagingSource +import androidx.paging.PagingState + +class InstanceMutePagingSource(private val viewModel: InstanceMuteViewModel) : 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) + } else { + LoadResult.Page(emptyList(), null, null) + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt new file mode 100644 index 000000000..62ac91b72 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt @@ -0,0 +1,56 @@ +package com.keylesspalace.tusky.components.instancemute + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import com.keylesspalace.tusky.network.MastodonApi +import com.keylesspalace.tusky.util.HttpHeaderLink +import retrofit2.HttpException +import retrofit2.Response + +@OptIn(ExperimentalPagingApi::class) +class InstanceMuteRemoteMediator( + private val api: MastodonApi, + private val viewModel: InstanceMuteViewModel +) : RemoteMediator() { + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + return try { + val response = request(loadType) + ?: return MediatorResult.Success(endOfPaginationReached = true) + + return applyResponse(response) + } catch (e: Exception) { + MediatorResult.Error(e) + } + } + + private suspend fun request(loadType: LoadType): Response>? { + return when (loadType) { + LoadType.PREPEND -> null + LoadType.APPEND -> api.domainBlocks(maxId = viewModel.nextKey) + LoadType.REFRESH -> { + viewModel.nextKey = null + viewModel.domains.clear() + api.domainBlocks() + } + } + } + + private fun applyResponse(response: Response>): MediatorResult { + val tags = response.body() + if (!response.isSuccessful || tags == null) { + return MediatorResult.Error(HttpException(response)) + } + + val links = HttpHeaderLink.parse(response.headers()["Link"]) + viewModel.nextKey = HttpHeaderLink.findByRelationType(links, "next")?.uri?.getQueryParameter("max_id") + viewModel.domains.addAll(tags) + viewModel.currentSource?.invalidate() + + return MediatorResult.Success(endOfPaginationReached = viewModel.nextKey == null) + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt new file mode 100644 index 000000000..84444d793 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt @@ -0,0 +1,71 @@ +package com.keylesspalace.tusky.components.instancemute + +import android.util.Log +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 com.keylesspalace.tusky.network.MastodonApi +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +class InstanceMuteViewModel @Inject constructor( + private val api: MastodonApi +) : ViewModel() { + val domains: MutableList = mutableListOf() + val uiEvents = MutableSharedFlow() + var nextKey: String? = null + var currentSource: InstanceMutePagingSource? = null + + @OptIn(ExperimentalPagingApi::class) + val pager = Pager( + config = PagingConfig(pageSize = 20), + remoteMediator = InstanceMuteRemoteMediator(api, this), + pagingSourceFactory = { + InstanceMutePagingSource( + viewModel = this + ).also { source -> + currentSource = source + } + } + ).flow.cachedIn(viewModelScope) + + fun mute(domain: String) { + viewModelScope.launch { + api.blockDomain(domain).fold({ + domains.add(domain) + currentSource?.invalidate() + }, { e -> + Log.w(TAG, "Error muting domain $domain", e) + uiEvents.emit(InstanceMuteEvent.MuteError(domain)) + }) + } + } + + fun unmute(domain: String) { + viewModelScope.launch { + api.unblockDomain(domain).fold({ + domains.remove(domain) + currentSource?.invalidate() + uiEvents.emit(InstanceMuteEvent.UnmuteSuccess(domain)) + }, { e -> + Log.w(TAG, "Error unmuting domain $domain", e) + uiEvents.emit(InstanceMuteEvent.UnmuteError(domain)) + }) + } + } + + companion object { + private const val TAG = "InstanceMuteViewModel" + } +} + +sealed class InstanceMuteEvent { + data class UnmuteSuccess(val domain: String) : InstanceMuteEvent() + data class UnmuteError(val domain: String) : InstanceMuteEvent() + data class MuteError(val domain: String) : InstanceMuteEvent() +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt index 13d8f2d83..e4f15d54b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt @@ -2,17 +2,14 @@ package com.keylesspalace.tusky.components.instancemute.adapter import android.view.LayoutInflater import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener +import androidx.paging.PagingDataAdapter +import com.keylesspalace.tusky.components.followedtags.FollowedTagsAdapter.Companion.STRING_COMPARATOR import com.keylesspalace.tusky.databinding.ItemMutedDomainBinding import com.keylesspalace.tusky.util.BindingHolder class DomainMutesAdapter( - private val actionListener: InstanceActionListener -) : RecyclerView.Adapter>() { - - var instances: MutableList = mutableListOf() - var bottomLoading: Boolean = false + private val onUnmute: (String) -> Unit +) : PagingDataAdapter>(STRING_COMPARATOR) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { val binding = ItemMutedDomainBinding.inflate(LayoutInflater.from(parent.context), parent, false) @@ -20,37 +17,11 @@ class DomainMutesAdapter( } override fun onBindViewHolder(holder: BindingHolder, position: Int) { - val instance = instances[position] - - holder.binding.mutedDomain.text = instance - holder.binding.mutedDomainUnmute.setOnClickListener { - actionListener.mute(false, instance, holder.bindingAdapterPosition) - } - } - - override fun getItemCount(): Int { - var count = instances.size - if (bottomLoading) { - ++count - } - return count - } - - fun addItems(newInstances: List) { - val end = instances.size - instances.addAll(newInstances) - notifyItemRangeInserted(end, instances.size) - } - - fun addItem(instance: String) { - instances.add(instance) - notifyItemInserted(instances.size) - } - - fun removeItem(position: Int) { - if (position >= 0 && position < instances.size) { - instances.removeAt(position) - notifyItemRemoved(position) + getItem(position)?.let { instance -> + holder.binding.mutedDomain.text = instance + holder.binding.mutedDomainUnmute.setOnClickListener { + onUnmute(instance) + } } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt index 1da0a2b7d..71e1a0482 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt @@ -4,155 +4,105 @@ import android.os.Bundle import android.util.Log import android.view.View import androidx.fragment.app.Fragment -import androidx.lifecycle.Lifecycle +import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope +import androidx.paging.LoadState import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import at.connyduck.calladapter.networkresult.fold -import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from -import autodispose2.autoDispose import com.google.android.material.snackbar.Snackbar import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.components.followedtags.FollowedTagsActivity +import com.keylesspalace.tusky.components.instancemute.InstanceMuteEvent +import com.keylesspalace.tusky.components.instancemute.InstanceMuteViewModel import com.keylesspalace.tusky.components.instancemute.adapter.DomainMutesAdapter -import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener import com.keylesspalace.tusky.databinding.FragmentInstanceListBinding import com.keylesspalace.tusky.di.Injectable -import com.keylesspalace.tusky.network.MastodonApi -import com.keylesspalace.tusky.util.HttpHeaderLink +import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.viewBinding -import com.keylesspalace.tusky.view.EndlessOnScrollListener -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import com.keylesspalace.tusky.util.visible +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject -class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener { +class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectable { @Inject - lateinit var api: MastodonApi + lateinit var viewModelFactory: ViewModelFactory private val binding by viewBinding(FragmentInstanceListBinding::bind) - private var fetching = false - private var bottomId: String? = null - private var adapter = DomainMutesAdapter(this) - private lateinit var scrollListener: EndlessOnScrollListener + private val viewModel: InstanceMuteViewModel by viewModels { viewModelFactory } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + val adapter = DomainMutesAdapter(viewModel::unmute) binding.recyclerView.setHasFixedSize(true) binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL)) binding.recyclerView.adapter = adapter + binding.recyclerView.layoutManager = LinearLayoutManager(view.context) - val layoutManager = LinearLayoutManager(view.context) - binding.recyclerView.layoutManager = layoutManager - - scrollListener = object : EndlessOnScrollListener(layoutManager) { - override fun onLoadMore(totalItemsCount: Int, view: RecyclerView) { - if (bottomId != null) { - fetchInstances(bottomId) - } - } - } - - binding.recyclerView.addOnScrollListener(scrollListener) - fetchInstances() - } - - override fun mute(mute: Boolean, instance: String, position: Int) { viewLifecycleOwner.lifecycleScope.launch { - if (mute) { - api.blockDomain(instance).fold({ - adapter.addItem(instance) - }, { e -> - Log.e(TAG, "Error muting domain $instance", e) - }) - } else { - api.unblockDomain(instance).fold({ - adapter.removeItem(position) - Snackbar.make(binding.recyclerView, getString(R.string.confirmation_domain_unmuted, instance), Snackbar.LENGTH_LONG) - .setAction(R.string.action_undo) { - mute(true, instance, position) - } - .show() - }, { e -> - Log.e(TAG, "Error unmuting domain $instance", e) - }) - } - } - } - - private fun fetchInstances(id: String? = null) { - if (fetching) { - return - } - fetching = true - binding.instanceProgressBar.show() - - if (id != null) { - binding.recyclerView.post { adapter.bottomLoading = true } - } - - api.domainBlocks(id, bottomId) - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) - .subscribe( - { response -> - val instances = response.body() - - if (response.isSuccessful && instances != null) { - onFetchInstancesSuccess(instances, response.headers()["Link"]) - } else { - onFetchInstancesFailure(Exception(response.message())) - } - }, - { throwable -> - onFetchInstancesFailure(throwable) + viewModel.uiEvents.collect { event -> + when (event) { + is InstanceMuteEvent.UnmuteError -> showUnmuteError(event.domain) + is InstanceMuteEvent.MuteError -> showMuteError(event.domain) + is InstanceMuteEvent.UnmuteSuccess -> showUnmuteSuccess(event.domain) } - ) - } - - private fun onFetchInstancesSuccess(instances: List, linkHeader: String?) { - adapter.bottomLoading = false - binding.instanceProgressBar.hide() - - val links = HttpHeaderLink.parse(linkHeader) - val next = HttpHeaderLink.findByRelationType(links, "next") - val fromId = next?.uri?.getQueryParameter("max_id") - adapter.addItems(instances) - bottomId = fromId - fetching = false - - if (adapter.itemCount == 0) { - binding.messageView.show() - binding.messageView.setup( - R.drawable.elephant_friend_empty, - R.string.message_empty, - null - ) - } else { - binding.messageView.hide() + } } - } - private fun onFetchInstancesFailure(throwable: Throwable) { - fetching = false - binding.instanceProgressBar.hide() - Log.e(TAG, "Fetch failure", throwable) + lifecycleScope.launch { + viewModel.pager.collectLatest { pagingData -> + adapter.submitData(pagingData) + } + } - if (adapter.itemCount == 0) { - binding.messageView.show() - binding.messageView.setup(throwable) { + adapter.addLoadStateListener { loadState -> + binding.instanceProgressBar.visible(loadState.refresh == LoadState.Loading && adapter.itemCount == 0) + + if (loadState.refresh is LoadState.Error) { + binding.recyclerView.hide() + binding.messageView.show() + val errorState = loadState.refresh as LoadState.Error + binding.messageView.setup(errorState.error) { adapter.retry() } + Log.w(FollowedTagsActivity.TAG, "error loading followed hashtags", errorState.error) + } else if (loadState.refresh is LoadState.NotLoading && adapter.itemCount == 0) { + binding.recyclerView.hide() + binding.messageView.show() + binding.messageView.setup(R.drawable.elephant_friend_empty, R.string.message_empty) + } else { + binding.recyclerView.show() binding.messageView.hide() - this.fetchInstances(null) } } } - companion object { - private const val TAG = "InstanceList" // logging tag + private fun showUnmuteError(domain: String) { + showSnackbar( + getString(R.string.error_unmuting_domain, domain), + R.string.action_retry + ) { viewModel.unmute(domain) } + } + + private fun showMuteError(domain: String) { + showSnackbar( + getString(R.string.error_muting_domain, domain), + R.string.action_retry + ) { viewModel.mute(domain) } + } + + private fun showUnmuteSuccess(domain: String) { + showSnackbar( + getString(R.string.confirmation_domain_unmuted, domain), + R.string.action_undo + ) { viewModel.mute(domain) } + } + + private fun showSnackbar(message: String, actionText: Int, action: (View) -> Unit) { + Snackbar.make(binding.recyclerView, message, Snackbar.LENGTH_LONG) + .setAction(actionText, action) + .show() } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/interfaces/InstanceActionListener.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/interfaces/InstanceActionListener.kt deleted file mode 100644 index 9b88ad966..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/interfaces/InstanceActionListener.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.keylesspalace.tusky.components.instancemute.interfaces - -interface InstanceActionListener { - fun mute(mute: Boolean, instance: String, position: Int) -} diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt index af1972d5f..b14604c6a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt @@ -31,6 +31,7 @@ import com.keylesspalace.tusky.components.drafts.DraftsViewModel import com.keylesspalace.tusky.components.filters.EditFilterViewModel import com.keylesspalace.tusky.components.filters.FiltersViewModel import com.keylesspalace.tusky.components.followedtags.FollowedTagsViewModel +import com.keylesspalace.tusky.components.instancemute.InstanceMuteViewModel import com.keylesspalace.tusky.components.login.LoginWebViewViewModel import com.keylesspalace.tusky.components.notifications.NotificationsViewModel import com.keylesspalace.tusky.components.report.ReportViewModel @@ -185,5 +186,10 @@ abstract class ViewModelModule { @ViewModelKey(EditFilterViewModel::class) internal abstract fun editFilterViewModel(viewModel: EditFilterViewModel): ViewModel + @Binds + @IntoMap + @ViewModelKey(InstanceMuteViewModel::class) + internal abstract fun instanceMuteViewModel(viewModel: InstanceMuteViewModel): ViewModel + // Add more ViewModels here } diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt index a50ca7964..bfa961713 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -452,11 +452,11 @@ interface MastodonApi { ): Response> @GET("api/v1/domain_blocks") - fun domainBlocks( + suspend fun domainBlocks( @Query("max_id") maxId: String? = null, @Query("since_id") sinceId: String? = null, @Query("limit") limit: Int? = null - ): Single>> + ): Response> @FormUrlEncoded @POST("api/v1/domain_blocks") diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9fbb6fda3..1ed16f2ea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,6 +44,8 @@ This instance does not support following hashtags. Error muting #%s Error unmuting #%s + Failed to mute %s + Failed to mute %s Failed to load the status source from the server. Login From 467096436239bcf4a0f4f1db62b58ad1ae93302f Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 4 Jul 2023 19:41:36 +0200 Subject: [PATCH 2/8] rename module and classes to domainblocks --- app/src/main/AndroidManifest.xml | 2 +- .../DomainBlocksActivity.kt} | 7 ++-- .../DomainBlocksAdapter.kt} | 4 +- .../DomainBlocksFragment.kt} | 40 +++++++++---------- .../DomainBlocksPagingSource.kt} | 4 +- .../DomainBlocksRemoteMediator.kt} | 6 +-- .../DomainBlocksViewModel.kt} | 36 ++++++++--------- .../preference/AccountPreferencesFragment.kt | 4 +- .../tusky/di/ActivitiesModule.kt | 4 +- .../tusky/di/FragmentBuildersModule.kt | 4 +- .../tusky/di/ViewModelFactory.kt | 6 +-- ...ce_list.xml => fragment_domain_blocks.xml} | 4 +- app/src/main/res/values/strings.xml | 4 +- 13 files changed, 62 insertions(+), 63 deletions(-) rename app/src/main/java/com/keylesspalace/tusky/components/{instancemute/InstanceListActivity.kt => domainblocks/DomainBlocksActivity.kt} (78%) rename app/src/main/java/com/keylesspalace/tusky/components/{instancemute/adapter/DomainMutesAdapter.kt => domainblocks/DomainBlocksAdapter.kt} (92%) rename app/src/main/java/com/keylesspalace/tusky/components/{instancemute/fragment/InstanceListFragment.kt => domainblocks/DomainBlocksFragment.kt} (68%) rename app/src/main/java/com/keylesspalace/tusky/components/{instancemute/InstanceMutePagingSource.kt => domainblocks/DomainBlocksPagingSource.kt} (73%) rename app/src/main/java/com/keylesspalace/tusky/components/{instancemute/InstanceMuteRemoteMediator.kt => domainblocks/DomainBlocksRemoteMediator.kt} (92%) rename app/src/main/java/com/keylesspalace/tusky/components/{instancemute/InstanceMuteViewModel.kt => domainblocks/DomainBlocksViewModel.kt} (57%) rename app/src/main/res/layout/{fragment_instance_list.xml => fragment_domain_blocks.xml} (93%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 232bf491c..f88f1fc80 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -148,7 +148,7 @@ - + diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksActivity.kt similarity index 78% rename from app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt rename to app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksActivity.kt index 667360c53..618174907 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksActivity.kt @@ -1,15 +1,14 @@ -package com.keylesspalace.tusky.components.instancemute +package com.keylesspalace.tusky.components.domainblocks import android.os.Bundle import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.components.instancemute.fragment.InstanceListFragment import com.keylesspalace.tusky.databinding.ActivityAccountListBinding import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector import javax.inject.Inject -class InstanceListActivity : BaseActivity(), HasAndroidInjector { +class DomainBlocksActivity : BaseActivity(), HasAndroidInjector { @Inject lateinit var androidInjector: DispatchingAndroidInjector @@ -28,7 +27,7 @@ class InstanceListActivity : BaseActivity(), HasAndroidInjector { supportFragmentManager .beginTransaction() - .replace(R.id.fragment_container, InstanceListFragment()) + .replace(R.id.fragment_container, DomainBlocksFragment()) .commit() } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt similarity index 92% rename from app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt rename to app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt index e4f15d54b..a4b21fc79 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt @@ -1,4 +1,4 @@ -package com.keylesspalace.tusky.components.instancemute.adapter +package com.keylesspalace.tusky.components.domainblocks import android.view.LayoutInflater import android.view.ViewGroup @@ -7,7 +7,7 @@ import com.keylesspalace.tusky.components.followedtags.FollowedTagsAdapter.Compa import com.keylesspalace.tusky.databinding.ItemMutedDomainBinding import com.keylesspalace.tusky.util.BindingHolder -class DomainMutesAdapter( +class DomainBlocksAdapter( private val onUnmute: (String) -> Unit ) : PagingDataAdapter>(STRING_COMPARATOR) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt similarity index 68% rename from app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt rename to app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt index 71e1a0482..9d2d8686c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt @@ -1,4 +1,4 @@ -package com.keylesspalace.tusky.components.instancemute.fragment +package com.keylesspalace.tusky.components.domainblocks import android.os.Bundle import android.util.Log @@ -11,11 +11,7 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.components.followedtags.FollowedTagsActivity -import com.keylesspalace.tusky.components.instancemute.InstanceMuteEvent -import com.keylesspalace.tusky.components.instancemute.InstanceMuteViewModel -import com.keylesspalace.tusky.components.instancemute.adapter.DomainMutesAdapter -import com.keylesspalace.tusky.databinding.FragmentInstanceListBinding +import com.keylesspalace.tusky.databinding.FragmentDomainBlocksBinding import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.util.hide @@ -26,17 +22,17 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject -class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectable { +class DomainBlocksFragment : Fragment(R.layout.fragment_domain_blocks), Injectable { @Inject lateinit var viewModelFactory: ViewModelFactory - private val binding by viewBinding(FragmentInstanceListBinding::bind) + private val binding by viewBinding(FragmentDomainBlocksBinding::bind) - private val viewModel: InstanceMuteViewModel by viewModels { viewModelFactory } + private val viewModel: DomainBlocksViewModel by viewModels { viewModelFactory } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - val adapter = DomainMutesAdapter(viewModel::unmute) + val adapter = DomainBlocksAdapter(viewModel::unblock) binding.recyclerView.setHasFixedSize(true) binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL)) @@ -46,9 +42,9 @@ class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectab viewLifecycleOwner.lifecycleScope.launch { viewModel.uiEvents.collect { event -> when (event) { - is InstanceMuteEvent.UnmuteError -> showUnmuteError(event.domain) - is InstanceMuteEvent.MuteError -> showMuteError(event.domain) - is InstanceMuteEvent.UnmuteSuccess -> showUnmuteSuccess(event.domain) + is DomainBlockEvent.UnblockError -> showUnmuteError(event.domain) + is DomainBlockEvent.BlockError -> showMuteError(event.domain) + is DomainBlockEvent.BlockSuccess -> showUnmuteSuccess(event.domain) } } } @@ -60,14 +56,14 @@ class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectab } adapter.addLoadStateListener { loadState -> - binding.instanceProgressBar.visible(loadState.refresh == LoadState.Loading && adapter.itemCount == 0) + binding.progressBar.visible(loadState.refresh == LoadState.Loading && adapter.itemCount == 0) if (loadState.refresh is LoadState.Error) { binding.recyclerView.hide() binding.messageView.show() val errorState = loadState.refresh as LoadState.Error binding.messageView.setup(errorState.error) { adapter.retry() } - Log.w(FollowedTagsActivity.TAG, "error loading followed hashtags", errorState.error) + Log.w(TAG, "error loading blocked domains", errorState.error) } else if (loadState.refresh is LoadState.NotLoading && adapter.itemCount == 0) { binding.recyclerView.hide() binding.messageView.show() @@ -81,23 +77,23 @@ class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectab private fun showUnmuteError(domain: String) { showSnackbar( - getString(R.string.error_unmuting_domain, domain), + getString(R.string.error_unblocking_domain, domain), R.string.action_retry - ) { viewModel.unmute(domain) } + ) { viewModel.unblock(domain) } } private fun showMuteError(domain: String) { showSnackbar( - getString(R.string.error_muting_domain, domain), + getString(R.string.error_blocking_domain, domain), R.string.action_retry - ) { viewModel.mute(domain) } + ) { viewModel.block(domain) } } private fun showUnmuteSuccess(domain: String) { showSnackbar( getString(R.string.confirmation_domain_unmuted, domain), R.string.action_undo - ) { viewModel.mute(domain) } + ) { viewModel.block(domain) } } private fun showSnackbar(message: String, actionText: Int, action: (View) -> Unit) { @@ -105,4 +101,8 @@ class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectab .setAction(actionText, action) .show() } + + companion object { + private const val TAG = "DomainBlocksFragment" + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt similarity index 73% rename from app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt rename to app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt index e61617e6a..a0c1c5b54 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt @@ -1,9 +1,9 @@ -package com.keylesspalace.tusky.components.instancemute +package com.keylesspalace.tusky.components.domainblocks import androidx.paging.PagingSource import androidx.paging.PagingState -class InstanceMutePagingSource(private val viewModel: InstanceMuteViewModel) : PagingSource() { +class DomainBlocksPagingSource(private val viewModel: DomainBlocksViewModel) : PagingSource() { override fun getRefreshKey(state: PagingState): String? = null override suspend fun load(params: LoadParams): LoadResult { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt similarity index 92% rename from app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt rename to app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt index 62ac91b72..ecbb61104 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt @@ -1,4 +1,4 @@ -package com.keylesspalace.tusky.components.instancemute +package com.keylesspalace.tusky.components.domainblocks import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType @@ -10,9 +10,9 @@ import retrofit2.HttpException import retrofit2.Response @OptIn(ExperimentalPagingApi::class) -class InstanceMuteRemoteMediator( +class DomainBlocksRemoteMediator( private val api: MastodonApi, - private val viewModel: InstanceMuteViewModel + private val viewModel: DomainBlocksViewModel ) : RemoteMediator() { override suspend fun load( loadType: LoadType, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt similarity index 57% rename from app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt rename to app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt index 84444d793..c70fa5077 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt @@ -1,4 +1,4 @@ -package com.keylesspalace.tusky.components.instancemute +package com.keylesspalace.tusky.components.domainblocks import android.util.Log import androidx.lifecycle.ViewModel @@ -13,20 +13,20 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import javax.inject.Inject -class InstanceMuteViewModel @Inject constructor( +class DomainBlocksViewModel @Inject constructor( private val api: MastodonApi ) : ViewModel() { val domains: MutableList = mutableListOf() - val uiEvents = MutableSharedFlow() + val uiEvents = MutableSharedFlow() var nextKey: String? = null - var currentSource: InstanceMutePagingSource? = null + var currentSource: DomainBlocksPagingSource? = null @OptIn(ExperimentalPagingApi::class) val pager = Pager( config = PagingConfig(pageSize = 20), - remoteMediator = InstanceMuteRemoteMediator(api, this), + remoteMediator = DomainBlocksRemoteMediator(api, this), pagingSourceFactory = { - InstanceMutePagingSource( + DomainBlocksPagingSource( viewModel = this ).also { source -> currentSource = source @@ -34,38 +34,38 @@ class InstanceMuteViewModel @Inject constructor( } ).flow.cachedIn(viewModelScope) - fun mute(domain: String) { + fun block(domain: String) { viewModelScope.launch { api.blockDomain(domain).fold({ domains.add(domain) currentSource?.invalidate() }, { e -> - Log.w(TAG, "Error muting domain $domain", e) - uiEvents.emit(InstanceMuteEvent.MuteError(domain)) + Log.w(TAG, "Error blocking domain $domain", e) + uiEvents.emit(DomainBlockEvent.BlockError(domain)) }) } } - fun unmute(domain: String) { + fun unblock(domain: String) { viewModelScope.launch { api.unblockDomain(domain).fold({ domains.remove(domain) currentSource?.invalidate() - uiEvents.emit(InstanceMuteEvent.UnmuteSuccess(domain)) + uiEvents.emit(DomainBlockEvent.BlockSuccess(domain)) }, { e -> - Log.w(TAG, "Error unmuting domain $domain", e) - uiEvents.emit(InstanceMuteEvent.UnmuteError(domain)) + Log.w(TAG, "Error unblocking domain $domain", e) + uiEvents.emit(DomainBlockEvent.UnblockError(domain)) }) } } companion object { - private const val TAG = "InstanceMuteViewModel" + private const val TAG = "DomainBlocksViewModel" } } -sealed class InstanceMuteEvent { - data class UnmuteSuccess(val domain: String) : InstanceMuteEvent() - data class UnmuteError(val domain: String) : InstanceMuteEvent() - data class MuteError(val domain: String) : InstanceMuteEvent() +sealed class DomainBlockEvent { + data class BlockSuccess(val domain: String) : DomainBlockEvent() + data class UnblockError(val domain: String) : DomainBlockEvent() + data class BlockError(val domain: String) : DomainBlockEvent() } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt index b0fedb2b1..629c3709d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt @@ -30,9 +30,9 @@ import com.keylesspalace.tusky.R import com.keylesspalace.tusky.TabPreferenceActivity import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.components.accountlist.AccountListActivity +import com.keylesspalace.tusky.components.domainblocks.DomainBlocksActivity import com.keylesspalace.tusky.components.filters.FiltersActivity import com.keylesspalace.tusky.components.followedtags.FollowedTagsActivity -import com.keylesspalace.tusky.components.instancemute.InstanceListActivity import com.keylesspalace.tusky.components.login.LoginActivity import com.keylesspalace.tusky.components.notifications.currentAccountNeedsMigration import com.keylesspalace.tusky.db.AccountManager @@ -156,7 +156,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { setTitle(R.string.title_domain_mutes) setIcon(R.drawable.ic_mute_24dp) setOnPreferenceClickListener { - val intent = Intent(context, InstanceListActivity::class.java) + val intent = Intent(context, DomainBlocksActivity::class.java) activity?.startActivity(intent) activity?.overridePendingTransition( R.anim.slide_from_right, diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt index 2ceb97213..02b4f6ea7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt @@ -29,11 +29,11 @@ import com.keylesspalace.tusky.components.account.AccountActivity import com.keylesspalace.tusky.components.accountlist.AccountListActivity import com.keylesspalace.tusky.components.announcements.AnnouncementsActivity import com.keylesspalace.tusky.components.compose.ComposeActivity +import com.keylesspalace.tusky.components.domainblocks.DomainBlocksActivity import com.keylesspalace.tusky.components.drafts.DraftsActivity import com.keylesspalace.tusky.components.filters.EditFilterActivity import com.keylesspalace.tusky.components.filters.FiltersActivity import com.keylesspalace.tusky.components.followedtags.FollowedTagsActivity -import com.keylesspalace.tusky.components.instancemute.InstanceListActivity import com.keylesspalace.tusky.components.login.LoginActivity import com.keylesspalace.tusky.components.login.LoginWebViewActivity import com.keylesspalace.tusky.components.preference.PreferencesActivity @@ -113,7 +113,7 @@ abstract class ActivitiesModule { abstract fun contributesReportActivity(): ReportActivity @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) - abstract fun contributesInstanceListActivity(): InstanceListActivity + abstract fun contributesInstanceListActivity(): DomainBlocksActivity @ContributesAndroidInjector abstract fun contributesScheduledStatusActivity(): ScheduledStatusActivity diff --git a/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt index aee1feab4..3292bbfe8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt @@ -20,7 +20,7 @@ import com.keylesspalace.tusky.components.account.list.ListsForAccountFragment import com.keylesspalace.tusky.components.account.media.AccountMediaFragment import com.keylesspalace.tusky.components.accountlist.AccountListFragment import com.keylesspalace.tusky.components.conversation.ConversationsFragment -import com.keylesspalace.tusky.components.instancemute.fragment.InstanceListFragment +import com.keylesspalace.tusky.components.domainblocks.DomainBlocksFragment import com.keylesspalace.tusky.components.notifications.NotificationsFragment import com.keylesspalace.tusky.components.preference.AccountPreferencesFragment import com.keylesspalace.tusky.components.preference.NotificationPreferencesFragment @@ -84,7 +84,7 @@ abstract class FragmentBuildersModule { abstract fun reportDoneFragment(): ReportDoneFragment @ContributesAndroidInjector - abstract fun instanceListFragment(): InstanceListFragment + abstract fun instanceListFragment(): DomainBlocksFragment @ContributesAndroidInjector abstract fun searchStatusesFragment(): SearchStatusesFragment diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt index b14604c6a..f2ae58893 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt @@ -27,11 +27,11 @@ import com.keylesspalace.tusky.components.account.media.AccountMediaViewModel import com.keylesspalace.tusky.components.announcements.AnnouncementsViewModel import com.keylesspalace.tusky.components.compose.ComposeViewModel import com.keylesspalace.tusky.components.conversation.ConversationsViewModel +import com.keylesspalace.tusky.components.domainblocks.DomainBlocksViewModel import com.keylesspalace.tusky.components.drafts.DraftsViewModel import com.keylesspalace.tusky.components.filters.EditFilterViewModel import com.keylesspalace.tusky.components.filters.FiltersViewModel import com.keylesspalace.tusky.components.followedtags.FollowedTagsViewModel -import com.keylesspalace.tusky.components.instancemute.InstanceMuteViewModel import com.keylesspalace.tusky.components.login.LoginWebViewViewModel import com.keylesspalace.tusky.components.notifications.NotificationsViewModel import com.keylesspalace.tusky.components.report.ReportViewModel @@ -188,8 +188,8 @@ abstract class ViewModelModule { @Binds @IntoMap - @ViewModelKey(InstanceMuteViewModel::class) - internal abstract fun instanceMuteViewModel(viewModel: InstanceMuteViewModel): ViewModel + @ViewModelKey(DomainBlocksViewModel::class) + internal abstract fun instanceMuteViewModel(viewModel: DomainBlocksViewModel): ViewModel // Add more ViewModels here } diff --git a/app/src/main/res/layout/fragment_instance_list.xml b/app/src/main/res/layout/fragment_domain_blocks.xml similarity index 93% rename from app/src/main/res/layout/fragment_instance_list.xml rename to app/src/main/res/layout/fragment_domain_blocks.xml index 8270cee33..f82573e24 100644 --- a/app/src/main/res/layout/fragment_instance_list.xml +++ b/app/src/main/res/layout/fragment_domain_blocks.xml @@ -5,7 +5,7 @@ android:layout_height="match_parent"> @@ -22,4 +22,4 @@ android:layout_gravity="center" android:visibility="gone" tools:visibility="visible" /> - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1ed16f2ea..6c1df77d4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,8 +44,8 @@ This instance does not support following hashtags. Error muting #%s Error unmuting #%s - Failed to mute %s - Failed to mute %s + Failed to mute %s + Failed to unmute %s Failed to load the status source from the server. Login From 2393ff1e4d3ff99235ba89c4c697c0ace04e483f Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Wed, 5 Jul 2023 19:23:04 +0200 Subject: [PATCH 3/8] rename item_muted_domain to item_blocked_domain --- .../components/domainblocks/DomainBlocksAdapter.kt | 14 +++++++------- ...em_muted_domain.xml => item_blocked_domain.xml} | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) rename app/src/main/res/layout/{item_muted_domain.xml => item_blocked_domain.xml} (91%) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt index a4b21fc79..e37aa917c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt @@ -4,22 +4,22 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.paging.PagingDataAdapter import com.keylesspalace.tusky.components.followedtags.FollowedTagsAdapter.Companion.STRING_COMPARATOR -import com.keylesspalace.tusky.databinding.ItemMutedDomainBinding +import com.keylesspalace.tusky.databinding.ItemBlockedDomainBinding import com.keylesspalace.tusky.util.BindingHolder class DomainBlocksAdapter( private val onUnmute: (String) -> Unit -) : PagingDataAdapter>(STRING_COMPARATOR) { +) : PagingDataAdapter>(STRING_COMPARATOR) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { - val binding = ItemMutedDomainBinding.inflate(LayoutInflater.from(parent.context), parent, false) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { + val binding = ItemBlockedDomainBinding.inflate(LayoutInflater.from(parent.context), parent, false) return BindingHolder(binding) } - override fun onBindViewHolder(holder: BindingHolder, position: Int) { + override fun onBindViewHolder(holder: BindingHolder, position: Int) { getItem(position)?.let { instance -> - holder.binding.mutedDomain.text = instance - holder.binding.mutedDomainUnmute.setOnClickListener { + holder.binding.blockedDomain.text = instance + holder.binding.blockedDomainUnblock.setOnClickListener { onUnmute(instance) } } diff --git a/app/src/main/res/layout/item_muted_domain.xml b/app/src/main/res/layout/item_blocked_domain.xml similarity index 91% rename from app/src/main/res/layout/item_muted_domain.xml rename to app/src/main/res/layout/item_blocked_domain.xml index 3147fca56..e043e1a3b 100644 --- a/app/src/main/res/layout/item_muted_domain.xml +++ b/app/src/main/res/layout/item_blocked_domain.xml @@ -11,7 +11,7 @@ > - \ No newline at end of file + From 8e56b5429b18a96a5a3741df8964229070f8d2b3 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Wed, 5 Jul 2023 19:42:30 +0200 Subject: [PATCH 4/8] introduce SnackbarEvent instead of DomainBlockEvent --- .../domainblocks/DomainBlocksFragment.kt | 37 ++++--------- .../domainblocks/DomainBlocksViewModel.kt | 54 +++++++++++++------ app/src/main/res/values/strings.xml | 4 +- 3 files changed, 50 insertions(+), 45 deletions(-) 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 9d2d8686c..66b59bfab 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 @@ -41,11 +41,7 @@ class DomainBlocksFragment : Fragment(R.layout.fragment_domain_blocks), Injectab viewLifecycleOwner.lifecycleScope.launch { viewModel.uiEvents.collect { event -> - when (event) { - is DomainBlockEvent.UnblockError -> showUnmuteError(event.domain) - is DomainBlockEvent.BlockError -> showMuteError(event.domain) - is DomainBlockEvent.BlockSuccess -> showUnmuteSuccess(event.domain) - } + showSnackbar(event) } } @@ -75,30 +71,17 @@ class DomainBlocksFragment : Fragment(R.layout.fragment_domain_blocks), Injectab } } - private fun showUnmuteError(domain: String) { - showSnackbar( - getString(R.string.error_unblocking_domain, domain), - R.string.action_retry - ) { viewModel.unblock(domain) } - } + private fun showSnackbar(event: SnackbarEvent) { + val message = if (event.throwable == null) { + getString(event.message, event.domain) + } else { + Log.w(TAG, event.throwable) + val error = event.throwable.localizedMessage ?: getString(R.string.ui_error_unknown) + getString(event.message, event.domain, error) + } - private fun showMuteError(domain: String) { - showSnackbar( - getString(R.string.error_blocking_domain, domain), - R.string.action_retry - ) { viewModel.block(domain) } - } - - private fun showUnmuteSuccess(domain: String) { - showSnackbar( - getString(R.string.confirmation_domain_unmuted, domain), - R.string.action_undo - ) { viewModel.block(domain) } - } - - private fun showSnackbar(message: String, actionText: Int, action: (View) -> Unit) { Snackbar.make(binding.recyclerView, message, Snackbar.LENGTH_LONG) - .setAction(actionText, action) + .setAction(event.actionText, event.action) .show() } 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 c70fa5077..cf0d42578 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 @@ -1,6 +1,7 @@ package com.keylesspalace.tusky.components.domainblocks -import android.util.Log +import android.view.View +import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.ExperimentalPagingApi @@ -8,6 +9,7 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.cachedIn import at.connyduck.calladapter.networkresult.fold +import com.keylesspalace.tusky.R import com.keylesspalace.tusky.network.MastodonApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch @@ -17,7 +19,7 @@ class DomainBlocksViewModel @Inject constructor( private val api: MastodonApi ) : ViewModel() { val domains: MutableList = mutableListOf() - val uiEvents = MutableSharedFlow() + val uiEvents = MutableSharedFlow() var nextKey: String? = null var currentSource: DomainBlocksPagingSource? = null @@ -40,8 +42,15 @@ class DomainBlocksViewModel @Inject constructor( domains.add(domain) currentSource?.invalidate() }, { e -> - Log.w(TAG, "Error blocking domain $domain", e) - uiEvents.emit(DomainBlockEvent.BlockError(domain)) + uiEvents.emit( + SnackbarEvent( + message = R.string.error_blocking_domain, + domain = domain, + throwable = e, + actionText = R.string.action_retry, + action = { block(domain) } + ) + ) }) } } @@ -51,21 +60,34 @@ class DomainBlocksViewModel @Inject constructor( api.unblockDomain(domain).fold({ domains.remove(domain) currentSource?.invalidate() - uiEvents.emit(DomainBlockEvent.BlockSuccess(domain)) + uiEvents.emit( + SnackbarEvent( + message = R.string.confirmation_domain_unmuted, + domain = domain, + throwable = null, + actionText = R.string.action_undo, + action = { block(domain) } + ) + ) }, { e -> - Log.w(TAG, "Error unblocking domain $domain", e) - uiEvents.emit(DomainBlockEvent.UnblockError(domain)) + uiEvents.emit( + SnackbarEvent( + message = R.string.error_unblocking_domain, + domain = domain, + throwable = e, + actionText = R.string.action_retry, + action = { unblock(domain) } + ) + ) }) } } - - companion object { - private const val TAG = "DomainBlocksViewModel" - } } -sealed class DomainBlockEvent { - data class BlockSuccess(val domain: String) : DomainBlockEvent() - data class UnblockError(val domain: String) : DomainBlockEvent() - data class BlockError(val domain: String) : DomainBlockEvent() -} +class SnackbarEvent( + @StringRes val message: Int, + val domain: String, + val throwable: Throwable?, + @StringRes val actionText: Int, + val action: (View) -> Unit +) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6c1df77d4..1e68f6612 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,8 +44,8 @@ This instance does not support following hashtags. Error muting #%s Error unmuting #%s - Failed to mute %s - Failed to unmute %s + Failed to mute %1$s: %2$s + Failed to unmute %1$s: %2$s Failed to load the status source from the server. Login From f8a6e0d8b801637a53072b65b501ea2ef958bff5 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Wed, 5 Jul 2023 20:09:16 +0200 Subject: [PATCH 5/8] 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 From c84815525fde3b6b9af884a2d0d8925af10a56ba Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Wed, 5 Jul 2023 20:11:12 +0200 Subject: [PATCH 6/8] ui improvements --- .../tusky/components/domainblocks/DomainBlocksFragment.kt | 1 + app/src/main/res/layout/fragment_domain_blocks.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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 4d689b3e4..896e81ead 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 @@ -81,6 +81,7 @@ class DomainBlocksFragment : Fragment(R.layout.fragment_domain_blocks), Injectab } Snackbar.make(binding.recyclerView, message, Snackbar.LENGTH_LONG) + .setTextMaxLines(5) .setAction(event.actionText, event.action) .show() } diff --git a/app/src/main/res/layout/fragment_domain_blocks.xml b/app/src/main/res/layout/fragment_domain_blocks.xml index f82573e24..65fdf1d3e 100644 --- a/app/src/main/res/layout/fragment_domain_blocks.xml +++ b/app/src/main/res/layout/fragment_domain_blocks.xml @@ -17,7 +17,7 @@ Date: Wed, 5 Jul 2023 20:21:32 +0200 Subject: [PATCH 7/8] update lint-baseline.xml --- app/lint-baseline.xml | 274 +++++++++++++++--------------------------- 1 file changed, 95 insertions(+), 179 deletions(-) diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index bf4a0466a..76e26c38f 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -817,7 +817,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -828,7 +828,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -839,7 +839,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -850,7 +850,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -861,7 +861,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2031,17 +2031,6 @@ column="5"/> - - - - - - - - - - - - - - - - - - - - - - - - @@ -2390,7 +2324,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2401,7 +2335,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -2412,7 +2346,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2423,7 +2357,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2434,7 +2368,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2445,7 +2379,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2456,7 +2390,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2467,7 +2401,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2478,7 +2412,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2489,7 +2423,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2500,7 +2434,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2511,7 +2445,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2522,7 +2456,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -2533,7 +2467,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2544,7 +2478,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -2555,7 +2489,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -2566,7 +2500,7 @@ errorLine2=" ~~~~~~~~~~~~~~"> @@ -2577,7 +2511,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2588,7 +2522,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2599,7 +2533,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -2610,7 +2544,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2621,7 +2555,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -2632,7 +2566,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2643,7 +2577,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2654,7 +2588,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -2665,7 +2599,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2676,7 +2610,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2687,7 +2621,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2698,7 +2632,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -2709,7 +2643,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2720,7 +2654,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2731,7 +2665,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2742,7 +2676,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2962,7 +2896,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2973,7 +2907,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2984,7 +2918,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~"> @@ -2995,7 +2929,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3006,7 +2940,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3017,7 +2951,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3028,7 +2962,7 @@ errorLine2=" ~~~~~~"> @@ -3039,7 +2973,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~"> @@ -3050,7 +2984,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -3061,7 +2995,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3072,7 +3006,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3083,7 +3017,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3094,7 +3028,7 @@ errorLine2=" ~~~~~~"> @@ -3204,7 +3138,7 @@ errorLine2=" ~~~~~~~"> @@ -3215,7 +3149,7 @@ errorLine2=" ~~~~~~~"> @@ -3270,7 +3204,7 @@ errorLine2=" ~~~~~~~"> @@ -3281,7 +3215,7 @@ errorLine2=" ~~~~~~~"> @@ -3292,7 +3226,7 @@ errorLine2=" ~~~~~~~"> @@ -3303,7 +3237,7 @@ errorLine2=" ~~~~~~~"> @@ -3314,18 +3248,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - @@ -3589,7 +3512,7 @@ errorLine2=" ~~~~~~~~~"> @@ -3600,7 +3523,7 @@ errorLine2=" ~~~~~~~~~"> @@ -4491,7 +4414,7 @@ errorLine2=" ~~~~~~~~~"> @@ -4502,7 +4425,7 @@ errorLine2=" ~~~~~~~~"> @@ -4513,7 +4436,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -4524,7 +4447,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -4535,7 +4458,7 @@ errorLine2=" ~~~~~~~"> @@ -4546,7 +4469,7 @@ errorLine2=" ~~~~~~~"> @@ -4557,7 +4480,7 @@ errorLine2=" ~~~~~~~"> @@ -4568,7 +4491,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -5096,17 +5019,10 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - + + + + @@ -5631,18 +5558,7 @@ errorLine2=" ~~~~~~~~"> - - - - @@ -6137,7 +6053,7 @@ errorLine2=" ~~~~~~~~"> @@ -6445,7 +6361,7 @@ errorLine2=" ~~~~~~~~~"> @@ -6500,7 +6416,7 @@ errorLine2=" ~~~~~~~~~"> @@ -6819,7 +6735,7 @@ errorLine2=" ~~~~~~~~~~~~~~"> @@ -6830,7 +6746,7 @@ errorLine2=" ~~~~~~"> @@ -6841,7 +6757,7 @@ errorLine2=" ~~~~~~~~"> @@ -6852,7 +6768,7 @@ errorLine2=" ~~~~"> @@ -6863,7 +6779,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6874,7 +6790,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -6885,7 +6801,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -6896,7 +6812,7 @@ errorLine2=" ~~~~~~~~"> @@ -6907,7 +6823,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> From f919252ede9ae1d60db2b2a292233b44f97fca24 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Wed, 5 Jul 2023 20:43:24 +0200 Subject: [PATCH 8/8] remove import wrongly added by auto refactoring --- .../components/notifications/NotificationsViewModelTestBase.kt | 1 - 1 file changed, 1 deletion(-) 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 6574e5cc2..773c67662 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,7 +21,6 @@ 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