Support for NIP-36, declaration of sensitive content.
This commit is contained in:
@@ -58,6 +58,7 @@ private object PrefKeys {
|
|||||||
const val HIDE_BLOCK_ALERT_DIALOG = "hide_block_alert_dialog"
|
const val HIDE_BLOCK_ALERT_DIALOG = "hide_block_alert_dialog"
|
||||||
const val USE_PROXY = "use_proxy"
|
const val USE_PROXY = "use_proxy"
|
||||||
const val PROXY_PORT = "proxy_port"
|
const val PROXY_PORT = "proxy_port"
|
||||||
|
const val SHOW_SENSITIVE_CONTENT = "show_sensitive_content"
|
||||||
val LAST_READ: (String) -> String = { route -> "last_read_route_$route" }
|
val LAST_READ: (String) -> String = { route -> "last_read_route_$route" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +215,12 @@ object LocalPreferences {
|
|||||||
putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, account.hideBlockAlertDialog)
|
putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, account.hideBlockAlertDialog)
|
||||||
putBoolean(PrefKeys.USE_PROXY, account.proxy != null)
|
putBoolean(PrefKeys.USE_PROXY, account.proxy != null)
|
||||||
putInt(PrefKeys.PROXY_PORT, account.proxyPort)
|
putInt(PrefKeys.PROXY_PORT, account.proxyPort)
|
||||||
|
|
||||||
|
if (account.showSensitiveContent == null) {
|
||||||
|
remove(PrefKeys.SHOW_SENSITIVE_CONTENT)
|
||||||
|
} else {
|
||||||
|
putBoolean(PrefKeys.SHOW_SENSITIVE_CONTENT, account.showSensitiveContent!!)
|
||||||
|
}
|
||||||
}.apply()
|
}.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +299,12 @@ object LocalPreferences {
|
|||||||
val proxyPort = getInt(PrefKeys.PROXY_PORT, 9050)
|
val proxyPort = getInt(PrefKeys.PROXY_PORT, 9050)
|
||||||
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)
|
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)
|
||||||
|
|
||||||
|
val showSensitiveContent = if (contains(PrefKeys.SHOW_SENSITIVE_CONTENT)) {
|
||||||
|
getBoolean(PrefKeys.SHOW_SENSITIVE_CONTENT, false)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
val a = Account(
|
val a = Account(
|
||||||
Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
|
Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
|
||||||
followingChannels,
|
followingChannels,
|
||||||
@@ -311,7 +324,8 @@ object LocalPreferences {
|
|||||||
hideBlockAlertDialog,
|
hideBlockAlertDialog,
|
||||||
latestContactList,
|
latestContactList,
|
||||||
proxy,
|
proxy,
|
||||||
proxyPort
|
proxyPort,
|
||||||
|
showSensitiveContent
|
||||||
)
|
)
|
||||||
|
|
||||||
return a
|
return a
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ class Account(
|
|||||||
var hideBlockAlertDialog: Boolean = false,
|
var hideBlockAlertDialog: Boolean = false,
|
||||||
var backupContactList: ContactListEvent? = null,
|
var backupContactList: ContactListEvent? = null,
|
||||||
var proxy: Proxy?,
|
var proxy: Proxy?,
|
||||||
var proxyPort: Int
|
var proxyPort: Int,
|
||||||
|
var showSensitiveContent: Boolean? = null
|
||||||
) {
|
) {
|
||||||
var transientHiddenUsers: Set<String> = setOf()
|
var transientHiddenUsers: Set<String> = setOf()
|
||||||
|
|
||||||
@@ -480,7 +481,14 @@ class Account(
|
|||||||
return LocalCache.notes[signedEvent.id]
|
return LocalCache.notes[signedEvent.id]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendPost(message: String, replyTo: List<Note>?, mentions: List<User>?, tags: List<String>? = null, zapReceiver: String? = null) {
|
fun sendPost(
|
||||||
|
message: String,
|
||||||
|
replyTo: List<Note>?,
|
||||||
|
mentions: List<User>?,
|
||||||
|
tags: List<String>? = null,
|
||||||
|
zapReceiver: String? = null,
|
||||||
|
wantsToMarkAsSensitive: Boolean
|
||||||
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val repliesToHex = replyTo?.filter { it.address() == null }?.map { it.idHex }
|
val repliesToHex = replyTo?.filter { it.address() == null }?.map { it.idHex }
|
||||||
@@ -494,6 +502,7 @@ class Account(
|
|||||||
addresses = addresses,
|
addresses = addresses,
|
||||||
extraTags = tags,
|
extraTags = tags,
|
||||||
zapReceiver = zapReceiver,
|
zapReceiver = zapReceiver,
|
||||||
|
markAsSensitive = wantsToMarkAsSensitive,
|
||||||
privateKey = loggedIn.privKey!!
|
privateKey = loggedIn.privKey!!
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -510,7 +519,8 @@ class Account(
|
|||||||
valueMinimum: Int?,
|
valueMinimum: Int?,
|
||||||
consensusThreshold: Int?,
|
consensusThreshold: Int?,
|
||||||
closedAt: Int?,
|
closedAt: Int?,
|
||||||
zapReceiver: String? = null
|
zapReceiver: String? = null,
|
||||||
|
wantsToMarkAsSensitive: Boolean
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
@@ -529,14 +539,15 @@ class Account(
|
|||||||
valueMinimum = valueMinimum,
|
valueMinimum = valueMinimum,
|
||||||
consensusThreshold = consensusThreshold,
|
consensusThreshold = consensusThreshold,
|
||||||
closedAt = closedAt,
|
closedAt = closedAt,
|
||||||
zapReceiver = zapReceiver
|
zapReceiver = zapReceiver,
|
||||||
|
markAsSensitive = wantsToMarkAsSensitive
|
||||||
)
|
)
|
||||||
// println("Sending new PollNoteEvent: %s".format(signedEvent.toJson()))
|
// println("Sending new PollNoteEvent: %s".format(signedEvent.toJson()))
|
||||||
Client.send(signedEvent)
|
Client.send(signedEvent)
|
||||||
LocalCache.consume(signedEvent)
|
LocalCache.consume(signedEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendChannelMessage(message: String, toChannel: String, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: String? = null) {
|
fun sendChannelMessage(message: String, toChannel: String, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||||
@@ -549,13 +560,14 @@ class Account(
|
|||||||
replyTos = repliesToHex,
|
replyTos = repliesToHex,
|
||||||
mentions = mentionsHex,
|
mentions = mentionsHex,
|
||||||
zapReceiver = zapReceiver,
|
zapReceiver = zapReceiver,
|
||||||
|
markAsSensitive = wantsToMarkAsSensitive,
|
||||||
privateKey = loggedIn.privKey!!
|
privateKey = loggedIn.privKey!!
|
||||||
)
|
)
|
||||||
Client.send(signedEvent)
|
Client.send(signedEvent)
|
||||||
LocalCache.consume(signedEvent, null)
|
LocalCache.consume(signedEvent, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendPrivateMessage(message: String, toUser: String, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: String? = null) {
|
fun sendPrivateMessage(message: String, toUser: String, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
val user = LocalCache.users[toUser] ?: return
|
val user = LocalCache.users[toUser] ?: return
|
||||||
|
|
||||||
@@ -569,6 +581,7 @@ class Account(
|
|||||||
replyTos = repliesToHex,
|
replyTos = repliesToHex,
|
||||||
mentions = mentionsHex,
|
mentions = mentionsHex,
|
||||||
zapReceiver = zapReceiver,
|
zapReceiver = zapReceiver,
|
||||||
|
markAsSensitive = wantsToMarkAsSensitive,
|
||||||
privateKey = loggedIn.privKey!!,
|
privateKey = loggedIn.privKey!!,
|
||||||
advertiseNip18 = false
|
advertiseNip18 = false
|
||||||
)
|
)
|
||||||
@@ -1114,6 +1127,12 @@ class Account(
|
|||||||
saveable.invalidateData()
|
saveable.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateShowSensitiveContent(show: Boolean?) {
|
||||||
|
showSensitiveContent = show
|
||||||
|
saveable.invalidateData()
|
||||||
|
live.invalidateData()
|
||||||
|
}
|
||||||
|
|
||||||
fun registerObservers() {
|
fun registerObservers() {
|
||||||
// Observes relays to restart connections
|
// Observes relays to restart connections
|
||||||
userProfile().live().relays.observeForever {
|
userProfile().live().relays.observeForever {
|
||||||
|
|||||||
@@ -22,7 +22,16 @@ class ChannelMessageEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 42
|
const val kind = 42
|
||||||
|
|
||||||
fun create(message: String, channel: String, replyTos: List<String>? = null, mentions: List<String>? = null, zapReceiver: String?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMessageEvent {
|
fun create(
|
||||||
|
message: String,
|
||||||
|
channel: String,
|
||||||
|
replyTos: List<String>? = null,
|
||||||
|
mentions: List<String>? = null,
|
||||||
|
zapReceiver: String?,
|
||||||
|
privateKey: ByteArray,
|
||||||
|
createdAt: Long = Date().time / 1000,
|
||||||
|
markAsSensitive: Boolean
|
||||||
|
): ChannelMessageEvent {
|
||||||
val content = message
|
val content = message
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = mutableListOf(
|
val tags = mutableListOf(
|
||||||
@@ -37,6 +46,9 @@ class ChannelMessageEvent(
|
|||||||
zapReceiver?.let {
|
zapReceiver?.let {
|
||||||
tags.add(listOf("zap", it))
|
tags.add(listOf("zap", it))
|
||||||
}
|
}
|
||||||
|
if (markAsSensitive) {
|
||||||
|
tags.add(listOf("content-warning", ""))
|
||||||
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ open class Event(
|
|||||||
|
|
||||||
fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] }
|
fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] }
|
||||||
|
|
||||||
|
override fun isSensitive() = tags.any {
|
||||||
|
(it.size > 0 && it[0].equals("content-warning", true)) ||
|
||||||
|
(it.size > 1 && it[0] == "t" && it[1].equals("nsfw", true)) ||
|
||||||
|
(it.size > 1 && it[0] == "t" && it[1].equals("nude", true))
|
||||||
|
}
|
||||||
|
|
||||||
override fun zapAddress() = tags.firstOrNull { it.size > 1 && it[0] == "zap" }?.get(1)
|
override fun zapAddress() = tags.firstOrNull { it.size > 1 && it[0] == "zap" }?.get(1)
|
||||||
|
|
||||||
fun taggedAddresses() = tags.filter { it.size > 1 && it[0] == "a" }.mapNotNull {
|
fun taggedAddresses() = tags.filter { it.size > 1 && it[0] == "a" }.mapNotNull {
|
||||||
|
|||||||
@@ -37,4 +37,5 @@ interface EventInterface {
|
|||||||
fun getPoWRank(): Int
|
fun getPoWRank(): Int
|
||||||
|
|
||||||
fun zapAddress(): String?
|
fun zapAddress(): String?
|
||||||
|
fun isSensitive(): Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ class PollNoteEvent(
|
|||||||
valueMinimum: Int?,
|
valueMinimum: Int?,
|
||||||
consensusThreshold: Int?,
|
consensusThreshold: Int?,
|
||||||
closedAt: Int?,
|
closedAt: Int?,
|
||||||
zapReceiver: String?
|
zapReceiver: String?,
|
||||||
|
markAsSensitive: Boolean
|
||||||
): PollNoteEvent {
|
): PollNoteEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
@@ -75,6 +76,9 @@ class PollNoteEvent(
|
|||||||
if (zapReceiver != null) {
|
if (zapReceiver != null) {
|
||||||
tags.add(listOf("zap", zapReceiver))
|
tags.add(listOf("zap", zapReceiver))
|
||||||
}
|
}
|
||||||
|
if (markAsSensitive) {
|
||||||
|
tags.add(listOf("content-warning", ""))
|
||||||
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ class PrivateDmEvent(
|
|||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000,
|
createdAt: Long = Date().time / 1000,
|
||||||
publishedRecipientPubKey: ByteArray? = null,
|
publishedRecipientPubKey: ByteArray? = null,
|
||||||
advertiseNip18: Boolean = true
|
advertiseNip18: Boolean = true,
|
||||||
|
markAsSensitive: Boolean
|
||||||
): PrivateDmEvent {
|
): PrivateDmEvent {
|
||||||
val content = Utils.encrypt(
|
val content = Utils.encrypt(
|
||||||
if (advertiseNip18) { nip18Advertisement } else { "" } + msg,
|
if (advertiseNip18) { nip18Advertisement } else { "" } + msg,
|
||||||
@@ -94,6 +95,9 @@ class PrivateDmEvent(
|
|||||||
zapReceiver?.let {
|
zapReceiver?.let {
|
||||||
tags.add(listOf("zap", it))
|
tags.add(listOf("zap", it))
|
||||||
}
|
}
|
||||||
|
if (markAsSensitive) {
|
||||||
|
tags.add(listOf("content-warning", ""))
|
||||||
|
}
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return PrivateDmEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
return PrivateDmEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class TextNoteEvent(
|
|||||||
addresses: List<ATag>?,
|
addresses: List<ATag>?,
|
||||||
extraTags: List<String>?,
|
extraTags: List<String>?,
|
||||||
zapReceiver: String?,
|
zapReceiver: String?,
|
||||||
|
markAsSensitive: Boolean,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = Date().time / 1000
|
||||||
): TextNoteEvent {
|
): TextNoteEvent {
|
||||||
@@ -56,6 +57,9 @@ class TextNoteEvent(
|
|||||||
findURLs(msg).forEach {
|
findURLs(msg).forEach {
|
||||||
tags.add(listOf("r", it))
|
tags.add(listOf("r", it))
|
||||||
}
|
}
|
||||||
|
if (markAsSensitive) {
|
||||||
|
tags.add(listOf("content-warning", ""))
|
||||||
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
|
|||||||
@@ -22,8 +22,12 @@ import androidx.compose.material.icons.filled.ArrowForwardIos
|
|||||||
import androidx.compose.material.icons.filled.Bolt
|
import androidx.compose.material.icons.filled.Bolt
|
||||||
import androidx.compose.material.icons.filled.Cancel
|
import androidx.compose.material.icons.filled.Cancel
|
||||||
import androidx.compose.material.icons.filled.CurrencyBitcoin
|
import androidx.compose.material.icons.filled.CurrencyBitcoin
|
||||||
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
|
import androidx.compose.material.icons.filled.VisibilityOff
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
import androidx.compose.material.icons.outlined.ArrowForwardIos
|
import androidx.compose.material.icons.outlined.ArrowForwardIos
|
||||||
import androidx.compose.material.icons.outlined.Bolt
|
import androidx.compose.material.icons.outlined.Bolt
|
||||||
|
import androidx.compose.material.icons.rounded.Warning
|
||||||
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
|
||||||
@@ -83,7 +87,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
|
||||||
val scroolState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
@@ -153,7 +157,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.verticalScroll(scroolState)
|
.verticalScroll(scrollState)
|
||||||
) {
|
) {
|
||||||
Notifying(postViewModel.mentions) {
|
Notifying(postViewModel.mentions) {
|
||||||
postViewModel.removeFromReplyList(it)
|
postViewModel.removeFromReplyList(it)
|
||||||
@@ -335,10 +339,11 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (postViewModel.canUsePoll) {
|
if (postViewModel.canUsePoll) {
|
||||||
val hashtag = stringResource(R.string.poll_hashtag)
|
// These should be hashtag recommendations the user selects in the future.
|
||||||
|
// val hashtag = stringResource(R.string.poll_hashtag)
|
||||||
|
// postViewModel.includePollHashtagInMessage(postViewModel.wantsPoll, hashtag)
|
||||||
AddPollButton(postViewModel.wantsPoll) {
|
AddPollButton(postViewModel.wantsPoll) {
|
||||||
postViewModel.wantsPoll = !postViewModel.wantsPoll
|
postViewModel.wantsPoll = !postViewModel.wantsPoll
|
||||||
postViewModel.includePollHashtagInMessage(postViewModel.wantsPoll, hashtag)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,6 +353,10 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MarkAsSensitive(postViewModel) {
|
||||||
|
postViewModel.wantsToMarkAsSensitive = !postViewModel.wantsToMarkAsSensitive
|
||||||
|
}
|
||||||
|
|
||||||
ForwardZapTo(postViewModel) {
|
ForwardZapTo(postViewModel) {
|
||||||
postViewModel.wantsForwardZapTo = !postViewModel.wantsForwardZapTo
|
postViewModel.wantsForwardZapTo = !postViewModel.wantsForwardZapTo
|
||||||
}
|
}
|
||||||
@@ -537,6 +546,60 @@ private fun ForwardZapTo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MarkAsSensitive(
|
||||||
|
postViewModel: NewPostViewModel,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.height(20.dp)
|
||||||
|
.width(23.dp)
|
||||||
|
) {
|
||||||
|
if (!postViewModel.wantsToMarkAsSensitive) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Visibility,
|
||||||
|
contentDescription = stringResource(R.string.content_warning),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(18.dp)
|
||||||
|
.align(Alignment.BottomStart),
|
||||||
|
tint = MaterialTheme.colors.onBackground
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Warning,
|
||||||
|
contentDescription = stringResource(R.string.content_warning),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(10.dp)
|
||||||
|
.align(Alignment.TopEnd),
|
||||||
|
tint = MaterialTheme.colors.onBackground
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.VisibilityOff,
|
||||||
|
contentDescription = stringResource(id = R.string.content_warning),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(18.dp)
|
||||||
|
.align(Alignment.BottomStart),
|
||||||
|
tint = Color.Red
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Warning,
|
||||||
|
contentDescription = stringResource(id = R.string.content_warning),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(10.dp)
|
||||||
|
.align(Alignment.TopEnd),
|
||||||
|
tint = Color.Yellow
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CloseButton(onCancel: () -> Unit) {
|
fun CloseButton(onCancel: () -> Unit) {
|
||||||
Button(
|
Button(
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
var forwardZapTo by mutableStateOf<User?>(null)
|
var forwardZapTo by mutableStateOf<User?>(null)
|
||||||
var forwardZapToEditting by mutableStateOf(TextFieldValue(""))
|
var forwardZapToEditting by mutableStateOf(TextFieldValue(""))
|
||||||
|
|
||||||
|
// NSFW, Sensitive
|
||||||
|
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||||
|
|
||||||
open fun load(account: Account, replyingTo: Note?, quote: Note?) {
|
open fun load(account: Account, replyingTo: Note?, quote: Note?) {
|
||||||
originalNote = replyingTo
|
originalNote = replyingTo
|
||||||
replyingTo?.let { replyNote ->
|
replyingTo?.let { replyNote ->
|
||||||
@@ -97,6 +100,7 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
contentToAddUrl = null
|
contentToAddUrl = null
|
||||||
|
|
||||||
wantsForwardZapTo = false
|
wantsForwardZapTo = false
|
||||||
|
wantsToMarkAsSensitive = false
|
||||||
forwardZapTo = null
|
forwardZapTo = null
|
||||||
forwardZapToEditting = TextFieldValue("")
|
forwardZapToEditting = TextFieldValue("")
|
||||||
|
|
||||||
@@ -118,13 +122,13 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (wantsPoll) {
|
if (wantsPoll) {
|
||||||
account?.sendPoll(tagger.message, tagger.replyTos, tagger.mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt, zapReceiver)
|
account?.sendPoll(tagger.message, tagger.replyTos, tagger.mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt, zapReceiver, wantsToMarkAsSensitive)
|
||||||
} else if (originalNote?.channel() != null) {
|
} else if (originalNote?.channel() != null) {
|
||||||
account?.sendChannelMessage(tagger.message, tagger.channel!!.idHex, tagger.replyTos, tagger.mentions, zapReceiver)
|
account?.sendChannelMessage(tagger.message, tagger.channel!!.idHex, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive)
|
||||||
} else if (originalNote?.event is PrivateDmEvent) {
|
} else if (originalNote?.event is PrivateDmEvent) {
|
||||||
account?.sendPrivateMessage(tagger.message, originalNote!!.author!!.pubkeyHex, originalNote!!, tagger.mentions, zapReceiver)
|
account?.sendPrivateMessage(tagger.message, originalNote!!.author!!.pubkeyHex, originalNote!!, tagger.mentions, zapReceiver, wantsToMarkAsSensitive)
|
||||||
} else {
|
} else {
|
||||||
account?.sendPost(tagger.message, tagger.replyTos, tagger.mentions, null, zapReceiver)
|
account?.sendPost(tagger.message, tagger.replyTos, tagger.mentions, null, zapReceiver, wantsToMarkAsSensitive)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
@@ -183,6 +187,7 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
wantsInvoice = false
|
wantsInvoice = false
|
||||||
|
|
||||||
wantsForwardZapTo = false
|
wantsForwardZapTo = false
|
||||||
|
wantsToMarkAsSensitive = false
|
||||||
forwardZapTo = null
|
forwardZapTo = null
|
||||||
forwardZapToEditting = TextFieldValue("")
|
forwardZapToEditting = TextFieldValue("")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.ButtonDefaults
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
|
import androidx.compose.material.icons.rounded.Warning
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SensitivityWarning(
|
||||||
|
hasSensitiveContent: Boolean,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
|
||||||
|
var showContentWarningNote by remember(accountState) {
|
||||||
|
mutableStateOf(accountState?.account?.showSensitiveContent != true && hasSensitiveContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showContentWarningNote) {
|
||||||
|
ContentWarningNote() {
|
||||||
|
showContentWarningNote = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ContentWarningNote(onDismiss: () -> Unit) {
|
||||||
|
Column() {
|
||||||
|
Row(modifier = Modifier.padding(horizontal = 12.dp)) {
|
||||||
|
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||||
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.height(80.dp)
|
||||||
|
.width(90.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Visibility,
|
||||||
|
contentDescription = stringResource(R.string.content_warning),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(70.dp)
|
||||||
|
.align(Alignment.BottomStart),
|
||||||
|
tint = MaterialTheme.colors.onBackground
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Warning,
|
||||||
|
contentDescription = stringResource(R.string.content_warning),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(30.dp)
|
||||||
|
.align(Alignment.TopEnd),
|
||||||
|
tint = MaterialTheme.colors.onBackground
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.content_warning),
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row() {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.content_warning_explanation),
|
||||||
|
color = Color.Gray,
|
||||||
|
modifier = Modifier.padding(top = 10.dp),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.padding(top = 10.dp),
|
||||||
|
onClick = onDismiss,
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
colors = ButtonDefaults
|
||||||
|
.buttonColors(
|
||||||
|
backgroundColor = MaterialTheme.colors.primary
|
||||||
|
),
|
||||||
|
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.show_anyway),
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,10 +55,11 @@ class AddBountyAmountViewModel : ViewModel() {
|
|||||||
|
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
account?.sendPost(
|
account?.sendPost(
|
||||||
newValue.toString(),
|
message = newValue.toString(),
|
||||||
listOfNotNull(bounty),
|
replyTo = listOfNotNull(bounty),
|
||||||
listOfNotNull(bounty?.author),
|
mentions = listOfNotNull(bounty?.author),
|
||||||
listOf("bounty-added-reward")
|
tags = listOf("bounty-added-reward"),
|
||||||
|
wantsToMarkAsSensitive = false
|
||||||
)
|
)
|
||||||
|
|
||||||
nextAmount = TextFieldValue("")
|
nextAmount = TextFieldValue("")
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
|||||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -369,15 +370,21 @@ private fun RenderRegularTextNote(
|
|||||||
val modifier = remember { Modifier.padding(top = 5.dp) }
|
val modifier = remember { Modifier.padding(top = 5.dp) }
|
||||||
|
|
||||||
if (eventContent != null) {
|
if (eventContent != null) {
|
||||||
TranslatableRichTextViewer(
|
val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false }
|
||||||
content = eventContent,
|
SensitivityWarning(
|
||||||
canPreview = canPreview,
|
hasSensitiveContent = hasSensitiveContent,
|
||||||
modifier = modifier,
|
accountViewModel = accountViewModel
|
||||||
tags = tags,
|
) {
|
||||||
backgroundColor = backgroundBubbleColor,
|
TranslatableRichTextViewer(
|
||||||
accountViewModel = accountViewModel,
|
content = eventContent,
|
||||||
nav = nav
|
canPreview = canPreview,
|
||||||
)
|
modifier = modifier,
|
||||||
|
tags = tags,
|
||||||
|
backgroundColor = backgroundBubbleColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
TranslatableRichTextViewer(
|
TranslatableRichTextViewer(
|
||||||
content = stringResource(id = R.string.could_not_decrypt_the_message),
|
content = stringResource(id = R.string.could_not_decrypt_the_message),
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
|||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.components.VideoView
|
import com.vitorpamplona.amethyst.ui.components.VideoView
|
||||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContent
|
import com.vitorpamplona.amethyst.ui.components.ZoomableContent
|
||||||
@@ -166,6 +167,7 @@ fun NoteCompose(
|
|||||||
|
|
||||||
val noteEvent = remember(noteState) { note.event }
|
val noteEvent = remember(noteState) { note.event }
|
||||||
val baseChannel = remember(noteState) { note.channel() }
|
val baseChannel = remember(noteState) { note.channel() }
|
||||||
|
val isSensitive = remember(noteState) { note.event?.isSensitive() ?: false }
|
||||||
|
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@@ -183,7 +185,7 @@ fun NoteCompose(
|
|||||||
note.let {
|
note.let {
|
||||||
NoteQuickActionMenu(it, popupExpanded, { popupExpanded = false }, accountViewModel)
|
NoteQuickActionMenu(it, popupExpanded, { popupExpanded = false }, accountViewModel)
|
||||||
}
|
}
|
||||||
} else if (account.isHidden(noteForReports.author!!)) {
|
} else if (account.isHidden(noteForReports.author!!) || (isSensitive && account.showSensitiveContent == false)) {
|
||||||
// Does nothing
|
// Does nothing
|
||||||
} else {
|
} else {
|
||||||
var showHiddenNote by remember { mutableStateOf(false) }
|
var showHiddenNote by remember { mutableStateOf(false) }
|
||||||
@@ -435,13 +437,10 @@ private fun RenderTextEvent(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val tags = remember(note.event?.id()) { note.event?.tags() }
|
val eventContent = remember(note.event) { accountViewModel.decrypt(note) }
|
||||||
val hashtags = remember(note.event?.id()) { note.event?.hashtags() ?: emptyList() }
|
|
||||||
val eventContent = remember(note.event?.id()) { accountViewModel.decrypt(note) }
|
|
||||||
val modifier = remember(note.event?.id()) { Modifier.fillMaxWidth() }
|
|
||||||
val isAuthorTheLoggedUser = remember(note.event?.id()) { accountViewModel.isLoggedUser(note.author) }
|
|
||||||
|
|
||||||
if (eventContent != null) {
|
if (eventContent != null) {
|
||||||
|
val isAuthorTheLoggedUser = remember(note.event) { accountViewModel.isLoggedUser(note.author) }
|
||||||
if (makeItShort && isAuthorTheLoggedUser) {
|
if (makeItShort && isAuthorTheLoggedUser) {
|
||||||
Text(
|
Text(
|
||||||
text = eventContent,
|
text = eventContent,
|
||||||
@@ -450,16 +449,27 @@ private fun RenderTextEvent(
|
|||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
TranslatableRichTextViewer(
|
val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false }
|
||||||
content = eventContent,
|
|
||||||
canPreview = canPreview && !makeItShort,
|
|
||||||
modifier = modifier,
|
|
||||||
tags = tags,
|
|
||||||
backgroundColor = backgroundColor,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
nav = nav
|
|
||||||
)
|
|
||||||
|
|
||||||
|
SensitivityWarning(
|
||||||
|
hasSensitiveContent = hasSensitiveContent,
|
||||||
|
accountViewModel = accountViewModel
|
||||||
|
) {
|
||||||
|
val modifier = remember(note.event) { Modifier.fillMaxWidth() }
|
||||||
|
val tags = remember(note.event) { note.event?.tags() }
|
||||||
|
|
||||||
|
TranslatableRichTextViewer(
|
||||||
|
content = eventContent,
|
||||||
|
canPreview = canPreview && !makeItShort,
|
||||||
|
modifier = modifier,
|
||||||
|
tags = tags,
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val hashtags = remember(note.event) { note.event?.hashtags() ?: emptyList() }
|
||||||
DisplayUncitedHashtags(hashtags, eventContent, nav)
|
DisplayUncitedHashtags(hashtags, eventContent, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -494,25 +504,31 @@ private fun RenderPoll(
|
|||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
TranslatableRichTextViewer(
|
val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false }
|
||||||
eventContent,
|
SensitivityWarning(
|
||||||
canPreview = canPreview && !makeItShort,
|
hasSensitiveContent = hasSensitiveContent,
|
||||||
Modifier.fillMaxWidth(),
|
accountViewModel = accountViewModel
|
||||||
noteEvent.tags(),
|
) {
|
||||||
backgroundColor,
|
TranslatableRichTextViewer(
|
||||||
accountViewModel,
|
eventContent,
|
||||||
nav
|
canPreview = canPreview && !makeItShort,
|
||||||
)
|
Modifier.fillMaxWidth(),
|
||||||
|
noteEvent.tags(),
|
||||||
|
backgroundColor,
|
||||||
|
accountViewModel,
|
||||||
|
nav
|
||||||
|
)
|
||||||
|
|
||||||
|
PollNote(
|
||||||
|
note,
|
||||||
|
canPreview = canPreview && !makeItShort,
|
||||||
|
backgroundColor,
|
||||||
|
accountViewModel,
|
||||||
|
nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav)
|
DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav)
|
||||||
|
|
||||||
PollNote(
|
|
||||||
note,
|
|
||||||
canPreview = canPreview && !makeItShort,
|
|
||||||
backgroundColor,
|
|
||||||
accountViewModel,
|
|
||||||
nav
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!makeItShort) {
|
if (!makeItShort) {
|
||||||
@@ -586,15 +602,21 @@ private fun RenderPrivateMessage(
|
|||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
TranslatableRichTextViewer(
|
val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false }
|
||||||
content = eventContent,
|
SensitivityWarning(
|
||||||
canPreview = canPreview && !makeItShort,
|
hasSensitiveContent = hasSensitiveContent,
|
||||||
modifier = modifier,
|
accountViewModel = accountViewModel
|
||||||
tags = tags,
|
) {
|
||||||
backgroundColor = backgroundColor,
|
TranslatableRichTextViewer(
|
||||||
accountViewModel = accountViewModel,
|
content = eventContent,
|
||||||
nav = nav
|
canPreview = canPreview && !makeItShort,
|
||||||
)
|
modifier = modifier,
|
||||||
|
tags = tags,
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
DisplayUncitedHashtags(hashtags, eventContent, nav)
|
DisplayUncitedHashtags(hashtags, eventContent, nav)
|
||||||
}
|
}
|
||||||
@@ -2124,7 +2146,9 @@ fun UserPicture(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val myIconModifier = remember {
|
val myIconModifier = remember {
|
||||||
Modifier.width(size.div(3.5f)).height(size.div(3.5f))
|
Modifier
|
||||||
|
.width(size.div(3.5f))
|
||||||
|
.height(size.div(3.5f))
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(myIconBoxModifier, contentAlignment = Alignment.Center) {
|
Box(myIconBoxModifier, contentAlignment = Alignment.Center) {
|
||||||
@@ -2147,7 +2171,9 @@ data class DropDownParams(
|
|||||||
val isFollowingAuthor: Boolean,
|
val isFollowingAuthor: Boolean,
|
||||||
val isPrivateBookmarkNote: Boolean,
|
val isPrivateBookmarkNote: Boolean,
|
||||||
val isPublicBookmarkNote: Boolean,
|
val isPublicBookmarkNote: Boolean,
|
||||||
val isLoggedUser: Boolean
|
val isLoggedUser: Boolean,
|
||||||
|
val isSensitive: Boolean,
|
||||||
|
val showSensitiveContent: Boolean?
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -2158,20 +2184,23 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
|||||||
var reportDialogShowing by remember { mutableStateOf(false) }
|
var reportDialogShowing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val bookmarkState by accountViewModel.userProfile().live().bookmarks.observeAsState()
|
val bookmarkState by accountViewModel.userProfile().live().bookmarks.observeAsState()
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
|
||||||
var state by remember {
|
var state by remember {
|
||||||
mutableStateOf<DropDownParams>(
|
mutableStateOf<DropDownParams>(
|
||||||
DropDownParams(false, false, false, false)
|
DropDownParams(false, false, false, false, false, null)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = note, key2 = bookmarkState) {
|
LaunchedEffect(key1 = note, key2 = bookmarkState, key3 = accountState) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
state = DropDownParams(
|
state = DropDownParams(
|
||||||
accountViewModel.isFollowing(note.author),
|
isFollowingAuthor = accountViewModel.isFollowing(note.author),
|
||||||
accountViewModel.isInPrivateBookmarks(note),
|
isPrivateBookmarkNote = accountViewModel.isInPrivateBookmarks(note),
|
||||||
accountViewModel.isInPublicBookmarks(note),
|
isPublicBookmarkNote = accountViewModel.isInPublicBookmarks(note),
|
||||||
accountViewModel.isLoggedUser(note.author)
|
isLoggedUser = accountViewModel.isLoggedUser(note.author),
|
||||||
|
isSensitive = note.event?.isSensitive() ?: false,
|
||||||
|
showSensitiveContent = accountState?.account?.showSensitiveContent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2249,6 +2278,24 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
|||||||
Text("Block / Report")
|
Text("Block / Report")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// if (state.isSensitive) {
|
||||||
|
Divider()
|
||||||
|
if (state.showSensitiveContent == null || state.showSensitiveContent == true) {
|
||||||
|
DropdownMenuItem(onClick = { accountViewModel.hideSensitiveContent(); onDismiss() }) {
|
||||||
|
Text(stringResource(R.string.content_warning_hide_all_sensitive_content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.showSensitiveContent == null || state.showSensitiveContent == false) {
|
||||||
|
DropdownMenuItem(onClick = { accountViewModel.disableContentWarnings(); onDismiss() }) {
|
||||||
|
Text(stringResource(R.string.content_warning_show_all_sensitive_content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.showSensitiveContent != null) {
|
||||||
|
DropdownMenuItem(onClick = { accountViewModel.seeContentWarnings(); onDismiss() }) {
|
||||||
|
Text(stringResource(R.string.content_warning_see_warnings))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reportDialogShowing) {
|
if (reportDialogShowing) {
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ fun PollNote(
|
|||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = remember(accountState) { accountState?.account } ?: return
|
val account = remember(accountState) { accountState?.account } ?: return
|
||||||
|
|
||||||
val pollViewModel: PollNoteViewModel = viewModel()
|
val pollViewModel: PollNoteViewModel = viewModel(
|
||||||
|
key = baseNote.idHex
|
||||||
|
)
|
||||||
|
|
||||||
LaunchedEffect(key1 = baseNote) {
|
LaunchedEffect(key1 = baseNote) {
|
||||||
pollViewModel.load(account, baseNote)
|
pollViewModel.load(account, baseNote)
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import com.vitorpamplona.amethyst.service.model.PeopleListEvent
|
|||||||
import com.vitorpamplona.amethyst.service.model.PinListEvent
|
import com.vitorpamplona.amethyst.service.model.PinListEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||||
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.note.*
|
import com.vitorpamplona.amethyst.ui.note.*
|
||||||
import com.vitorpamplona.amethyst.ui.note.BadgeDisplay
|
import com.vitorpamplona.amethyst.ui.note.BadgeDisplay
|
||||||
@@ -376,15 +377,22 @@ fun NoteMaster(
|
|||||||
!noteForReports.hasAnyReports()
|
!noteForReports.hasAnyReports()
|
||||||
|
|
||||||
if (eventContent != null) {
|
if (eventContent != null) {
|
||||||
TranslatableRichTextViewer(
|
val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false }
|
||||||
eventContent,
|
|
||||||
canPreview,
|
SensitivityWarning(
|
||||||
Modifier.fillMaxWidth(),
|
hasSensitiveContent = hasSensitiveContent,
|
||||||
note.event?.tags(),
|
accountViewModel = accountViewModel
|
||||||
MaterialTheme.colors.background,
|
) {
|
||||||
accountViewModel,
|
TranslatableRichTextViewer(
|
||||||
nav
|
eventContent,
|
||||||
)
|
canPreview,
|
||||||
|
Modifier.fillMaxWidth(),
|
||||||
|
note.event?.tags(),
|
||||||
|
MaterialTheme.colors.background,
|
||||||
|
accountViewModel,
|
||||||
|
nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav)
|
DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav)
|
||||||
|
|
||||||
|
|||||||
@@ -232,6 +232,18 @@ class AccountViewModel(private val account: Account) : ViewModel() {
|
|||||||
account.setHideBlockAlertDialog()
|
account.setHideBlockAlertDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hideSensitiveContent() {
|
||||||
|
account.updateShowSensitiveContent(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disableContentWarnings() {
|
||||||
|
account.updateShowSensitiveContent(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun seeContentWarnings() {
|
||||||
|
account.updateShowSensitiveContent(null)
|
||||||
|
}
|
||||||
|
|
||||||
class Factory(val account: Account) : ViewModelProvider.Factory {
|
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||||
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
|
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
|
||||||
return AccountViewModel(account) as AccountViewModel
|
return AccountViewModel(account) as AccountViewModel
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ fun ChannelScreen(
|
|||||||
onPost = {
|
onPost = {
|
||||||
val tagger = NewMessageTagger(channel, listOfNotNull(replyTo.value?.author), listOfNotNull(replyTo.value), channelScreenModel.message.text)
|
val tagger = NewMessageTagger(channel, listOfNotNull(replyTo.value?.author), listOfNotNull(replyTo.value), channelScreenModel.message.text)
|
||||||
tagger.run()
|
tagger.run()
|
||||||
account.sendChannelMessage(tagger.message, channel.idHex, tagger.replyTos, tagger.mentions)
|
account.sendChannelMessage(tagger.message, channel.idHex, tagger.replyTos, tagger.mentions, wantsToMarkAsSensitive = false)
|
||||||
channelScreenModel.message = TextFieldValue("")
|
channelScreenModel.message = TextFieldValue("")
|
||||||
replyTo.value = null
|
replyTo.value = null
|
||||||
feedViewModel.invalidateData() // Don't wait a full second before updating
|
feedViewModel.invalidateData() // Don't wait a full second before updating
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, nav: (St
|
|||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
PostButton(
|
PostButton(
|
||||||
onPost = {
|
onPost = {
|
||||||
account.sendPrivateMessage(chatRoomScreenModel.message.text, userId, replyTo.value, null)
|
account.sendPrivateMessage(chatRoomScreenModel.message.text, userId, replyTo.value, null, wantsToMarkAsSensitive = false)
|
||||||
chatRoomScreenModel.message = TextFieldValue("")
|
chatRoomScreenModel.message = TextFieldValue("")
|
||||||
replyTo.value = null
|
replyTo.value = null
|
||||||
feedViewModel.invalidateData() // Don't wait a full second before updating
|
feedViewModel.invalidateData() // Don't wait a full second before updating
|
||||||
|
|||||||
@@ -404,4 +404,10 @@
|
|||||||
<string name="channel_list_join_channel">Join</string>
|
<string name="channel_list_join_channel">Join</string>
|
||||||
|
|
||||||
<string name="today">Today</string>
|
<string name="today">Today</string>
|
||||||
|
|
||||||
|
<string name="content_warning">Content warning</string>
|
||||||
|
<string name="content_warning_explanation">This post contains sensitive content which some people may find offensive or disturbing</string>
|
||||||
|
<string name="content_warning_hide_all_sensitive_content">Always hide sensitive content</string>
|
||||||
|
<string name="content_warning_show_all_sensitive_content">Always show sensitive content</string>
|
||||||
|
<string name="content_warning_see_warnings">Always show content warnings</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user