Support the mastodon 4 filter api (#3188)

* Replace "warn"-filtered posts in timelines and thread view with placeholders

* Adapt hashtag muting interface

* Rework filter UI

* Add icon for account preferences

* Clean up UI

* WIP: Use chips instead of a list. Adjust padding

* Scroll the filter edit activity

Nested scrolling views (e.g., an activity that scrolls with an embedded list
that also scrolls) can be difficult UI.

Since the list of contexts is fixed, replace it with a fixed collection of
switches, so there's no need to scroll the list.

Since the list of actions is only two (warn, hide), and are mutually
exclusive, replace the spinner with two radio buttons.

Use the accent colour and title styles on the different heading titles in
the layout, to match the presentation in Preferences.

Add an explicit "Cancel" button.

The layout is a straightforward LinearLayout, so use that instead of
ConstraintLayout, and remove some unncessary IDs.

Update EditFilterActivity to handle the new layout.

* Cleanup

* Add more information to the filter list view

* First pass on code review comments

* Add view model to filters activity

* Add view model to edit filters activity

* Only use the status wrapper for filtered statuses

* Relint

---------

Co-authored-by: Nik Clayton <nik@ngo.org.uk>
This commit is contained in:
Levi Bard 2023-03-11 13:12:50 +01:00 committed by GitHub
commit ff8dd37855
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
109 changed files with 2770 additions and 631 deletions

View file

@ -1,48 +1,44 @@
/* Copyright 2018 Levi Bard
*
* 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 <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.entity
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import java.util.Date
@Parcelize
data class Filter(
val id: String,
val phrase: String,
val title: String,
val context: List<String>,
@SerializedName("expires_at") val expiresAt: Date?,
val irreversible: Boolean,
@SerializedName("whole_word") val wholeWord: Boolean
) {
companion object {
const val HOME = "home"
const val NOTIFICATIONS = "notifications"
const val PUBLIC = "public"
const val THREAD = "thread"
const val ACCOUNT = "account"
}
@SerializedName("filter_action") private val filterAction: String,
val keywords: List<FilterKeyword>,
// val statuses: List<FilterStatus>,
) : Parcelable {
enum class Action(val action: String) {
NONE("none"),
WARN("warn"),
HIDE("hide");
override fun hashCode(): Int {
return id.hashCode()
}
override fun equals(other: Any?): Boolean {
if (other !is Filter) {
return false
companion object {
fun from(action: String): Action = values().firstOrNull { it.action == action } ?: WARN
}
val filter = other as Filter?
return filter?.id.equals(id)
}
enum class Kind(val kind: String) {
HOME("home"),
NOTIFICATIONS("notifications"),
PUBLIC("public"),
THREAD("thread"),
ACCOUNT("account");
companion object {
fun from(kind: String): Kind = values().firstOrNull { it.kind == kind } ?: PUBLIC
}
}
val action: Action
get() = Action.from(filterAction)
val kinds: List<Kind>
get() = context.map { Kind.from(it) }
}

View file

@ -0,0 +1,12 @@
package com.keylesspalace.tusky.entity
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class FilterKeyword(
val id: String,
val keyword: String,
@SerializedName("whole_word") val wholeWord: Boolean,
) : Parcelable

View file

@ -0,0 +1,9 @@
package com.keylesspalace.tusky.entity
import com.google.gson.annotations.SerializedName
data class FilterResult(
val filter: Filter,
@SerializedName("keyword_matches") val keywordMatches: List<String>?,
@SerializedName("status_matches") val statusMatches: String?,
)

View file

@ -0,0 +1,65 @@
/* Copyright 2018 Levi Bard
*
* 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 <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.entity
import com.google.gson.annotations.SerializedName
import java.util.Date
data class FilterV1(
val id: String,
val phrase: String,
val context: List<String>,
@SerializedName("expires_at") val expiresAt: Date?,
val irreversible: Boolean,
@SerializedName("whole_word") val wholeWord: Boolean
) {
companion object {
const val HOME = "home"
const val NOTIFICATIONS = "notifications"
const val PUBLIC = "public"
const val THREAD = "thread"
const val ACCOUNT = "account"
}
override fun hashCode(): Int {
return id.hashCode()
}
override fun equals(other: Any?): Boolean {
if (other !is FilterV1) {
return false
}
val filter = other as FilterV1?
return filter?.id.equals(id)
}
fun toFilter(): Filter {
return Filter(
id = id,
title = phrase,
context = context,
expiresAt = expiresAt,
filterAction = Filter.Action.WARN.action,
keywords = listOf(
FilterKeyword(
id = id,
keyword = phrase,
wholeWord = wholeWord,
)
)
)
}
}

View file

@ -51,6 +51,7 @@ data class Status(
val poll: Poll?,
val card: Card?,
val language: String?,
val filtered: List<FilterResult>?,
) {
val actionableId: String