3891 better hashtag filtering (#3893)

Fixes #3891 (the first two observerations; the rest is either ok or has
another issue)
This commit is contained in:
Levi Bard 2023-09-05 09:35:33 +02:00 committed by GitHub
commit b0150212c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 52 additions and 9 deletions

View File

@ -27,6 +27,8 @@ import at.connyduck.calladapter.networkresult.fold
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.EventHub
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
import com.keylesspalace.tusky.components.filters.EditFilterActivity
import com.keylesspalace.tusky.components.filters.FiltersActivity
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
@ -132,6 +134,8 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
{ {
followTagItem?.isVisible = false followTagItem?.isVisible = false
unfollowTagItem?.isVisible = true unfollowTagItem?.isVisible = true
Snackbar.make(binding.root, getString(R.string.following_hashtag_success_format, tag), Snackbar.LENGTH_SHORT).show()
}, },
{ {
Snackbar.make(binding.root, getString(R.string.error_following_hashtag_format, tag), Snackbar.LENGTH_SHORT).show() Snackbar.make(binding.root, getString(R.string.error_following_hashtag_format, tag), Snackbar.LENGTH_SHORT).show()
@ -152,6 +156,8 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
{ {
followTagItem?.isVisible = true followTagItem?.isVisible = true
unfollowTagItem?.isVisible = false unfollowTagItem?.isVisible = false
Snackbar.make(binding.root, getString(R.string.unfollowing_hashtag_success_format, tag), Snackbar.LENGTH_SHORT).show()
}, },
{ {
Snackbar.make(binding.root, getString(R.string.error_unfollowing_hashtag_format, tag), Snackbar.LENGTH_SHORT).show() Snackbar.make(binding.root, getString(R.string.error_unfollowing_hashtag_format, tag), Snackbar.LENGTH_SHORT).show()
@ -169,6 +175,7 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
*/ */
private fun updateMuteTagMenuItems() { private fun updateMuteTagMenuItems() {
val tag = hashtag ?: return val tag = hashtag ?: return
val hashedTag = "#$tag"
muteTagItem?.isVisible = true muteTagItem?.isVisible = true
muteTagItem?.isEnabled = false muteTagItem?.isEnabled = false
@ -178,9 +185,8 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
mastodonApi.getFilters().fold( mastodonApi.getFilters().fold(
{ filters -> { filters ->
mutedFilter = filters.firstOrNull { filter -> mutedFilter = filters.firstOrNull { filter ->
filter.context.contains(Filter.Kind.HOME.kind) && filter.keywords.any { // TODO shouldn't this be an exact match (only one keyword; exactly the hashtag)?
it.keyword == tag filter.context.contains(Filter.Kind.HOME.kind) && filter.title == hashedTag
}
} }
updateTagMuteState(mutedFilter != null) updateTagMuteState(mutedFilter != null)
}, },
@ -189,7 +195,7 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
mastodonApi.getFiltersV1().fold( mastodonApi.getFiltersV1().fold(
{ filters -> { filters ->
mutedFilterV1 = filters.firstOrNull { filter -> mutedFilterV1 = filters.firstOrNull { filter ->
tag == filter.phrase && filter.context.contains(FilterV1.HOME) hashedTag == filter.phrase && filter.context.contains(FilterV1.HOME)
} }
updateTagMuteState(mutedFilterV1 != null) updateTagMuteState(mutedFilterV1 != null)
}, },
@ -221,6 +227,9 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
val tag = hashtag ?: return true val tag = hashtag ?: return true
lifecycleScope.launch { lifecycleScope.launch {
var filterCreateSuccess = false
val hashedTag = "#$tag"
mastodonApi.createFilter( mastodonApi.createFilter(
title = "#$tag", title = "#$tag",
context = listOf(FilterV1.HOME), context = listOf(FilterV1.HOME),
@ -228,10 +237,13 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
expiresInSeconds = null expiresInSeconds = null
).fold( ).fold(
{ filter -> { filter ->
if (mastodonApi.addFilterKeyword(filterId = filter.id, keyword = tag, wholeWord = true).isSuccess) { if (mastodonApi.addFilterKeyword(filterId = filter.id, keyword = hashedTag, wholeWord = true).isSuccess) {
mutedFilter = filter // must be requested again; otherwise does not contain the keyword (but server does)
updateTagMuteState(true) mutedFilter = mastodonApi.getFilter(filter.id).getOrNull()
// TODO the preference key here ("home") is not meaningful; should probably be another event if any
eventHub.dispatch(PreferenceChangedEvent(filter.context[0])) eventHub.dispatch(PreferenceChangedEvent(filter.context[0]))
filterCreateSuccess = true
} else { } else {
Snackbar.make(binding.root, getString(R.string.error_muting_hashtag_format, tag), Snackbar.LENGTH_SHORT).show() Snackbar.make(binding.root, getString(R.string.error_muting_hashtag_format, tag), Snackbar.LENGTH_SHORT).show()
Log.e(TAG, "Failed to mute #$tag") Log.e(TAG, "Failed to mute #$tag")
@ -240,7 +252,7 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
{ throwable -> { throwable ->
if (throwable is HttpException && throwable.code() == 404) { if (throwable is HttpException && throwable.code() == 404) {
mastodonApi.createFilterV1( mastodonApi.createFilterV1(
tag, hashedTag,
listOf(FilterV1.HOME), listOf(FilterV1.HOME),
irreversible = false, irreversible = false,
wholeWord = true, wholeWord = true,
@ -248,8 +260,8 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
).fold( ).fold(
{ filter -> { filter ->
mutedFilterV1 = filter mutedFilterV1 = filter
updateTagMuteState(true)
eventHub.dispatch(PreferenceChangedEvent(filter.context[0])) eventHub.dispatch(PreferenceChangedEvent(filter.context[0]))
filterCreateSuccess = true
}, },
{ throwable -> { throwable ->
Snackbar.make(binding.root, getString(R.string.error_muting_hashtag_format, tag), Snackbar.LENGTH_SHORT).show() Snackbar.make(binding.root, getString(R.string.error_muting_hashtag_format, tag), Snackbar.LENGTH_SHORT).show()
@ -262,6 +274,24 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
} }
} }
) )
if (filterCreateSuccess) {
updateTagMuteState(true)
Snackbar.make(binding.root, getString(R.string.muting_hashtag_success_format, tag), Snackbar.LENGTH_LONG).apply {
setAction(R.string.action_view_filter) {
val intent = if (mutedFilter != null) {
Intent(this@StatusListActivity, EditFilterActivity::class.java).apply {
putExtra(EditFilterActivity.FILTER_TO_EDIT, mutedFilter)
}
} else {
Intent(this@StatusListActivity, FiltersActivity::class.java)
}
startActivityWithSlideInAnimation(intent)
}
show()
}
}
} }
return true return true
@ -307,6 +337,8 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
eventHub.dispatch(PreferenceChangedEvent(Filter.Kind.HOME.kind)) eventHub.dispatch(PreferenceChangedEvent(Filter.Kind.HOME.kind))
mutedFilterV1 = null mutedFilterV1 = null
mutedFilter = null mutedFilter = null
Snackbar.make(binding.root, getString(R.string.unmuting_hashtag_success_format, tag), Snackbar.LENGTH_SHORT).show()
}, },
{ throwable -> { throwable ->
Snackbar.make(binding.root, getString(R.string.error_unmuting_hashtag_format, tag), Snackbar.LENGTH_SHORT).show() Snackbar.make(binding.root, getString(R.string.error_unmuting_hashtag_format, tag), Snackbar.LENGTH_SHORT).show()

View File

@ -89,6 +89,11 @@ interface MastodonApi {
@GET("api/v1/filters") @GET("api/v1/filters")
suspend fun getFiltersV1(): NetworkResult<List<FilterV1>> suspend fun getFiltersV1(): NetworkResult<List<FilterV1>>
@GET("api/v2/filters/{filterId}")
suspend fun getFilter(
@Path("filterId") filterId: String
): NetworkResult<Filter>
@GET("api/v2/filters") @GET("api/v2/filters")
suspend fun getFilters(): NetworkResult<List<Filter>> suspend fun getFilters(): NetworkResult<List<Filter>>

View File

@ -614,6 +614,12 @@
<string name="notifications_apply_filter">Filter notifications</string> <string name="notifications_apply_filter">Filter notifications</string>
<string name="filter_apply">Apply</string> <string name="filter_apply">Apply</string>
<string name="muting_hashtag_success_format">Muting hashtag #%s as a warning</string>
<string name="unmuting_hashtag_success_format">Unmuting hashtag #%s</string>
<string name="action_view_filter">View filter</string>
<string name="following_hashtag_success_format">Now following hashtag #%s</string>
<string name="unfollowing_hashtag_success_format">No longer following hashtag #%s</string>
<string name="compose_shortcut_long_label">Compose post</string> <string name="compose_shortcut_long_label">Compose post</string>
<string name="compose_shortcut_short_label">Compose</string> <string name="compose_shortcut_short_label">Compose</string>