Moves all hashtags to lowercase and unfollows with ignore case just in case some other client added in uppercase

This commit is contained in:
Vitor Pamplona
2025-11-15 15:20:56 -05:00
parent a1879a93df
commit 7742e27780
11 changed files with 23 additions and 12 deletions
@@ -89,9 +89,9 @@ class HashtagListState(
val hashtagList = getHashtagList() val hashtagList = getHashtagList()
return if (hashtagList == null) { return if (hashtagList == null) {
HashtagListEvent.create(hashtag, true, signer) HashtagListEvent.create(hashtag.lowercase(), true, signer)
} else { } else {
HashtagListEvent.add(hashtagList, hashtag, true, signer) HashtagListEvent.add(hashtagList, hashtag.lowercase(), true, signer)
} }
} }
@@ -141,7 +141,7 @@ fun uriToRoute(
return Route.Notification return Route.Notification
} }
if (isHashtagRoute(uri)) { if (isHashtagRoute(uri)) {
return Route.Hashtag(uri.removePrefix("nostr:").removePrefix("hashtag?id=")) return Route.Hashtag(uri.removePrefix("nostr:").removePrefix("hashtag?id=").lowercase())
} }
val nip19 = Nip19Parser.uriToRoute(uri)?.entity val nip19 = Nip19Parser.uriToRoute(uri)?.entity
@@ -692,7 +692,7 @@ fun HashTag(
modifier = modifier =
remember { remember {
Modifier.clickable { Modifier.clickable {
nav.nav(Route.Hashtag(segment.hashtag)) nav.nav(Route.Hashtag(segment.hashtag.lowercase()))
} }
}, },
inlineContent = inlineContent =
@@ -86,6 +86,6 @@ private fun DisplayTagList(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1, maxLines = 1,
) { ) {
nav.nav(Route.Hashtag(firstTag)) nav.nav(Route.Hashtag(firstTag.lowercase()))
} }
} }
@@ -96,7 +96,7 @@ fun DisplayUncitedHashtags(
unusedHashtags.forEach { hashtag -> unusedHashtags.forEach { hashtag ->
ClickableTextColor( ClickableTextColor(
text = "#$hashtag ", text = "#$hashtag ",
onClick = { nav.nav(Route.Hashtag(hashtag)) }, onClick = { nav.nav(Route.Hashtag(hashtag.lowercase())) },
linkColor = MaterialTheme.colorScheme.lessImportantLink, linkColor = MaterialTheme.colorScheme.lessImportantLink,
) )
} }
@@ -55,7 +55,7 @@ fun DisplayHashtagExternalId(
nav: INav, nav: INav,
) { ) {
DisplayHashtagExternalId(externalId.topic) { DisplayHashtagExternalId(externalId.topic) {
nav.nav(Route.Hashtag(externalId.topic)) nav.nav(Route.Hashtag(externalId.topic.lowercase()))
} }
} }
@@ -57,7 +57,7 @@ fun TabFollowedTags(
HashtagHeader( HashtagHeader(
tag = hashtag, tag = hashtag,
account = accountViewModel, account = accountViewModel,
onClick = { nav.nav(Route.Hashtag(hashtag)) }, onClick = { nav.nav(Route.Hashtag(hashtag.lowercase())) },
) )
HorizontalDivider( HorizontalDivider(
thickness = DividerThickness, thickness = DividerThickness,
@@ -257,7 +257,7 @@ private fun DisplaySearchResults(
hashTags, hashTags,
key = { _, item -> "#$item" }, key = { _, item -> "#$item" },
) { _, item -> ) { _, item ->
HashtagLine(item) { nav.nav(Route.Hashtag(item)) } HashtagLine(item.lowercase()) { nav.nav(Route.Hashtag(item.lowercase())) }
HorizontalDivider( HorizontalDivider(
modifier = Modifier.padding(top = 10.dp), modifier = Modifier.padding(top = 10.dp),
@@ -24,6 +24,7 @@ import com.vitorpamplona.quartz.nip01Core.core.Tag
import com.vitorpamplona.quartz.nip01Core.core.TagArray import com.vitorpamplona.quartz.nip01Core.core.TagArray
import com.vitorpamplona.quartz.utils.startsWith import com.vitorpamplona.quartz.utils.startsWith
import com.vitorpamplona.quartz.utils.startsWithAny import com.vitorpamplona.quartz.utils.startsWithAny
import com.vitorpamplona.quartz.utils.startsWithIgnoreCase
inline fun TagArray.filterToArray(predicate: (Array<String>) -> Boolean): TagArray = filterTo(ArrayList(), predicate).toTypedArray() inline fun TagArray.filterToArray(predicate: (Array<String>) -> Boolean): TagArray = filterTo(ArrayList(), predicate).toTypedArray()
@@ -31,6 +32,8 @@ inline fun TagArray.remove(predicate: (Array<String>) -> Boolean): TagArray = fi
fun TagArray.remove(startsWith: Array<String>): TagArray = filterNotTo(ArrayList(this.size), { it.startsWith(startsWith) }).toTypedArray() fun TagArray.remove(startsWith: Array<String>): TagArray = filterNotTo(ArrayList(this.size), { it.startsWith(startsWith) }).toTypedArray()
fun TagArray.removeIgnoreCase(startsWith: Array<String>): TagArray = filterNotTo(ArrayList(this.size), { it.startsWithIgnoreCase(startsWith) }).toTypedArray()
fun <R> TagArray.removeParsing( fun <R> TagArray.removeParsing(
transform: (Tag) -> R, transform: (Tag) -> R,
equalsTo: R, equalsTo: R,
@@ -36,8 +36,8 @@ import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEvent import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEvent
import com.vitorpamplona.quartz.nip51Lists.encryption.PrivateTagsInContent import com.vitorpamplona.quartz.nip51Lists.encryption.PrivateTagsInContent
import com.vitorpamplona.quartz.nip51Lists.encryption.signNip51List import com.vitorpamplona.quartz.nip51Lists.encryption.signNip51List
import com.vitorpamplona.quartz.nip51Lists.remove
import com.vitorpamplona.quartz.nip51Lists.removeAny import com.vitorpamplona.quartz.nip51Lists.removeAny
import com.vitorpamplona.quartz.nip51Lists.removeIgnoreCase
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable @Immutable
@@ -140,8 +140,8 @@ class HashtagListEvent(
): HashtagListEvent { ): HashtagListEvent {
val privateTags = earlierVersion.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException() val privateTags = earlierVersion.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
return resign( return resign(
privateTags = privateTags.remove(HashtagTag.assemble(hashtag)), privateTags = privateTags.removeIgnoreCase(HashtagTag.assemble(hashtag)),
tags = earlierVersion.tags.remove(HashtagTag.assemble(hashtag)), tags = earlierVersion.tags.removeIgnoreCase(HashtagTag.assemble(hashtag)),
signer = signer, signer = signer,
createdAt = createdAt, createdAt = createdAt,
) )
@@ -40,6 +40,14 @@ fun Array<String>.startsWith(startsWith: Array<String>): Boolean {
return true return true
} }
fun Array<String>.startsWithIgnoreCase(startsWith: Array<String>): Boolean {
if (startsWith.size > this.size) return false
for (tagIdx in startsWith.indices) {
if (!startsWith[tagIdx].equals(this[tagIdx], ignoreCase = true)) return false
}
return true
}
fun Array<String>.startsWithAny(startsWithList: List<Array<String>>): Boolean = startsWithList.any { startsWith(it) } fun Array<String>.startsWithAny(startsWithList: List<Array<String>>): Boolean = startsWithList.any { startsWith(it) }
public inline fun <T, R> Array<out T>.lastNotNullOfOrNull(transform: (T) -> R?): R? { public inline fun <T, R> Array<out T>.lastNotNullOfOrNull(transform: (T) -> R?): R? {