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
This commit is contained in:
Nik Clayton 2022-12-05 14:36:30 +01:00 committed by GitHub
parent 64a06bfbe2
commit 424326f99f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 0 deletions

View File

@ -22,15 +22,22 @@ import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.fragment.app.commit import androidx.fragment.app.commit
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import at.connyduck.calladapter.networkresult.fold import at.connyduck.calladapter.networkresult.fold
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider
import autodispose2.autoDispose
import com.google.android.material.snackbar.Snackbar 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.TimelineFragment
import com.keylesspalace.tusky.components.timeline.viewmodel.TimelineViewModel.Kind import com.keylesspalace.tusky.components.timeline.viewmodel.TimelineViewModel.Kind
import com.keylesspalace.tusky.databinding.ActivityStatuslistBinding import com.keylesspalace.tusky.databinding.ActivityStatuslistBinding
import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.viewBinding
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -39,13 +46,22 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
@Inject @Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any> lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
@Inject
lateinit var eventHub: EventHub
private val binding: ActivityStatuslistBinding by viewBinding(ActivityStatuslistBinding::inflate) private val binding: ActivityStatuslistBinding by viewBinding(ActivityStatuslistBinding::inflate)
private lateinit var kind: Kind private lateinit var kind: Kind
private var hashtag: String? = null private var hashtag: String? = null
private var followTagItem: MenuItem? = null private var followTagItem: MenuItem? = null
private var unfollowTagItem: 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?) { override fun onCreate(savedInstanceState: Bundle?) {
Log.d("StatusListActivity", "onCreate")
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(binding.root) setContentView(binding.root)
@ -89,10 +105,15 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
menuInflater.inflate(R.menu.view_hashtag_toolbar, menu) menuInflater.inflate(R.menu.view_hashtag_toolbar, menu)
followTagItem = menu.findItem(R.id.action_follow_hashtag) followTagItem = menu.findItem(R.id.action_follow_hashtag)
unfollowTagItem = menu.findItem(R.id.action_unfollow_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 followTagItem?.isVisible = tagEntity.following == false
unfollowTagItem?.isVisible = tagEntity.following == true unfollowTagItem?.isVisible = tagEntity.following == true
followTagItem?.setOnMenuItemClickListener { followTag() } followTagItem?.setOnMenuItemClickListener { followTag() }
unfollowTagItem?.setOnMenuItemClickListener { unfollowTag() } unfollowTagItem?.setOnMenuItemClickListener { unfollowTag() }
muteTagItem?.setOnMenuItemClickListener { muteTag() }
unmuteTagItem?.setOnMenuItemClickListener { unmuteTag() }
updateMuteTagMenuItems()
}, },
{ {
Log.w(TAG, "Failed to query tag #$tag", it) Log.w(TAG, "Failed to query tag #$tag", it)
@ -144,6 +165,85 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
return true 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 override fun androidInjector() = dispatchingAndroidInjector
companion object { companion object {

View File

@ -17,5 +17,18 @@
app:iconTint="?attr/colorOnSurface" app:iconTint="?attr/colorOnSurface"
android:icon="@drawable/ic_person_remove_24dp" /> android:icon="@drawable/ic_person_remove_24dp" />
<item
android:id="@+id/action_mute_hashtag"
android:title="Mute"
app:showAsAction="ifRoom"
app:iconTint="?attr/colorOnSurface"
android:icon="@drawable/ic_mute_24dp" />
<item
android:id="@+id/action_unmute_hashtag"
android:title="Unmute"
app:showAsAction="ifRoom"
app:iconTint="?attr/colorOnSurface"
android:icon="@drawable/ic_unmute_24dp" />
</menu> </menu>

View File

@ -25,6 +25,8 @@
<string name="error_following_hashtag_format">Error following #%s</string> <string name="error_following_hashtag_format">Error following #%s</string>
<string name="error_unfollowing_hashtag_format">Error unfollowing #%s</string> <string name="error_unfollowing_hashtag_format">Error unfollowing #%s</string>
<string name="error_following_hashtags_unsupported">This instance does not support following hashtags.</string> <string name="error_following_hashtags_unsupported">This instance does not support following hashtags.</string>
<string name="error_muting_hashtag_format">Error muting #%s</string>
<string name="error_unmuting_hashtag_format">Error unmuting #%s</string>
<string name="title_login">Login</string> <string name="title_login">Login</string>
<string name="title_home">Home</string> <string name="title_home">Home</string>