Adding Bounty information to Note

pull/310/head
Vitor Pamplona 2023-03-23 10:49:01 -04:00
rodzic 7375c9e9e3
commit 4589e6f329
7 zmienionych plików z 213 dodań i 76 usunięć

Wyświetl plik

@ -235,6 +235,20 @@ open class Note(val idHex: String) {
}.sumOf { it }
}
fun pledgedAmountByOthers(): BigDecimal {
return replies
.filter { it.event?.isTaggedHash("bounty-added-reward") ?: false }
.mapNotNull {
try {
BigDecimal(it.event?.content())
} catch (e: Exception) {
null
// do nothing if it can't convert to bigdecimal
}
}
.sumOf { it }
}
fun hasAnyReports(): Boolean {
val dayAgo = Date().time / 1000 - 24 * 60 * 60
return reports.isNotEmpty() ||

Wyświetl plik

@ -9,6 +9,7 @@ import fr.acinq.secp256k1.Secp256k1
import nostr.postr.Utils
import nostr.postr.toHex
import java.lang.reflect.Type
import java.math.BigDecimal
import java.security.MessageDigest
import java.util.*
@ -55,6 +56,14 @@ open class Event(
override fun isTaggedHashes(hashtags: Set<String>) = tags.any { it.getOrNull(0) == "t" && it.getOrNull(1)?.lowercase() in hashtags }
override fun firstIsTaggedHashes(hashtags: Set<String>) = tags.firstOrNull { it.getOrNull(0) == "t" && it.getOrNull(1)?.lowercase() in hashtags }?.getOrNull(1)
override fun getReward(): BigDecimal? {
return try {
tags.filter { it.firstOrNull() == "reward" }.mapNotNull { BigDecimal(it.getOrNull(1)) }.firstOrNull()
} catch (e: Exception) {
null
}
}
/**
* Checks if the ID is correct and then if the pubKey's secret key signed the event.
*/

Wyświetl plik

@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst.service.model
import com.vitorpamplona.amethyst.model.HexKey
import java.math.BigDecimal
interface EventInterface {
fun id(): HexKey
@ -29,4 +30,6 @@ interface EventInterface {
fun isTaggedHashes(hashtag: Set<String>): Boolean
fun firstIsTaggedHashes(hashtag: Set<String>): String?
fun hashtags(): List<String>
fun getReward(): BigDecimal?
}

Wyświetl plik

@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@ -76,18 +77,18 @@ fun nip05VerificationAsAState(user: UserMetadata, pubkeyHex: String): State<Bool
}
@Composable
fun ObserveDisplayNip05Status(baseNote: Note) {
fun ObserveDisplayNip05Status(baseNote: Note, columnModifier: Modifier = Modifier) {
val noteState by baseNote.live().metadata.observeAsState()
val note = noteState?.note ?: return
val author = note.author
if (author != null) {
ObserveDisplayNip05Status(author)
ObserveDisplayNip05Status(author, columnModifier)
}
}
@Composable
fun ObserveDisplayNip05Status(baseUser: User) {
fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifier) {
val userState by baseUser.live().metadata.observeAsState()
val user = userState?.user ?: return
@ -96,52 +97,54 @@ fun ObserveDisplayNip05Status(baseUser: User) {
user.nip05()?.let { nip05 ->
if (nip05.split("@").size == 2) {
val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
Row(verticalAlignment = Alignment.CenterVertically) {
if (nip05.split("@")[0] != "_") {
Text(
text = AnnotatedString(nip05.split("@")[0]),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
Column(modifier = columnModifier) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (nip05.split("@")[0] != "_") {
Text(
text = AnnotatedString(nip05.split("@")[0]),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (nip05Verified == null) {
Icon(
tint = Color.Yellow,
imageVector = Icons.Default.Downloading,
contentDescription = "Downloading",
modifier = Modifier
.size(14.dp)
.padding(top = 1.dp)
)
} else if (nip05Verified == true) {
Icon(
painter = painterResource(R.drawable.ic_verified),
"NIP-05 Verified",
tint = Nip05.copy(0.52f),
modifier = Modifier
.size(14.dp)
.padding(top = 1.dp)
)
} else {
Icon(
tint = Color.Red,
imageVector = Icons.Default.Report,
contentDescription = "Invalid Nip05",
modifier = Modifier
.size(14.dp)
.padding(top = 1.dp)
)
}
ClickableText(
text = AnnotatedString(nip05.split("@")[1]),
onClick = { nip05.let { runCatching { uri.openUri("https://${it.split("@")[1]}") } } },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(0.52f)),
maxLines = 1,
overflow = TextOverflow.Ellipsis
overflow = TextOverflow.Visible
)
}
if (nip05Verified == null) {
Icon(
tint = Color.Yellow,
imageVector = Icons.Default.Downloading,
contentDescription = "Downloading",
modifier = Modifier
.size(14.dp)
.padding(top = 1.dp)
)
} else if (nip05Verified == true) {
Icon(
painter = painterResource(R.drawable.ic_verified),
"NIP-05 Verified",
tint = Nip05.copy(0.52f),
modifier = Modifier
.size(14.dp)
.padding(top = 1.dp)
)
} else {
Icon(
tint = Color.Red,
imageVector = Icons.Default.Report,
contentDescription = "Invalid Nip05",
modifier = Modifier
.size(14.dp)
.padding(top = 1.dp)
)
}
ClickableText(
text = AnnotatedString(nip05.split("@")[1]),
onClick = { nip05.let { runCatching { uri.openUri("https://${it.split("@")[1]}") } } },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(0.52f)),
maxLines = 1,
overflow = TextOverflow.Visible
)
}
}
}

Wyświetl plik

@ -10,8 +10,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bolt
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.Bolt
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
@ -42,6 +44,7 @@ import coil.compose.AsyncImage
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@ -50,6 +53,7 @@ import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
import com.vitorpamplona.amethyst.service.model.EventInterface
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.service.model.ReactionEvent
@ -65,9 +69,11 @@ import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.Following
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.math.BigDecimal
@OptIn(ExperimentalFoundationApi::class)
@Composable
@ -273,15 +279,8 @@ fun NoteCompose(
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
}
val firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet())
if (firstTag != null) {
ClickableText(
text = AnnotatedString(" #$firstTag"),
onClick = { navController.navigate("Hashtag/$firstTag") },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(alpha = 0.52f))
)
} else {
DisplayFollowingHashtagsInPost(noteEvent, account, navController)
}
Text(
@ -306,7 +305,14 @@ fun NoteCompose(
}
if (note.author != null && !makeItShort && !isQuotedNote) {
ObserveDisplayNip05Status(note.author!!)
Row(verticalAlignment = Alignment.CenterVertically) {
ObserveDisplayNip05Status(note.author!!, Modifier.weight(1f))
val baseReward = noteEvent.getReward()
if (baseReward != null) {
DisplayReward(baseReward, baseNote, navController)
}
}
}
Spacer(modifier = Modifier.height(3.dp))
@ -483,24 +489,7 @@ fun NoteCompose(
navController
)
val hashtags = noteEvent.hashtags()
if (hashtags.isNotEmpty()) {
FlowRow() {
hashtags.forEach {
if (!eventContent.contains(it, true)) {
ClickableText(
text = AnnotatedString("#$it "),
onClick = { navController.navigate("Hashtag/$it") },
style = LocalTextStyle.current.copy(
color = MaterialTheme.colors.primary.copy(
alpha = 0.52f
)
)
)
}
}
}
}
DisplayUncitedHashtags(noteEvent, eventContent, navController)
}
if (!makeItShort) {
@ -520,6 +509,107 @@ fun NoteCompose(
}
}
@Composable
fun DisplayFollowingHashtagsInPost(
noteEvent: EventInterface,
account: Account,
navController: NavController
) {
Column() {
Row(verticalAlignment = Alignment.CenterVertically) {
val firstTag =
noteEvent.firstIsTaggedHashes(account.followingTagSet())
if (firstTag != null) {
ClickableText(
text = AnnotatedString(" #$firstTag"),
onClick = { navController.navigate("Hashtag/$firstTag") },
style = LocalTextStyle.current.copy(
color = MaterialTheme.colors.primary.copy(
alpha = 0.52f
)
)
)
}
}
}
}
@Composable
fun DisplayUncitedHashtags(
noteEvent: EventInterface,
eventContent: String,
navController: NavController
) {
val hashtags = noteEvent.hashtags()
if (hashtags.isNotEmpty()) {
FlowRow() {
hashtags.forEach {
if (!eventContent.contains(it, true)) {
ClickableText(
text = AnnotatedString("#$it "),
onClick = { navController.navigate("Hashtag/$it") },
style = LocalTextStyle.current.copy(
color = MaterialTheme.colors.primary.copy(
alpha = 0.52f
)
)
)
}
}
}
}
}
@Composable
fun DisplayReward(
baseReward: BigDecimal,
baseNote: Note,
navController: NavController
) {
Column() {
Row(verticalAlignment = Alignment.CenterVertically) {
ClickableText(
text = AnnotatedString("#bounty"),
onClick = { navController.navigate("Hashtag/bounty") },
style = LocalTextStyle.current.copy(
color = MaterialTheme.colors.primary.copy(
alpha = 0.52f
)
)
)
Icon(
imageVector = Icons.Default.Bolt,
contentDescription = stringResource(R.string.zaps),
modifier = Modifier.size(20.dp),
tint = BitcoinOrange
)
val repliesState by baseNote.live().replies.observeAsState()
val replyNote = repliesState?.note
var rewardAmount by remember {
mutableStateOf<BigDecimal?>(
baseReward
)
}
LaunchedEffect(key1 = repliesState) {
withContext(Dispatchers.IO) {
replyNote?.pledgedAmountByOthers()?.let {
rewardAmount = baseReward.add(it)
}
}
}
Text(
showAmount(rewardAmount),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
}
}
}
@Composable
fun BadgeDisplay(baseNote: Note) {
val background = MaterialTheme.colors.background

Wyświetl plik

@ -79,7 +79,11 @@ private fun lightenColor(color: Color, amount: Float): Color {
val externalLinkForNote = { note: Note ->
if (note is AddressableNote) {
"https://habla.news/a/${note.address().toNAddr()}"
if (note.event?.getReward() != null) {
"https://nostrbounties.com/b/${note.address().toNAddr()}"
} else {
"https://habla.news/a/${note.address().toNAddr()}"
}
} else {
"https://snort.social/e/${note.idNote()}"
}

Wyświetl plik

@ -57,6 +57,9 @@ import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
import com.vitorpamplona.amethyst.ui.note.BadgeDisplay
import com.vitorpamplona.amethyst.ui.note.BlankNote
import com.vitorpamplona.amethyst.ui.note.DisplayFollowingHashtagsInPost
import com.vitorpamplona.amethyst.ui.note.DisplayReward
import com.vitorpamplona.amethyst.ui.note.DisplayUncitedHashtags
import com.vitorpamplona.amethyst.ui.note.HiddenNote
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
import com.vitorpamplona.amethyst.ui.note.NoteCompose
@ -250,6 +253,8 @@ fun NoteMaster(
Row(verticalAlignment = Alignment.CenterVertically) {
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
DisplayFollowingHashtagsInPost(noteEvent, account, navController)
Text(
timeAgo(note.createdAt(), context = context),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
@ -271,7 +276,14 @@ fun NoteMaster(
}
}
ObserveDisplayNip05Status(baseNote)
Row(verticalAlignment = Alignment.CenterVertically) {
ObserveDisplayNip05Status(baseNote, Modifier.weight(1f))
val baseReward = noteEvent.getReward()
if (baseReward != null) {
DisplayReward(baseReward, baseNote, navController)
}
}
}
}
@ -341,6 +353,8 @@ fun NoteMaster(
accountViewModel,
navController
)
DisplayUncitedHashtags(noteEvent, eventContent, navController)
}
ReactionsRow(note, accountViewModel)