From 424326f99fca6492e8923499b67189222ea5d373 Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Mon, 5 Dec 2022 14:36:30 +0100 Subject: [PATCH] Add a menu option to mute / filter a hashtag from a status list (#2882) * Add a menu option to mute / filter a hashtag from a status list Un/muting uses the "home" filter * Set the initial mute button visibility from existing filters Check the user's filters to see if the tag is already filtered from HOME. If it is then the initial button is to unmute it. If it isn't then the initial button is to mute it. * Avoid "mute tag" menu items "popping" in - Initial state shows the "mute" option, disabled - Update the state after the API call completes --- .../keylesspalace/tusky/StatusListActivity.kt | 100 ++++++++++++++++++ .../main/res/menu/view_hashtag_toolbar.xml | 13 +++ app/src/main/res/values/strings.xml | 2 + 3 files changed, 115 insertions(+) diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt index cc12479ad..917416b4c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt @@ -22,15 +22,22 @@ import android.util.Log import android.view.Menu import android.view.MenuItem import androidx.fragment.app.commit +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import at.connyduck.calladapter.networkresult.fold +import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider +import autodispose2.autoDispose import com.google.android.material.snackbar.Snackbar +import com.keylesspalace.tusky.appstore.EventHub +import com.keylesspalace.tusky.appstore.PreferenceChangedEvent import com.keylesspalace.tusky.components.timeline.TimelineFragment import com.keylesspalace.tusky.components.timeline.viewmodel.TimelineViewModel.Kind import com.keylesspalace.tusky.databinding.ActivityStatuslistBinding +import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.util.viewBinding import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import kotlinx.coroutines.launch import javax.inject.Inject @@ -39,13 +46,22 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector { @Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector + @Inject + lateinit var eventHub: EventHub + private val binding: ActivityStatuslistBinding by viewBinding(ActivityStatuslistBinding::inflate) private lateinit var kind: Kind private var hashtag: String? = null private var followTagItem: MenuItem? = null private var unfollowTagItem: MenuItem? = null + private var muteTagItem: MenuItem? = null + private var unmuteTagItem: MenuItem? = null + + /** The filter muting hashtag, null if unknown or hashtag is not filtered */ + private var mutedFilter: Filter? = null override fun onCreate(savedInstanceState: Bundle?) { + Log.d("StatusListActivity", "onCreate") super.onCreate(savedInstanceState) setContentView(binding.root) @@ -89,10 +105,15 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector { menuInflater.inflate(R.menu.view_hashtag_toolbar, menu) followTagItem = menu.findItem(R.id.action_follow_hashtag) unfollowTagItem = menu.findItem(R.id.action_unfollow_hashtag) + muteTagItem = menu.findItem(R.id.action_mute_hashtag) + unmuteTagItem = menu.findItem(R.id.action_unmute_hashtag) followTagItem?.isVisible = tagEntity.following == false unfollowTagItem?.isVisible = tagEntity.following == true followTagItem?.setOnMenuItemClickListener { followTag() } unfollowTagItem?.setOnMenuItemClickListener { unfollowTag() } + muteTagItem?.setOnMenuItemClickListener { muteTag() } + unmuteTagItem?.setOnMenuItemClickListener { unmuteTag() } + updateMuteTagMenuItems() }, { Log.w(TAG, "Failed to query tag #$tag", it) @@ -144,6 +165,85 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector { return true } + /** + * Determine if the current hashtag is muted, and update the UI state accordingly. + */ + private fun updateMuteTagMenuItems() { + val tag = hashtag ?: return + + muteTagItem?.isVisible = true + muteTagItem?.isEnabled = false + unmuteTagItem?.isVisible = false + + mastodonApi.getFilters().observeOn(AndroidSchedulers.mainThread()) + .autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)) + .subscribe { filters -> + for (filter in filters) { + if ((tag == filter.phrase) and filter.context.contains(Filter.HOME)) { + Log.d(TAG, "Tag $hashtag is filtered") + muteTagItem?.isVisible = false + unmuteTagItem?.isVisible = true + mutedFilter = filter + return@subscribe + } + } + + Log.d(TAG, "Tag $hashtag is not filtered") + mutedFilter = null + muteTagItem?.isEnabled = true + muteTagItem?.isVisible = true + muteTagItem?.isVisible = true + } + } + + private fun muteTag(): Boolean { + val tag = hashtag ?: return true + + lifecycleScope.launch { + mastodonApi.createFilter( + tag, + listOf(Filter.HOME), + irreversible = false, + wholeWord = true, + expiresInSeconds = null + ).fold( + { filter -> + mutedFilter = filter + muteTagItem?.isVisible = false + unmuteTagItem?.isVisible = true + eventHub.dispatch(PreferenceChangedEvent(filter.context[0])) + }, + { + Snackbar.make(binding.root, getString(R.string.error_muting_hashtag_format, tag), Snackbar.LENGTH_SHORT).show() + Log.e(TAG, "Failed to mute #$tag", it) + } + ) + } + + return true + } + + private fun unmuteTag(): Boolean { + val filter = mutedFilter ?: return true + + lifecycleScope.launch { + mastodonApi.deleteFilter(filter.id).fold( + { + muteTagItem?.isVisible = true + unmuteTagItem?.isVisible = false + eventHub.dispatch(PreferenceChangedEvent(filter.context[0])) + mutedFilter = null + }, + { + Snackbar.make(binding.root, getString(R.string.error_unmuting_hashtag_format, filter.phrase), Snackbar.LENGTH_SHORT).show() + Log.e(TAG, "Failed to unmute #${filter.phrase}", it) + } + ) + } + + return true + } + override fun androidInjector() = dispatchingAndroidInjector companion object { diff --git a/app/src/main/res/menu/view_hashtag_toolbar.xml b/app/src/main/res/menu/view_hashtag_toolbar.xml index 159593dc4..fb22c7e09 100644 --- a/app/src/main/res/menu/view_hashtag_toolbar.xml +++ b/app/src/main/res/menu/view_hashtag_toolbar.xml @@ -17,5 +17,18 @@ app:iconTint="?attr/colorOnSurface" android:icon="@drawable/ic_person_remove_24dp" /> + + + \ 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 ac478113f..1965c4db7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,6 +25,8 @@ Error following #%s Error unfollowing #%s This instance does not support following hashtags. + Error muting #%s + Error unmuting #%s Login Home