mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-10-02 19:53:14 +02:00
Adds hidden words filter to search, hashtag and geotag feeds
Applies hidden words even to hashtags that were not included in the content of the event. Pre-process search to avoid showing and hiding posts after hidden words where processed by the UI.
This commit is contained in:
@@ -2043,7 +2043,10 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findUsersStartingWith(username: String): List<User> {
|
fun findUsersStartingWith(
|
||||||
|
username: String,
|
||||||
|
forAccount: Account?,
|
||||||
|
): List<User> {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
val key = decodePublicKeyAsHexOrNull(username)
|
val key = decodePublicKeyAsHexOrNull(username)
|
||||||
@@ -2056,13 +2059,19 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return users.filter { _, user: User ->
|
return users.filter { _, user: User ->
|
||||||
|
(
|
||||||
(user.anyNameStartsWith(username)) ||
|
(user.anyNameStartsWith(username)) ||
|
||||||
user.pubkeyHex.startsWith(username, true) ||
|
user.pubkeyHex.startsWith(username, true) ||
|
||||||
user.pubkeyNpub().startsWith(username, true)
|
user.pubkeyNpub().startsWith(username, true)
|
||||||
|
) &&
|
||||||
|
(forAccount == null || (!forAccount.isHidden(user) && !user.containsAny(forAccount.flowHiddenUsers.value.hiddenWordsCase)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findNotesStartingWith(text: String): List<Note> {
|
fun findNotesStartingWith(
|
||||||
|
text: String,
|
||||||
|
forAccount: Account,
|
||||||
|
): List<Note> {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
val key = decodeEventIdAsHexOrNull(text)
|
val key = decodeEventIdAsHexOrNull(text)
|
||||||
@@ -2089,11 +2098,19 @@ object LocalCache {
|
|||||||
note.idHex.startsWith(text, true) ||
|
note.idHex.startsWith(text, true) ||
|
||||||
note.idNote().startsWith(text, true)
|
note.idNote().startsWith(text, true)
|
||||||
) {
|
) {
|
||||||
|
if (!note.isHiddenFor(forAccount.flowHiddenUsers.value)) {
|
||||||
return@filter true
|
return@filter true
|
||||||
|
} else {
|
||||||
|
return@filter false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.event?.isContentEncoded() == false) {
|
if (note.event?.isContentEncoded() == false) {
|
||||||
|
if (!note.isHiddenFor(forAccount.flowHiddenUsers.value)) {
|
||||||
return@filter note.event?.content()?.contains(text, true) ?: false
|
return@filter note.event?.content()?.contains(text, true) ?: false
|
||||||
|
} else {
|
||||||
|
return@filter false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return@filter false
|
return@filter false
|
||||||
@@ -2112,11 +2129,19 @@ object LocalCache {
|
|||||||
if (addressable.event?.matchTag1With(text) == true ||
|
if (addressable.event?.matchTag1With(text) == true ||
|
||||||
addressable.idHex.startsWith(text, true)
|
addressable.idHex.startsWith(text, true)
|
||||||
) {
|
) {
|
||||||
|
if (!addressable.isHiddenFor(forAccount.flowHiddenUsers.value)) {
|
||||||
return@filter true
|
return@filter true
|
||||||
|
} else {
|
||||||
|
return@filter false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addressable.event?.isContentEncoded() == false) {
|
if (addressable.event?.isContentEncoded() == false) {
|
||||||
|
if (!addressable.isHiddenFor(forAccount.flowHiddenUsers.value)) {
|
||||||
return@filter addressable.event?.content()?.contains(text, true) ?: false
|
return@filter addressable.event?.content()?.contains(text, true) ?: false
|
||||||
|
} else {
|
||||||
|
return@filter false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return@filter false
|
return@filter false
|
||||||
|
@@ -771,29 +771,11 @@ open class Note(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (author?.toBestDisplayName()?.containsAny(accountChoices.hiddenWordsCase) == true) {
|
if (thisEvent.anyHashTag { it.containsAny(accountChoices.hiddenWordsCase) }) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (author?.profilePicture()?.containsAny(accountChoices.hiddenWordsCase) == true) {
|
if (author?.containsAny(accountChoices.hiddenWordsCase) == true) return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (author?.info?.banner?.containsAny(accountChoices.hiddenWordsCase) == true) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (author?.info?.about?.containsAny(accountChoices.hiddenWordsCase) == true) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (author?.info?.lud06?.containsAny(accountChoices.hiddenWordsCase) == true) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (author?.info?.lud16?.containsAny(accountChoices.hiddenWordsCase) == true) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@@ -45,6 +45,8 @@ import com.vitorpamplona.quartz.events.MetadataEvent
|
|||||||
import com.vitorpamplona.quartz.events.ReportEvent
|
import com.vitorpamplona.quartz.events.ReportEvent
|
||||||
import com.vitorpamplona.quartz.events.UserMetadata
|
import com.vitorpamplona.quartz.events.UserMetadata
|
||||||
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
||||||
|
import com.vitorpamplona.quartz.utils.DualCase
|
||||||
|
import com.vitorpamplona.quartz.utils.containsAny
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -370,6 +372,36 @@ class User(
|
|||||||
(it.event as ReportEvent).reportedAuthor().any { it.reportType == type }
|
(it.event as ReportEvent).reportedAuthor().any { it.reportType == type }
|
||||||
} != null
|
} != null
|
||||||
|
|
||||||
|
fun containsAny(hiddenWordsCase: List<DualCase>): Boolean {
|
||||||
|
if (hiddenWordsCase.isEmpty()) return false
|
||||||
|
|
||||||
|
if (toBestDisplayName().containsAny(hiddenWordsCase)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profilePicture()?.containsAny(hiddenWordsCase) == true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info?.banner?.containsAny(hiddenWordsCase) == true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info?.about?.containsAny(hiddenWordsCase) == true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info?.lud06?.containsAny(hiddenWordsCase) == true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info?.lud16?.containsAny(hiddenWordsCase) == true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
fun anyNameStartsWith(username: String): Boolean = info?.anyNameStartsWith(username) ?: false
|
fun anyNameStartsWith(username: String): Boolean = info?.anyNameStartsWith(username) ?: false
|
||||||
|
|
||||||
var liveSet: UserLiveSet? = null
|
var liveSet: UserLiveSet? = null
|
||||||
|
@@ -274,7 +274,7 @@ open class EditPostViewModel : ViewModel() {
|
|||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
userSuggestions =
|
userSuggestions =
|
||||||
LocalCache
|
LocalCache
|
||||||
.findUsersStartingWith(lastWord.removePrefix("@"))
|
.findUsersStartingWith(lastWord.removePrefix("@"), account)
|
||||||
.sortedWith(compareBy({ account?.isFollowing(it) }, { it.toBestDisplayName() }, { it.pubkeyHex }))
|
.sortedWith(compareBy({ account?.isFollowing(it) }, { it.toBestDisplayName() }, { it.pubkeyHex }))
|
||||||
.reversed()
|
.reversed()
|
||||||
}
|
}
|
||||||
|
@@ -1002,7 +1002,7 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
userSuggestions =
|
userSuggestions =
|
||||||
LocalCache
|
LocalCache
|
||||||
.findUsersStartingWith(lastWord.removePrefix("@"))
|
.findUsersStartingWith(lastWord.removePrefix("@"), account)
|
||||||
.sortedWith(compareBy({ account?.isFollowing(it) }, { it.toBestDisplayName() }, { it.pubkeyHex }))
|
.sortedWith(compareBy({ account?.isFollowing(it) }, { it.toBestDisplayName() }, { it.pubkeyHex }))
|
||||||
.reversed()
|
.reversed()
|
||||||
}
|
}
|
||||||
@@ -1031,7 +1031,7 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
userSuggestions =
|
userSuggestions =
|
||||||
LocalCache
|
LocalCache
|
||||||
.findUsersStartingWith(lastWord.removePrefix("@"))
|
.findUsersStartingWith(lastWord.removePrefix("@"), account)
|
||||||
.sortedWith(compareBy({ account?.isFollowing(it) }, { it.toBestDisplayName() }, { it.pubkeyHex }))
|
.sortedWith(compareBy({ account?.isFollowing(it) }, { it.toBestDisplayName() }, { it.pubkeyHex }))
|
||||||
.reversed()
|
.reversed()
|
||||||
}
|
}
|
||||||
@@ -1059,7 +1059,7 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
userSuggestions =
|
userSuggestions =
|
||||||
LocalCache
|
LocalCache
|
||||||
.findUsersStartingWith(lastWord.removePrefix("@"))
|
.findUsersStartingWith(lastWord.removePrefix("@"), account)
|
||||||
.sortedWith(
|
.sortedWith(
|
||||||
compareBy(
|
compareBy(
|
||||||
{ account?.isFollowing(it) },
|
{ account?.isFollowing(it) },
|
||||||
|
@@ -64,6 +64,7 @@ class GeoHashFeedFilter(
|
|||||||
it.event is AudioHeaderEvent
|
it.event is AudioHeaderEvent
|
||||||
) &&
|
) &&
|
||||||
it.event?.isTaggedGeoHash(geoTag) == true &&
|
it.event?.isTaggedGeoHash(geoTag) == true &&
|
||||||
|
!it.isHiddenFor(account.flowHiddenUsers.value) &&
|
||||||
account.isAcceptable(it)
|
account.isAcceptable(it)
|
||||||
|
|
||||||
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
|
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
|
||||||
|
@@ -68,6 +68,7 @@ class HashtagFeedFilter(
|
|||||||
it.event is AudioHeaderEvent
|
it.event is AudioHeaderEvent
|
||||||
) &&
|
) &&
|
||||||
it.event?.isTaggedHash(hashTag) == true &&
|
it.event?.isTaggedHash(hashTag) == true &&
|
||||||
|
!it.isHiddenFor(account.flowHiddenUsers.value) &&
|
||||||
account.isAcceptable(it)
|
account.isAcceptable(it)
|
||||||
|
|
||||||
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
|
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
|
||||||
|
@@ -75,7 +75,7 @@ class SearchBarViewModel(
|
|||||||
_hashtagResults.emit(findHashtags(searchValue))
|
_hashtagResults.emit(findHashtags(searchValue))
|
||||||
_searchResultsUsers.emit(
|
_searchResultsUsers.emit(
|
||||||
LocalCache
|
LocalCache
|
||||||
.findUsersStartingWith(searchValue)
|
.findUsersStartingWith(searchValue, account)
|
||||||
.sortedWith(
|
.sortedWith(
|
||||||
compareBy(
|
compareBy(
|
||||||
{ it.toBestDisplayName().startsWith(searchValue, true) },
|
{ it.toBestDisplayName().startsWith(searchValue, true) },
|
||||||
@@ -86,7 +86,7 @@ class SearchBarViewModel(
|
|||||||
)
|
)
|
||||||
_searchResultsNotes.emit(
|
_searchResultsNotes.emit(
|
||||||
LocalCache
|
LocalCache
|
||||||
.findNotesStartingWith(searchValue)
|
.findNotesStartingWith(searchValue, account)
|
||||||
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
|
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
|
||||||
.reversed(),
|
.reversed(),
|
||||||
)
|
)
|
||||||
|
@@ -36,7 +36,6 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
|||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -178,7 +177,7 @@ fun WatchAccountForSearchScreen(accountViewModel: AccountViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(FlowPreview::class, ExperimentalMaterial3Api::class)
|
@OptIn(FlowPreview::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun SearchBar(
|
private fun SearchBar(
|
||||||
searchBarViewModel: SearchBarViewModel,
|
searchBarViewModel: SearchBarViewModel,
|
||||||
|
@@ -107,6 +107,19 @@ open class Event(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun anyHashTag(onEach: (str: String) -> Boolean) = anyTagged("t", onEach)
|
||||||
|
|
||||||
|
private fun anyTagged(
|
||||||
|
tagName: String,
|
||||||
|
onEach: (str: String) -> Boolean,
|
||||||
|
) = tags.any {
|
||||||
|
if (it.size > 1 && it[0] == tagName) {
|
||||||
|
onEach(it[1])
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun <R> mapTaggedEvent(map: (eventId: HexKey) -> R) = mapTagged("e", map)
|
override fun <R> mapTaggedEvent(map: (eventId: HexKey) -> R) = mapTagged("e", map)
|
||||||
|
|
||||||
override fun <R> mapTaggedAddress(map: (address: String) -> R) = mapTagged("a", map)
|
override fun <R> mapTaggedAddress(map: (address: String) -> R) = mapTagged("a", map)
|
||||||
|
@@ -121,6 +121,8 @@ interface EventInterface {
|
|||||||
|
|
||||||
fun forEachHashTag(onEach: (eventId: HexKey) -> Unit)
|
fun forEachHashTag(onEach: (eventId: HexKey) -> Unit)
|
||||||
|
|
||||||
|
fun anyHashTag(onEach: (str: String) -> Boolean): Boolean
|
||||||
|
|
||||||
fun <R> mapTaggedEvent(map: (eventId: HexKey) -> R): List<R>
|
fun <R> mapTaggedEvent(map: (eventId: HexKey) -> R): List<R>
|
||||||
|
|
||||||
fun <R> mapTaggedAddress(map: (address: String) -> R): List<R>
|
fun <R> mapTaggedAddress(map: (address: String) -> R): List<R>
|
||||||
|
Reference in New Issue
Block a user