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