Mute keywords
This commit is contained in:
@@ -105,6 +105,7 @@ class Account(
|
|||||||
data class LiveHiddenUsers(
|
data class LiveHiddenUsers(
|
||||||
val hiddenUsers: ImmutableSet<String>,
|
val hiddenUsers: ImmutableSet<String>,
|
||||||
val spammers: ImmutableSet<String>,
|
val spammers: ImmutableSet<String>,
|
||||||
|
val hiddenWords: ImmutableSet<String>,
|
||||||
val showSensitiveContent: Boolean?
|
val showSensitiveContent: Boolean?
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -129,23 +130,25 @@ class Account(
|
|||||||
|
|
||||||
LiveHiddenUsers(
|
LiveHiddenUsers(
|
||||||
hiddenUsers = persistentSetOf(),
|
hiddenUsers = persistentSetOf(),
|
||||||
spammers = localLive?.account?.transientHiddenUsers
|
hiddenWords = persistentSetOf(),
|
||||||
?: persistentSetOf(),
|
spammers = localLive?.account?.transientHiddenUsers ?: persistentSetOf(),
|
||||||
showSensitiveContent = showSensitiveContent
|
showSensitiveContent = showSensitiveContent
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
blockList.decryptedContent = ExternalSignerUtils.cachedDecryptedContent[blockList.id]
|
blockList.decryptedContent = ExternalSignerUtils.cachedDecryptedContent[blockList.id]
|
||||||
val liveBlockedUsers = blockList.publicAndPrivateUsers(blockList.decryptedContent ?: "")
|
val liveBlockedUsers = blockList.publicAndPrivateUsers(blockList.decryptedContent ?: "")
|
||||||
|
val liveBlockedWords = blockList.publicAndPrivateWords(blockList.decryptedContent ?: "")
|
||||||
LiveHiddenUsers(
|
LiveHiddenUsers(
|
||||||
hiddenUsers = liveBlockedUsers,
|
hiddenUsers = liveBlockedUsers,
|
||||||
spammers = localLive?.account?.transientHiddenUsers
|
hiddenWords = liveBlockedWords,
|
||||||
?: persistentSetOf(),
|
spammers = localLive?.account?.transientHiddenUsers ?: persistentSetOf(),
|
||||||
showSensitiveContent = showSensitiveContent
|
showSensitiveContent = showSensitiveContent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LiveHiddenUsers(
|
LiveHiddenUsers(
|
||||||
hiddenUsers = persistentSetOf(),
|
hiddenUsers = persistentSetOf(),
|
||||||
|
hiddenWords = persistentSetOf(),
|
||||||
spammers = localLive?.account?.transientHiddenUsers
|
spammers = localLive?.account?.transientHiddenUsers
|
||||||
?: persistentSetOf(),
|
?: persistentSetOf(),
|
||||||
showSensitiveContent = showSensitiveContent
|
showSensitiveContent = showSensitiveContent
|
||||||
@@ -153,8 +156,10 @@ class Account(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val liveBlockedUsers = blockList?.publicAndPrivateUsers(keyPair.privKey)
|
val liveBlockedUsers = blockList?.publicAndPrivateUsers(keyPair.privKey)
|
||||||
|
val liveBlockedWords = blockList?.publicAndPrivateWords(keyPair.privKey)
|
||||||
LiveHiddenUsers(
|
LiveHiddenUsers(
|
||||||
hiddenUsers = liveBlockedUsers ?: persistentSetOf(),
|
hiddenUsers = liveBlockedUsers ?: persistentSetOf(),
|
||||||
|
hiddenWords = liveBlockedWords ?: persistentSetOf(),
|
||||||
spammers = localLive?.account?.transientHiddenUsers ?: persistentSetOf(),
|
spammers = localLive?.account?.transientHiddenUsers ?: persistentSetOf(),
|
||||||
showSensitiveContent = showSensitiveContent
|
showSensitiveContent = showSensitiveContent
|
||||||
)
|
)
|
||||||
@@ -2276,6 +2281,157 @@ class Account(
|
|||||||
return returningList
|
return returningList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hideWord(word: String) {
|
||||||
|
val blockList = migrateHiddenUsersIfNeeded(getBlockList())
|
||||||
|
if (loginWithExternalSigner) {
|
||||||
|
val id = blockList?.id
|
||||||
|
val encryptedContent = if (id == null) {
|
||||||
|
val privateTags = listOf(listOf("word", word))
|
||||||
|
val msg = Event.mapper.writeValueAsString(privateTags)
|
||||||
|
|
||||||
|
ExternalSignerUtils.encrypt(msg, keyPair.pubKey.toHexKey(), "encrypted")
|
||||||
|
val encryptedContent = ExternalSignerUtils.content["encrypted"] ?: ""
|
||||||
|
ExternalSignerUtils.content.remove("encrypted")
|
||||||
|
if (encryptedContent.isBlank()) return
|
||||||
|
encryptedContent
|
||||||
|
} else {
|
||||||
|
var decryptedContent = ExternalSignerUtils.cachedDecryptedContent[id]
|
||||||
|
if (decryptedContent == null) {
|
||||||
|
ExternalSignerUtils.decrypt(blockList.content, keyPair.pubKey.toHexKey(), id)
|
||||||
|
val content = ExternalSignerUtils.content[id] ?: ""
|
||||||
|
if (content.isBlank()) return
|
||||||
|
decryptedContent = content
|
||||||
|
}
|
||||||
|
|
||||||
|
val privateTags = blockList.privateTagsOrEmpty(decryptedContent).plus(element = listOf("word", word))
|
||||||
|
val msg = Event.mapper.writeValueAsString(privateTags)
|
||||||
|
ExternalSignerUtils.encrypt(msg, keyPair.pubKey.toHexKey(), id)
|
||||||
|
val eventContent = ExternalSignerUtils.content[id] ?: ""
|
||||||
|
if (eventContent.isBlank()) return
|
||||||
|
eventContent
|
||||||
|
}
|
||||||
|
|
||||||
|
var event = if (blockList != null) {
|
||||||
|
PeopleListEvent.addWord(
|
||||||
|
earlierVersion = blockList,
|
||||||
|
word = word,
|
||||||
|
isPrivate = true,
|
||||||
|
pubKey = keyPair.pubKey.toHexKey(),
|
||||||
|
encryptedContent
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
PeopleListEvent.createListWithWord(
|
||||||
|
name = PeopleListEvent.blockList,
|
||||||
|
word = word,
|
||||||
|
isPrivate = true,
|
||||||
|
pubKey = keyPair.pubKey.toHexKey(),
|
||||||
|
encryptedContent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalSignerUtils.openSigner(event)
|
||||||
|
|
||||||
|
val eventContent = ExternalSignerUtils.content[event.id] ?: ""
|
||||||
|
if (eventContent.isBlank()) return
|
||||||
|
event = PeopleListEvent(
|
||||||
|
event.id,
|
||||||
|
event.pubKey,
|
||||||
|
event.createdAt,
|
||||||
|
event.tags,
|
||||||
|
event.content,
|
||||||
|
eventContent
|
||||||
|
)
|
||||||
|
|
||||||
|
Client.send(event)
|
||||||
|
LocalCache.consume(event)
|
||||||
|
} else {
|
||||||
|
val event = if (blockList != null) {
|
||||||
|
PeopleListEvent.addWord(
|
||||||
|
earlierVersion = blockList,
|
||||||
|
word = word,
|
||||||
|
isPrivate = true,
|
||||||
|
privateKey = keyPair.privKey!!
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
PeopleListEvent.createListWithWord(
|
||||||
|
name = PeopleListEvent.blockList,
|
||||||
|
word = word,
|
||||||
|
isPrivate = true,
|
||||||
|
privateKey = keyPair.privKey!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.send(event)
|
||||||
|
LocalCache.consume(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
live.invalidateData()
|
||||||
|
saveable.invalidateData()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showWord(word: String) {
|
||||||
|
val blockList = migrateHiddenUsersIfNeeded(getBlockList())
|
||||||
|
|
||||||
|
if (blockList != null) {
|
||||||
|
if (loginWithExternalSigner) {
|
||||||
|
val content = blockList.content
|
||||||
|
val encryptedContent = if (content.isBlank()) {
|
||||||
|
val privateTags = listOf(listOf("word", word))
|
||||||
|
val msg = Event.mapper.writeValueAsString(privateTags)
|
||||||
|
|
||||||
|
ExternalSignerUtils.encrypt(msg, keyPair.pubKey.toHexKey(), blockList.id)
|
||||||
|
val eventContent = ExternalSignerUtils.content[blockList.id] ?: ""
|
||||||
|
if (eventContent.isBlank()) return
|
||||||
|
eventContent
|
||||||
|
} else {
|
||||||
|
var decryptedContent = ExternalSignerUtils.cachedDecryptedContent[blockList.id]
|
||||||
|
if (decryptedContent == null) {
|
||||||
|
ExternalSignerUtils.decrypt(blockList.content, keyPair.pubKey.toHexKey(), blockList.id)
|
||||||
|
val eventContent = ExternalSignerUtils.content[blockList.id] ?: ""
|
||||||
|
if (eventContent.isBlank()) return
|
||||||
|
decryptedContent = eventContent
|
||||||
|
}
|
||||||
|
val privateTags = blockList.privateTagsOrEmpty(decryptedContent).minus(element = listOf("word", word))
|
||||||
|
val msg = Event.mapper.writeValueAsString(privateTags)
|
||||||
|
ExternalSignerUtils.encrypt(msg, keyPair.pubKey.toHexKey(), blockList.id)
|
||||||
|
val eventContent = ExternalSignerUtils.content[blockList.id] ?: ""
|
||||||
|
if (eventContent.isBlank()) return
|
||||||
|
eventContent
|
||||||
|
}
|
||||||
|
|
||||||
|
var event = PeopleListEvent.removeTag(
|
||||||
|
earlierVersion = blockList,
|
||||||
|
tag = word,
|
||||||
|
isPrivate = true,
|
||||||
|
pubKey = keyPair.pubKey.toHexKey(),
|
||||||
|
encryptedContent
|
||||||
|
)
|
||||||
|
|
||||||
|
ExternalSignerUtils.openSigner(event)
|
||||||
|
val eventContent = ExternalSignerUtils.content[event.id] ?: ""
|
||||||
|
if (eventContent.isBlank()) return
|
||||||
|
event = PeopleListEvent.create(event, eventContent)
|
||||||
|
|
||||||
|
Client.send(event)
|
||||||
|
LocalCache.consume(event)
|
||||||
|
} else {
|
||||||
|
val event = PeopleListEvent.removeWord(
|
||||||
|
earlierVersion = blockList,
|
||||||
|
word = word,
|
||||||
|
isPrivate = true,
|
||||||
|
privateKey = keyPair.privKey!!
|
||||||
|
)
|
||||||
|
|
||||||
|
Client.send(event)
|
||||||
|
LocalCache.consume(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transientHiddenUsers = (transientHiddenUsers - word).toImmutableSet()
|
||||||
|
live.invalidateData()
|
||||||
|
saveable.invalidateData()
|
||||||
|
}
|
||||||
|
|
||||||
fun hideUser(pubkeyHex: String) {
|
fun hideUser(pubkeyHex: String) {
|
||||||
val blockList = migrateHiddenUsersIfNeeded(getBlockList())
|
val blockList = migrateHiddenUsersIfNeeded(getBlockList())
|
||||||
if (loginWithExternalSigner) {
|
if (loginWithExternalSigner) {
|
||||||
@@ -2284,7 +2440,7 @@ class Account(
|
|||||||
val privateTags = listOf(listOf("p", pubkeyHex))
|
val privateTags = listOf(listOf("p", pubkeyHex))
|
||||||
val msg = Event.mapper.writeValueAsString(privateTags)
|
val msg = Event.mapper.writeValueAsString(privateTags)
|
||||||
|
|
||||||
ExternalSignerUtils.encrypt(msg, keyPair.pubKey.toHexKey(), "encrypt")
|
ExternalSignerUtils.encrypt(msg, keyPair.pubKey.toHexKey(), "encrypted")
|
||||||
val encryptedContent = ExternalSignerUtils.content["encrypted"] ?: ""
|
val encryptedContent = ExternalSignerUtils.content["encrypted"] ?: ""
|
||||||
ExternalSignerUtils.content.remove("encrypted")
|
ExternalSignerUtils.content.remove("encrypted")
|
||||||
if (encryptedContent.isBlank()) return
|
if (encryptedContent.isBlank()) return
|
||||||
@@ -2393,9 +2549,9 @@ class Account(
|
|||||||
eventContent
|
eventContent
|
||||||
}
|
}
|
||||||
|
|
||||||
var event = PeopleListEvent.addUser(
|
var event = PeopleListEvent.removeTag(
|
||||||
earlierVersion = blockList,
|
earlierVersion = blockList,
|
||||||
pubKeyHex = pubkeyHex,
|
tag = pubkeyHex,
|
||||||
isPrivate = true,
|
isPrivate = true,
|
||||||
pubKey = keyPair.pubKey.toHexKey(),
|
pubKey = keyPair.pubKey.toHexKey(),
|
||||||
encryptedContent
|
encryptedContent
|
||||||
|
|||||||
@@ -673,16 +673,24 @@ open class Note(val idHex: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isHiddenFor(accountChoices: Account.LiveHiddenUsers): Boolean {
|
fun isHiddenFor(accountChoices: Account.LiveHiddenUsers): Boolean {
|
||||||
if (event == null) return false
|
val thisEvent = event ?: return false
|
||||||
|
|
||||||
val isBoostedNoteHidden = if (event is GenericRepostEvent || event is RepostEvent || event is CommunityPostApprovalEvent) {
|
val isBoostedNoteHidden = if (thisEvent is GenericRepostEvent || thisEvent is RepostEvent || thisEvent is CommunityPostApprovalEvent) {
|
||||||
replyTo?.lastOrNull()?.isHiddenFor(accountChoices) ?: false
|
replyTo?.lastOrNull()?.isHiddenFor(accountChoices) ?: false
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
val isSensitive = event?.isSensitive() ?: false
|
val isHiddenByWord = if (thisEvent is BaseTextNoteEvent) {
|
||||||
return isBoostedNoteHidden ||
|
accountChoices.hiddenWords.any {
|
||||||
|
thisEvent.content.contains(it, true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
val isSensitive = thisEvent.isSensitive()
|
||||||
|
return isBoostedNoteHidden || isHiddenByWord ||
|
||||||
accountChoices.hiddenUsers.contains(author?.pubkeyHex) ||
|
accountChoices.hiddenUsers.contains(author?.pubkeyHex) ||
|
||||||
accountChoices.spammers.contains(author?.pubkeyHex) ||
|
accountChoices.spammers.contains(author?.pubkeyHex) ||
|
||||||
(isSensitive && accountChoices.showSensitiveContent == false)
|
(isSensitive && accountChoices.showSensitiveContent == false)
|
||||||
|
|||||||
@@ -32,6 +32,32 @@ class HiddenAccountsFeedFilter(val account: Account) : FeedFilter<User>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HiddenWordsFeedFilter(val account: Account) : FeedFilter<String>() {
|
||||||
|
override fun feedKey(): String {
|
||||||
|
return account.userProfile().pubkeyHex
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showHiddenKey(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun feed(): List<String> {
|
||||||
|
val blockList = account.getBlockList()
|
||||||
|
val decryptedContent = blockList?.decryptedContent ?: ""
|
||||||
|
if (account.loginWithExternalSigner) {
|
||||||
|
if (decryptedContent.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
return blockList
|
||||||
|
?.publicAndPrivateWords(decryptedContent)?.toList()
|
||||||
|
?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockList
|
||||||
|
?.publicAndPrivateWords(account.keyPair.privKey)?.toList()
|
||||||
|
?: emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SpammerAccountsFeedFilter(val account: Account) : FeedFilter<User>() {
|
class SpammerAccountsFeedFilter(val account: Account) : FeedFilter<User>() {
|
||||||
override fun feedKey(): String {
|
override fun feedKey(): String {
|
||||||
return account.userProfile().pubkeyHex
|
return account.userProfile().pubkeyHex
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ private fun EditStatusBox(baseAccountUser: User, accountViewModel: AccountViewMo
|
|||||||
singleLine = true,
|
singleLine = true,
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
if (hasChanged) {
|
if (hasChanged) {
|
||||||
UserStatusSendButton() {
|
SendButton() {
|
||||||
accountViewModel.createStatus(currentStatus.value)
|
accountViewModel.createStatus(currentStatus.value)
|
||||||
focusManager.clearFocus(true)
|
focusManager.clearFocus(true)
|
||||||
}
|
}
|
||||||
@@ -338,7 +338,7 @@ private fun EditStatusBox(baseAccountUser: User, accountViewModel: AccountViewMo
|
|||||||
singleLine = true,
|
singleLine = true,
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
if (hasChanged) {
|
if (hasChanged) {
|
||||||
UserStatusSendButton() {
|
SendButton() {
|
||||||
accountViewModel.updateStatus(it, thisStatus.value)
|
accountViewModel.updateStatus(it, thisStatus.value)
|
||||||
focusManager.clearFocus(true)
|
focusManager.clearFocus(true)
|
||||||
}
|
}
|
||||||
@@ -356,7 +356,7 @@ private fun EditStatusBox(baseAccountUser: User, accountViewModel: AccountViewMo
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UserStatusSendButton(onClick: () -> Unit) {
|
fun SendButton(onClick: () -> Unit) {
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Size26Modifier,
|
modifier = Size26Modifier,
|
||||||
onClick = onClick
|
onClick = onClick
|
||||||
|
|||||||
@@ -258,9 +258,7 @@ fun FeedError(errorMessage: String, onRefresh: () -> Unit) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun FeedEmpty(onRefresh: () -> Unit) {
|
fun FeedEmpty(onRefresh: () -> Unit) {
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier.fillMaxSize(),
|
||||||
.fillMaxHeight()
|
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
sealed class StringFeedState {
|
||||||
|
object Loading : StringFeedState()
|
||||||
|
class Loaded(val feed: MutableState<ImmutableList<String>>) : StringFeedState()
|
||||||
|
object Empty : StringFeedState()
|
||||||
|
class FeedError(val errorMessage: String) : StringFeedState()
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.OutlinedButton
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefreshingFeedStringFeedView(
|
||||||
|
viewModel: StringFeedViewModel,
|
||||||
|
enablePullRefresh: Boolean = true,
|
||||||
|
inner: @Composable (String) -> Unit
|
||||||
|
) {
|
||||||
|
RefresheableView(viewModel, enablePullRefresh) {
|
||||||
|
StringFeedView(viewModel, inner = inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StringFeedView(
|
||||||
|
viewModel: StringFeedViewModel,
|
||||||
|
pre: (@Composable () -> Unit)? = null,
|
||||||
|
post: (@Composable () -> Unit)? = null,
|
||||||
|
inner: @Composable (String) -> Unit
|
||||||
|
) {
|
||||||
|
val feedState by viewModel.feedContent.collectAsState()
|
||||||
|
|
||||||
|
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
||||||
|
when (state) {
|
||||||
|
is StringFeedState.Empty -> {
|
||||||
|
StringFeedEmpty(pre, post) {
|
||||||
|
viewModel.invalidateData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is StringFeedState.FeedError -> {
|
||||||
|
FeedError(state.errorMessage) {
|
||||||
|
viewModel.invalidateData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is StringFeedState.Loaded -> {
|
||||||
|
FeedLoaded(state, pre, post, inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
is StringFeedState.Loading -> {
|
||||||
|
LoadingFeed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StringFeedEmpty(
|
||||||
|
pre: (@Composable () -> Unit)? = null,
|
||||||
|
post: (@Composable () -> Unit)? = null,
|
||||||
|
onRefresh: () -> Unit
|
||||||
|
) {
|
||||||
|
Column() {
|
||||||
|
pre?.let { it() }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier.weight(1f).fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.feed_is_empty))
|
||||||
|
OutlinedButton(onClick = onRefresh) {
|
||||||
|
Text(text = stringResource(R.string.refresh))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post?.let { it() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FeedLoaded(
|
||||||
|
state: StringFeedState.Loaded,
|
||||||
|
pre: (@Composable () -> Unit)? = null,
|
||||||
|
post: (@Composable () -> Unit)? = null,
|
||||||
|
inner: @Composable (String) -> Unit
|
||||||
|
) {
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
top = 10.dp,
|
||||||
|
bottom = 10.dp
|
||||||
|
),
|
||||||
|
state = listState
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
pre?.let { it() }
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsIndexed(state.feed.value, key = { _, item -> item }) { _, item ->
|
||||||
|
inner(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
post?.let { it() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.HiddenWordsFeedFilter
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class NostrHiddenWordsFeedViewModel(val account: Account) : StringFeedViewModel(
|
||||||
|
HiddenWordsFeedFilter(account)
|
||||||
|
) {
|
||||||
|
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||||
|
override fun <NostrHiddenWordsFeedViewModel : ViewModel> create(modelClass: Class<NostrHiddenWordsFeedViewModel>): NostrHiddenWordsFeedViewModel {
|
||||||
|
return NostrHiddenWordsFeedViewModel(account) as NostrHiddenWordsFeedViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
open class StringFeedViewModel(val dataSource: FeedFilter<String>) : ViewModel(), InvalidatableViewModel {
|
||||||
|
private val _feedContent = MutableStateFlow<StringFeedState>(StringFeedState.Loading)
|
||||||
|
val feedContent = _feedContent.asStateFlow()
|
||||||
|
|
||||||
|
private fun refresh() {
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
refreshSuspended()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshSuspended() {
|
||||||
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
val notes = dataSource.loadTop().toImmutableList()
|
||||||
|
|
||||||
|
val oldNotesState = _feedContent.value
|
||||||
|
if (oldNotesState is StringFeedState.Loaded) {
|
||||||
|
// Using size as a proxy for has changed.
|
||||||
|
if (!equalImmutableLists(notes, oldNotesState.feed.value)) {
|
||||||
|
updateFeed(notes)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateFeed(notes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateFeed(notes: ImmutableList<String>) {
|
||||||
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
|
val currentState = _feedContent.value
|
||||||
|
if (notes.isEmpty()) {
|
||||||
|
_feedContent.update { StringFeedState.Empty }
|
||||||
|
} else if (currentState is StringFeedState.Loaded) {
|
||||||
|
// updates the current list
|
||||||
|
currentState.feed.value = notes
|
||||||
|
} else {
|
||||||
|
_feedContent.update { StringFeedState.Loaded(mutableStateOf(notes)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val bundler = BundledUpdate(250, Dispatchers.IO)
|
||||||
|
|
||||||
|
override fun invalidateData(ignoreIfDoing: Boolean) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
bundler.invalidate(ignoreIfDoing) {
|
||||||
|
// adds the time to perform the refresh into this delay
|
||||||
|
// holding off new updates in case of heavy refresh routines.
|
||||||
|
refreshSuspended()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var collectorJob: Job? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
Log.d("Init", this.javaClass.simpleName)
|
||||||
|
collectorJob = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
LocalCache.live.newEventBundles.collect { newNotes ->
|
||||||
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
invalidateData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
bundler.cancel()
|
||||||
|
collectorJob?.cancel()
|
||||||
|
super.onCleared()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -491,6 +491,12 @@ class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hide(word: String) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.hideWord(word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun showUser(pubkeyHex: String) {
|
fun showUser(pubkeyHex: String) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
account.showUser(pubkeyHex)
|
account.showUser(pubkeyHex)
|
||||||
|
|||||||
+236
-8
@@ -1,20 +1,31 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.ButtonDefaults
|
||||||
import androidx.compose.material.Checkbox
|
import androidx.compose.material.Checkbox
|
||||||
|
import androidx.compose.material.Divider
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.OutlinedTextField
|
||||||
|
import androidx.compose.material.ScrollableTabRow
|
||||||
import androidx.compose.material.Tab
|
import androidx.compose.material.Tab
|
||||||
import androidx.compose.material.TabRow
|
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -23,19 +34,35 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
|
import androidx.lifecycle.map
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.SendButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenWordsFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrSpammerAccountsFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrSpammerAccountsFeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.RefresheableView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView
|
import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.StringFeedView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -44,17 +71,22 @@ fun HiddenUsersScreen(accountViewModel: AccountViewModel, nav: (String) -> Unit)
|
|||||||
factory = NostrHiddenAccountsFeedViewModel.Factory(accountViewModel.account)
|
factory = NostrHiddenAccountsFeedViewModel.Factory(accountViewModel.account)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val hiddenWordsFeedViewModel: NostrHiddenWordsFeedViewModel = viewModel(
|
||||||
|
factory = NostrHiddenWordsFeedViewModel.Factory(accountViewModel.account)
|
||||||
|
)
|
||||||
|
|
||||||
val spammerFeedViewModel: NostrSpammerAccountsFeedViewModel = viewModel(
|
val spammerFeedViewModel: NostrSpammerAccountsFeedViewModel = viewModel(
|
||||||
factory = NostrSpammerAccountsFeedViewModel.Factory(accountViewModel.account)
|
factory = NostrSpammerAccountsFeedViewModel.Factory(accountViewModel.account)
|
||||||
)
|
)
|
||||||
|
|
||||||
HiddenUsersScreen(hiddenFeedViewModel, spammerFeedViewModel, accountViewModel, nav)
|
HiddenUsersScreen(hiddenFeedViewModel, hiddenWordsFeedViewModel, spammerFeedViewModel, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun HiddenUsersScreen(
|
fun HiddenUsersScreen(
|
||||||
hiddenFeedViewModel: NostrHiddenAccountsFeedViewModel,
|
hiddenFeedViewModel: NostrHiddenAccountsFeedViewModel,
|
||||||
|
hiddenWordsViewModel: NostrHiddenWordsFeedViewModel,
|
||||||
spammerFeedViewModel: NostrSpammerAccountsFeedViewModel,
|
spammerFeedViewModel: NostrSpammerAccountsFeedViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
@@ -65,6 +97,7 @@ fun HiddenUsersScreen(
|
|||||||
val observer = LifecycleEventObserver { _, event ->
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
if (event == Lifecycle.Event.ON_RESUME) {
|
||||||
println("Hidden Users Start")
|
println("Hidden Users Start")
|
||||||
|
hiddenWordsViewModel.invalidateData()
|
||||||
hiddenFeedViewModel.invalidateData()
|
hiddenFeedViewModel.invalidateData()
|
||||||
spammerFeedViewModel.invalidateData()
|
spammerFeedViewModel.invalidateData()
|
||||||
}
|
}
|
||||||
@@ -78,7 +111,7 @@ fun HiddenUsersScreen(
|
|||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
Column(modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
|
Column(modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
|
||||||
val pagerState = rememberPagerState() { 2 }
|
val pagerState = rememberPagerState() { 3 }
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var warnAboutReports by remember { mutableStateOf(accountViewModel.account.warnAboutPostsWithReports) }
|
var warnAboutReports by remember { mutableStateOf(accountViewModel.account.warnAboutPostsWithReports) }
|
||||||
var filterSpam by remember { mutableStateOf(accountViewModel.account.filterSpamFromStrangers) }
|
var filterSpam by remember { mutableStateOf(accountViewModel.account.filterSpamFromStrangers) }
|
||||||
@@ -109,8 +142,9 @@ fun HiddenUsersScreen(
|
|||||||
Text(stringResource(R.string.filter_spam_from_strangers))
|
Text(stringResource(R.string.filter_spam_from_strangers))
|
||||||
}
|
}
|
||||||
|
|
||||||
TabRow(
|
ScrollableTabRow(
|
||||||
backgroundColor = MaterialTheme.colors.background,
|
backgroundColor = MaterialTheme.colors.background,
|
||||||
|
edgePadding = 8.dp,
|
||||||
selectedTabIndex = pagerState.currentPage,
|
selectedTabIndex = pagerState.currentPage,
|
||||||
modifier = TabRowHeight
|
modifier = TabRowHeight
|
||||||
) {
|
) {
|
||||||
@@ -128,25 +162,98 @@ fun HiddenUsersScreen(
|
|||||||
Text(text = stringResource(R.string.spamming_users))
|
Text(text = stringResource(R.string.spamming_users))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
Tab(
|
||||||
|
selected = pagerState.currentPage == 2,
|
||||||
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } },
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(R.string.hidden_words))
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
HorizontalPager(state = pagerState) { page ->
|
HorizontalPager(state = pagerState) { page ->
|
||||||
when (page) {
|
when (page) {
|
||||||
0 -> RefreshingUserFeedView(hiddenFeedViewModel, accountViewModel, nav)
|
0 -> RefreshingUserFeedView(hiddenFeedViewModel, accountViewModel) {
|
||||||
1 -> RefreshingUserFeedView(spammerFeedViewModel, accountViewModel, nav)
|
RefreshingFeedUserFeedView(hiddenFeedViewModel, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
1 -> RefreshingUserFeedView(spammerFeedViewModel, accountViewModel) {
|
||||||
|
RefreshingFeedUserFeedView(spammerFeedViewModel, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
2 -> HiddenWordsFeed(hiddenWordsViewModel, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun HiddenWordsFeed(
|
||||||
|
hiddenWordsViewModel: NostrHiddenWordsFeedViewModel,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
RefresheableView(hiddenWordsViewModel, false) {
|
||||||
|
StringFeedView(
|
||||||
|
hiddenWordsViewModel,
|
||||||
|
post = { AddMuteWordTextField(accountViewModel) }
|
||||||
|
) {
|
||||||
|
MutedWordHeader(tag = it, account = accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AddMuteWordTextField(accountViewModel: AccountViewModel) {
|
||||||
|
Row {
|
||||||
|
val currentWordToAdd = remember {
|
||||||
|
mutableStateOf("")
|
||||||
|
}
|
||||||
|
val hasChanged by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
currentWordToAdd.value != ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = currentWordToAdd.value,
|
||||||
|
onValueChange = { currentWordToAdd.value = it },
|
||||||
|
label = { Text(text = stringResource(R.string.hide_new_word_label)) },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.hide_new_word_label),
|
||||||
|
color = MaterialTheme.colors.placeholderText
|
||||||
|
)
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(
|
||||||
|
imeAction = ImeAction.Send,
|
||||||
|
capitalization = KeyboardCapitalization.Sentences
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onSend = {
|
||||||
|
accountViewModel.hide(currentWordToAdd.value)
|
||||||
|
currentWordToAdd.value = ""
|
||||||
|
}
|
||||||
|
),
|
||||||
|
singleLine = true,
|
||||||
|
trailingIcon = {
|
||||||
|
if (hasChanged) {
|
||||||
|
SendButton {
|
||||||
|
accountViewModel.hide(currentWordToAdd.value)
|
||||||
|
currentWordToAdd.value = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RefreshingUserFeedView(
|
fun RefreshingUserFeedView(
|
||||||
feedViewModel: UserFeedViewModel,
|
feedViewModel: UserFeedViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
inner: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
WatchAccountAndBlockList(feedViewModel, accountViewModel)
|
WatchAccountAndBlockList(feedViewModel, accountViewModel)
|
||||||
RefreshingFeedUserFeedView(feedViewModel, accountViewModel, nav)
|
inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -161,3 +268,124 @@ fun WatchAccountAndBlockList(
|
|||||||
feedViewModel.invalidateData()
|
feedViewModel.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MutedWordHeader(tag: String, modifier: Modifier = StdPadding, account: AccountViewModel) {
|
||||||
|
Column(
|
||||||
|
Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column(modifier = modifier) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
tag,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
MutedWordActionOptions(tag, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider(
|
||||||
|
thickness = 0.25.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MutedWordActionOptions(
|
||||||
|
word: String,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val isMutedWord by accountViewModel.account.liveHiddenUsers.map {
|
||||||
|
word in it.hiddenWords
|
||||||
|
}.distinctUntilChanged().observeAsState()
|
||||||
|
|
||||||
|
if (isMutedWord == true) {
|
||||||
|
ShowWordButton {
|
||||||
|
if (!accountViewModel.isWriteable()) {
|
||||||
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.account.showWord(word)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.login_with_a_private_key_to_be_able_to_unfollow),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.account.showWord(word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
HideWordButton {
|
||||||
|
if (!accountViewModel.isWriteable()) {
|
||||||
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.account.hideWord(word)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.login_with_a_private_key_to_be_able_to_follow),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.account.hideWord(word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HideWordButton(onClick: () -> Unit) {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
|
onClick = onClick,
|
||||||
|
shape = ButtonBorder,
|
||||||
|
colors = ButtonDefaults
|
||||||
|
.buttonColors(
|
||||||
|
backgroundColor = MaterialTheme.colors.primary
|
||||||
|
),
|
||||||
|
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.block_only), color = Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShowWordButton(text: Int = R.string.unblock, onClick: () -> Unit) {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.padding(start = 3.dp),
|
||||||
|
onClick = onClick,
|
||||||
|
shape = ButtonBorder,
|
||||||
|
colors = ButtonDefaults
|
||||||
|
.buttonColors(
|
||||||
|
backgroundColor = MaterialTheme.colors.primary
|
||||||
|
),
|
||||||
|
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(text), color = Color.White, textAlign = TextAlign.Center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -595,4 +595,7 @@
|
|||||||
|
|
||||||
<string name="no_wallet_found_with_error">No Wallets found to pay a lightning invoice (Error: %1$s). Please install a Lightning wallet to use zaps</string>
|
<string name="no_wallet_found_with_error">No Wallets found to pay a lightning invoice (Error: %1$s). Please install a Lightning wallet to use zaps</string>
|
||||||
<string name="no_wallet_found">No Wallets found to pay a lightning invoice. Please install a Lightning wallet to use zaps</string>
|
<string name="no_wallet_found">No Wallets found to pay a lightning invoice. Please install a Lightning wallet to use zaps</string>
|
||||||
|
|
||||||
|
<string name="hidden_words">Hidden Words</string>
|
||||||
|
<string name="hide_new_word_label">Hide new word or sentence</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class BookmarkListEvent(
|
|||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
) : GeneralListEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
) : GeneralListEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
@Transient
|
||||||
var decryptedContent = ""
|
var decryptedContent = ""
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -125,14 +125,20 @@ open class Event(
|
|||||||
|
|
||||||
override fun matchTag1With(text: String) = tags.any { it.size > 1 && it[1].contains(text, true) }
|
override fun matchTag1With(text: String) = tags.any { it.size > 1 && it[1].contains(text, true) }
|
||||||
|
|
||||||
override fun isTaggedUser(idHex: String) = tags.any { it.size > 1 && it[0] == "p" && it[1] == idHex }
|
override fun isTagged(key: String, tag: String) = tags.any { it.size > 1 && it[0] == key && it[1] == tag }
|
||||||
override fun isTaggedUsers(idHexes: Set<String>) = tags.any { it.size > 1 && it[0] == "p" && it[1] in idHexes }
|
|
||||||
|
|
||||||
override fun isTaggedEvent(idHex: String) = tags.any { it.size > 1 && it[0] == "e" && it[1] == idHex }
|
override fun isAnyTagged(key: String, tags: Set<String>) = this.tags.any { it.size > 1 && it[0] == key && it[1] in tags }
|
||||||
|
|
||||||
override fun isTaggedAddressableNote(idHex: String) = tags.any { it.size > 1 && it[0] == "a" && it[1] == idHex }
|
override fun isTaggedWord(word: String) = isTagged("word", word)
|
||||||
|
|
||||||
override fun isTaggedAddressableNotes(idHexes: Set<String>) = tags.any { it.size > 1 && it[0] == "a" && it[1] in idHexes }
|
override fun isTaggedUser(idHex: String) = isTagged("p", idHex)
|
||||||
|
override fun isTaggedUsers(idHexes: Set<String>) = isAnyTagged("p", idHexes)
|
||||||
|
|
||||||
|
override fun isTaggedEvent(idHex: String) = isTagged("e", idHex)
|
||||||
|
|
||||||
|
override fun isTaggedAddressableNote(idHex: String) = isTagged("a", idHex)
|
||||||
|
|
||||||
|
override fun isTaggedAddressableNotes(idHexes: Set<String>) = isAnyTagged( "a", idHexes)
|
||||||
|
|
||||||
override fun isTaggedHash(hashtag: String) = tags.any { it.size > 1 && it[0] == "t" && it[1].equals(hashtag, true) }
|
override fun isTaggedHash(hashtag: String) = tags.any { it.size > 1 && it[0] == "t" && it[1].equals(hashtag, true) }
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ interface EventInterface {
|
|||||||
|
|
||||||
fun hasValidSignature(): Boolean
|
fun hasValidSignature(): Boolean
|
||||||
|
|
||||||
|
fun isTagged(key: String, tag: String): Boolean
|
||||||
|
fun isAnyTagged(key: String, tags: Set<String>): Boolean
|
||||||
|
fun isTaggedWord(word: String): Boolean
|
||||||
|
|
||||||
fun isTaggedUser(idHex: String): Boolean
|
fun isTaggedUser(idHex: String): Boolean
|
||||||
fun isTaggedUsers(idHex: Set<String>): Boolean
|
fun isTaggedUsers(idHex: Set<String>): Boolean
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ abstract class GeneralListEvent(
|
|||||||
return privateTagsCache
|
return privateTagsCache
|
||||||
}
|
}
|
||||||
|
|
||||||
fun privateTagsOrEmpty(privKey: ByteArray): List<List<String>> {
|
fun privateTagsOrEmpty(privKey: ByteArray?): List<List<String>> {
|
||||||
|
if (privKey == null) return emptyList()
|
||||||
return privateTags(privKey) ?: emptyList()
|
return privateTags(privKey) ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,62 +18,94 @@ class PeopleListEvent(
|
|||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
) : GeneralListEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
) : GeneralListEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
@Transient
|
||||||
var decryptedContent: String? = null
|
var decryptedContent: String? = null
|
||||||
|
@Transient
|
||||||
var publicAndPrivateUserCache: ImmutableSet<HexKey>? = null
|
var publicAndPrivateUserCache: ImmutableSet<HexKey>? = null
|
||||||
|
@Transient
|
||||||
|
var publicAndPrivateWordCache: ImmutableSet<String>? = null
|
||||||
|
|
||||||
|
fun filterTagList(key: String, privateTags: List<List<String>>?): ImmutableSet<String> {
|
||||||
|
val privateUserList = privateTags?.let {
|
||||||
|
it.filter { it.size > 1 && it[0] == key }.map { it[1] }.toSet()
|
||||||
|
} ?: emptySet()
|
||||||
|
val publicUserList = tags.filter { it.size > 1 && it[0] == key }.map { it[1] }.toSet()
|
||||||
|
|
||||||
|
return (privateUserList + publicUserList).toImmutableSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun publicAndPrivateWords(privateKey: ByteArray?): ImmutableSet<String> {
|
||||||
|
publicAndPrivateWordCache?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
publicAndPrivateWordCache = filterTagList("word", privateTagsOrEmpty(privKey = privateKey))
|
||||||
|
|
||||||
|
return publicAndPrivateWordCache ?: persistentSetOf()
|
||||||
|
}
|
||||||
|
|
||||||
fun publicAndPrivateUsers(privateKey: ByteArray?): ImmutableSet<HexKey> {
|
fun publicAndPrivateUsers(privateKey: ByteArray?): ImmutableSet<HexKey> {
|
||||||
publicAndPrivateUserCache?.let {
|
publicAndPrivateUserCache?.let {
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|
||||||
val privateUserList = privateKey?.let {
|
publicAndPrivateUserCache = filterTagList("p", privateTagsOrEmpty(privKey = privateKey))
|
||||||
privateTagsOrEmpty(privKey = it).filter { it.size > 1 && it[0] == "p" }.map { it[1] }.toSet()
|
|
||||||
} ?: emptySet()
|
|
||||||
val publicUserList = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }.toSet()
|
|
||||||
|
|
||||||
publicAndPrivateUserCache = (privateUserList + publicUserList).toImmutableSet()
|
|
||||||
|
|
||||||
return publicAndPrivateUserCache ?: persistentSetOf()
|
return publicAndPrivateUserCache ?: persistentSetOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun publicAndPrivateWords(decryptedContent: String): ImmutableSet<HexKey> {
|
||||||
|
publicAndPrivateWordCache?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
publicAndPrivateWordCache = filterTagList("word", privateTagsOrEmpty(decryptedContent))
|
||||||
|
|
||||||
|
return publicAndPrivateWordCache ?: persistentSetOf()
|
||||||
|
}
|
||||||
|
|
||||||
fun publicAndPrivateUsers(decryptedContent: String): ImmutableSet<HexKey> {
|
fun publicAndPrivateUsers(decryptedContent: String): ImmutableSet<HexKey> {
|
||||||
publicAndPrivateUserCache?.let {
|
publicAndPrivateUserCache?.let {
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|
||||||
val privateUserList = privateTagsOrEmpty(decryptedContent).filter { it.size > 1 && it[0] == "p" }.map { it[1] }.toSet()
|
publicAndPrivateUserCache = filterTagList("p", privateTagsOrEmpty(decryptedContent))
|
||||||
|
|
||||||
val publicUserList = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }.toSet()
|
|
||||||
|
|
||||||
publicAndPrivateUserCache = (privateUserList + publicUserList).toImmutableSet()
|
|
||||||
|
|
||||||
return publicAndPrivateUserCache ?: persistentSetOf()
|
return publicAndPrivateUserCache ?: persistentSetOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isTaggedUser(idHex: String, isPrivate: Boolean, privateKey: ByteArray): Boolean {
|
fun isTagged(key: String, tag: String, isPrivate: Boolean, privateKey: ByteArray): Boolean {
|
||||||
return if (isPrivate) {
|
return if (isPrivate) {
|
||||||
privateTagsOrEmpty(privKey = privateKey).any { it.size > 1 && it[0] == "p" && it[1] == idHex }
|
privateTagsOrEmpty(privKey = privateKey).any { it.size > 1 && it[0] == key && it[1] == tag }
|
||||||
} else {
|
} else {
|
||||||
isTaggedUser(idHex)
|
isTagged(key, tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isTaggedUser(idHex: String, isPrivate: Boolean, content: String): Boolean {
|
fun isTagged(key: String, tag: String, isPrivate: Boolean, decryptedContent: String): Boolean {
|
||||||
return if (isPrivate) {
|
return if (isPrivate) {
|
||||||
privateTagsOrEmpty(content).any { it.size > 1 && it[0] == "p" && it[1] == idHex }
|
privateTagsOrEmpty(decryptedContent).any { it.size > 1 && it[0] == key && it[1] == tag }
|
||||||
} else {
|
} else {
|
||||||
isTaggedUser(idHex)
|
isTagged(key, tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isTaggedWord(word: String, isPrivate: Boolean, privateKey: ByteArray) = isTagged( "word", word, isPrivate, privateKey)
|
||||||
|
|
||||||
|
fun isTaggedUser(idHex: String, isPrivate: Boolean, privateKey: ByteArray) = isTagged( "p", idHex, isPrivate, privateKey)
|
||||||
|
|
||||||
|
fun isTaggedUser(idHex: String, isPrivate: Boolean, decryptedContent: String) = isTagged( "p", idHex, isPrivate, decryptedContent)
|
||||||
|
|
||||||
|
fun isTaggedWord(idHex: String, isPrivate: Boolean, decryptedContent: String) = isTagged( "word", idHex, isPrivate, decryptedContent)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val kind = 30000
|
const val kind = 30000
|
||||||
const val blockList = "mute"
|
const val blockList = "mute"
|
||||||
|
|
||||||
fun createListWithUser(name: String, pubKeyHex: String, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
fun createListWithTag(name: String, key: String, tag: String, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
return if (isPrivate) {
|
return if (isPrivate) {
|
||||||
create(
|
create(
|
||||||
content = encryptTags(listOf(listOf("p", pubKeyHex)), privateKey),
|
content = encryptTags(listOf(listOf(key, tag)), privateKey),
|
||||||
tags = listOf(listOf("d", name)),
|
tags = listOf(listOf("d", name)),
|
||||||
privateKey = privateKey,
|
privateKey = privateKey,
|
||||||
createdAt = createdAt
|
createdAt = createdAt
|
||||||
@@ -81,13 +113,21 @@ class PeopleListEvent(
|
|||||||
} else {
|
} else {
|
||||||
create(
|
create(
|
||||||
content = "",
|
content = "",
|
||||||
tags = listOf(listOf("d", name), listOf("p", pubKeyHex)),
|
tags = listOf(listOf("d", name), listOf(key, tag)),
|
||||||
privateKey = privateKey,
|
privateKey = privateKey,
|
||||||
createdAt = createdAt
|
createdAt = createdAt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createListWithUser(name: String, pubKeyHex: String, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
|
return createListWithTag(name, "p", pubKeyHex, isPrivate, privateKey, createdAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createListWithWord(name: String, word: String, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
|
return createListWithTag(name, "word", word, isPrivate, privateKey, createdAt)
|
||||||
|
}
|
||||||
|
|
||||||
fun createListWithUser(name: String, pubKeyHex: String, isPrivate: Boolean, pubKey: HexKey, encryptedContent: String, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
fun createListWithUser(name: String, pubKeyHex: String, isPrivate: Boolean, pubKey: HexKey, encryptedContent: String, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
return if (isPrivate) {
|
return if (isPrivate) {
|
||||||
create(
|
create(
|
||||||
@@ -106,6 +146,24 @@ class PeopleListEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createListWithWord(name: String, word: String, isPrivate: Boolean, pubKey: HexKey, encryptedContent: String, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
|
return if (isPrivate) {
|
||||||
|
create(
|
||||||
|
content = encryptedContent,
|
||||||
|
tags = listOf(listOf("d", name)),
|
||||||
|
pubKey = pubKey,
|
||||||
|
createdAt = createdAt
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
create(
|
||||||
|
content = "",
|
||||||
|
tags = listOf(listOf("d", name), listOf("word", word)),
|
||||||
|
pubKey = pubKey,
|
||||||
|
createdAt = createdAt
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun addUsers(earlierVersion: PeopleListEvent, listPubKeyHex: List<String>, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
fun addUsers(earlierVersion: PeopleListEvent, listPubKeyHex: List<String>, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
return if (isPrivate) {
|
return if (isPrivate) {
|
||||||
create(
|
create(
|
||||||
@@ -135,6 +193,24 @@ class PeopleListEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addWord(earlierVersion: PeopleListEvent, word: String, isPrivate: Boolean, pubKey: HexKey, encryptedContent: String, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
|
return if (isPrivate) {
|
||||||
|
create(
|
||||||
|
content = encryptedContent,
|
||||||
|
tags = earlierVersion.tags,
|
||||||
|
pubKey = pubKey,
|
||||||
|
createdAt = createdAt
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
create(
|
||||||
|
content = earlierVersion.content,
|
||||||
|
tags = earlierVersion.tags.plus(element = listOf("word", word)),
|
||||||
|
pubKey = pubKey,
|
||||||
|
createdAt = createdAt
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun addUser(earlierVersion: PeopleListEvent, pubKeyHex: String, isPrivate: Boolean, pubKey: HexKey, encryptedContent: String, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
fun addUser(earlierVersion: PeopleListEvent, pubKeyHex: String, isPrivate: Boolean, pubKey: HexKey, encryptedContent: String, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
return if (isPrivate) {
|
return if (isPrivate) {
|
||||||
create(
|
create(
|
||||||
@@ -153,13 +229,40 @@ class PeopleListEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeTag(earlierVersion: PeopleListEvent, tag: String, isPrivate: Boolean, pubKey: HexKey, encryptedContent: String, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
|
return if (isPrivate) {
|
||||||
|
create(
|
||||||
|
content = encryptedContent,
|
||||||
|
tags = earlierVersion.tags,
|
||||||
|
pubKey = pubKey,
|
||||||
|
createdAt = createdAt
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
create(
|
||||||
|
content = earlierVersion.content,
|
||||||
|
tags = earlierVersion.tags.filter { it.size > 1 && it[1] != tag },
|
||||||
|
pubKey = pubKey,
|
||||||
|
createdAt = createdAt
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addWord(earlierVersion: PeopleListEvent, word: String, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
|
return addTag(earlierVersion, "word", word, isPrivate, privateKey, createdAt)
|
||||||
|
}
|
||||||
|
|
||||||
fun addUser(earlierVersion: PeopleListEvent, pubKeyHex: String, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
fun addUser(earlierVersion: PeopleListEvent, pubKeyHex: String, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
if (earlierVersion.isTaggedUser(pubKeyHex, isPrivate, privateKey)) return earlierVersion
|
return addTag(earlierVersion, "p", pubKeyHex, isPrivate, privateKey, createdAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun addTag(earlierVersion: PeopleListEvent, key: String, tag: String, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
|
if (earlierVersion.isTagged(key, tag, isPrivate, privateKey)) return earlierVersion
|
||||||
|
|
||||||
return if (isPrivate) {
|
return if (isPrivate) {
|
||||||
create(
|
create(
|
||||||
content = encryptTags(
|
content = encryptTags(
|
||||||
privateTags = earlierVersion.privateTagsOrEmpty(privKey = privateKey).plus(element = listOf("p", pubKeyHex)),
|
privateTags = earlierVersion.privateTagsOrEmpty(privKey = privateKey).plus(element = listOf(key, tag)),
|
||||||
privateKey = privateKey
|
privateKey = privateKey
|
||||||
),
|
),
|
||||||
tags = earlierVersion.tags,
|
tags = earlierVersion.tags,
|
||||||
@@ -169,48 +272,38 @@ class PeopleListEvent(
|
|||||||
} else {
|
} else {
|
||||||
create(
|
create(
|
||||||
content = earlierVersion.content,
|
content = earlierVersion.content,
|
||||||
tags = earlierVersion.tags.plus(element = listOf("p", pubKeyHex)),
|
tags = earlierVersion.tags.plus(element = listOf(key, tag)),
|
||||||
privateKey = privateKey,
|
privateKey = privateKey,
|
||||||
createdAt = createdAt
|
createdAt = createdAt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeUser(earlierVersion: PeopleListEvent, pubKeyHex: String, isPrivate: Boolean, encryptedContent: String, pubKey: HexKey, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
fun removeWord(earlierVersion: PeopleListEvent, word: String, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
return if (isPrivate) {
|
return removeTag(earlierVersion, "word", word, isPrivate, privateKey, createdAt)
|
||||||
create(
|
|
||||||
content = encryptedContent,
|
|
||||||
tags = earlierVersion.tags.filter { it.size > 1 && it[1] != pubKeyHex },
|
|
||||||
pubKey = pubKey,
|
|
||||||
createdAt = createdAt
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
create(
|
|
||||||
content = earlierVersion.content,
|
|
||||||
tags = earlierVersion.tags.filter { it.size > 1 && it[1] != pubKeyHex },
|
|
||||||
pubKey = pubKey,
|
|
||||||
createdAt = createdAt
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeUser(earlierVersion: PeopleListEvent, pubKeyHex: String, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
fun removeUser(earlierVersion: PeopleListEvent, pubKeyHex: String, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
if (!earlierVersion.isTaggedUser(pubKeyHex, isPrivate, privateKey)) return earlierVersion
|
return removeTag(earlierVersion, "p", pubKeyHex, isPrivate, privateKey, createdAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeTag(earlierVersion: PeopleListEvent, key: String, tag: String, isPrivate: Boolean, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): PeopleListEvent {
|
||||||
|
if (!earlierVersion.isTagged(key, tag, isPrivate, privateKey)) return earlierVersion
|
||||||
|
|
||||||
return if (isPrivate) {
|
return if (isPrivate) {
|
||||||
create(
|
create(
|
||||||
content = encryptTags(
|
content = encryptTags(
|
||||||
privateTags = earlierVersion.privateTagsOrEmpty(privKey = privateKey).filter { it.size > 1 && it[1] != pubKeyHex },
|
privateTags = earlierVersion.privateTagsOrEmpty(privKey = privateKey).filter { it.size > 1 && !(it[0] == key && it[1] == tag) },
|
||||||
privateKey = privateKey
|
privateKey = privateKey
|
||||||
),
|
),
|
||||||
tags = earlierVersion.tags.filter { it.size > 1 && it[1] != pubKeyHex },
|
tags = earlierVersion.tags.filter { it.size > 1 && !(it[0] == key && it[1] == tag) },
|
||||||
privateKey = privateKey,
|
privateKey = privateKey,
|
||||||
createdAt = createdAt
|
createdAt = createdAt
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
create(
|
create(
|
||||||
content = earlierVersion.content,
|
content = earlierVersion.content,
|
||||||
tags = earlierVersion.tags.filter { it.size > 1 && it[1] != pubKeyHex },
|
tags = earlierVersion.tags.filter { it.size > 1 && !(it[0] == key && it[1] == tag) },
|
||||||
privateKey = privateKey,
|
privateKey = privateKey,
|
||||||
createdAt = createdAt
|
createdAt = createdAt
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user