Quote Note support
This commit is contained in:
@@ -41,6 +41,8 @@ fun decodePublicKey(key: String): ByteArray {
|
|||||||
Persona(privKey = key.bechToBytes()).pubKey
|
Persona(privKey = key.bechToBytes()).pubKey
|
||||||
} else if (key.startsWith("npub")) {
|
} else if (key.startsWith("npub")) {
|
||||||
key.bechToBytes()
|
key.bechToBytes()
|
||||||
|
} else if (key.startsWith("note")) {
|
||||||
|
key.bechToBytes()
|
||||||
} else { //if (pattern.matcher(key).matches()) {
|
} else { //if (pattern.matcher(key).matches()) {
|
||||||
//} else {
|
//} else {
|
||||||
Hex.decode(key)
|
Hex.decode(key)
|
||||||
|
|||||||
@@ -45,11 +45,9 @@ import nostr.postr.events.TextNoteEvent
|
|||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, account: Account) {
|
fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, account: Account) {
|
||||||
val postViewModel: NewPostViewModel = viewModel()
|
val postViewModel: NewPostViewModel = viewModel()
|
||||||
|
|
||||||
postViewModel.load(account, baseReplyTo)
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
// initialize focus reference to be able to request focus programmatically
|
// initialize focus reference to be able to request focus programmatically
|
||||||
@@ -59,6 +57,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, account: Account
|
|||||||
val scroolState = rememberScrollState()
|
val scroolState = rememberScrollState()
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
postViewModel.load(account, baseReplyTo, quote)
|
||||||
delay(100)
|
delay(100)
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class NewPostViewModel: ViewModel() {
|
|||||||
var userSuggestions by mutableStateOf<List<User>>(emptyList())
|
var userSuggestions by mutableStateOf<List<User>>(emptyList())
|
||||||
var userSuggestionAnchor: TextRange? = null
|
var userSuggestionAnchor: TextRange? = null
|
||||||
|
|
||||||
fun load(account: Account, replyingTo: Note?) {
|
fun load(account: Account, replyingTo: Note?, quote: Note?) {
|
||||||
originalNote = replyingTo
|
originalNote = replyingTo
|
||||||
replyingTo?.let { replyNote ->
|
replyingTo?.let { replyNote ->
|
||||||
this.replyTos = (replyNote.replyTo ?: emptyList()).plus(replyNote)
|
this.replyTos = (replyNote.replyTo ?: emptyList()).plus(replyNote)
|
||||||
@@ -45,29 +45,60 @@ class NewPostViewModel: ViewModel() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
quote?.let {
|
||||||
|
message = TextFieldValue(message.text + "\n\n@${it.idNote()}")
|
||||||
|
}
|
||||||
|
|
||||||
this.account = account
|
this.account = account
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addUserToMentionsIfNotInAndReturnIndex(user: User): Int {
|
fun addUserToMentions(user: User) {
|
||||||
val replyToSize = replyTos?.size ?: 0
|
mentions = if (mentions?.contains(user) == true) mentions else mentions?.plus(user) ?: listOf(user)
|
||||||
|
|
||||||
var myMentions = mentions
|
|
||||||
if (myMentions == null) {
|
|
||||||
mentions = listOf(user)
|
|
||||||
return replyToSize + 0 // position of the user
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val index = myMentions.indexOf(user)
|
fun addNoteToReplyTos(note: Note) {
|
||||||
|
note.author?.let { addUserToMentions(it) }
|
||||||
|
replyTos = if (replyTos?.contains(note) == true) replyTos else replyTos?.plus(note) ?: listOf(note)
|
||||||
|
}
|
||||||
|
|
||||||
if (index >= 0) return replyToSize + index
|
fun tagIndex(user: User): Int {
|
||||||
|
// Postr Events assembles replies before mentions in the tag order
|
||||||
|
return (replyTos?.size ?: 0) + (mentions?.indexOf(user) ?: 0)
|
||||||
|
}
|
||||||
|
|
||||||
myMentions = myMentions.plus(user)
|
fun tagIndex(note: Note): Int {
|
||||||
mentions = myMentions
|
// Postr Events assembles replies before mentions in the tag order
|
||||||
return replyToSize + myMentions.indexOf(user)
|
return (replyTos?.indexOf(note) ?: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendPost() {
|
fun sendPost() {
|
||||||
// Moves @npub to mentions
|
// adds all references to mentions and reply tos
|
||||||
|
message.text.split('\n').forEach { paragraph: String ->
|
||||||
|
paragraph.split(' ').forEach { word: String ->
|
||||||
|
try {
|
||||||
|
if (word.startsWith("@npub") && word.length >= 64) {
|
||||||
|
val keyB32 = word.substring(0, 64)
|
||||||
|
|
||||||
|
val key = decodePublicKey(keyB32.removePrefix("@"))
|
||||||
|
val user = LocalCache.getOrCreateUser(key.toHexKey())
|
||||||
|
|
||||||
|
addUserToMentions(user)
|
||||||
|
} else if (word.startsWith("@note") && word.length >= 64) {
|
||||||
|
val keyB32 = word.substring(0, 64)
|
||||||
|
|
||||||
|
val key = decodePublicKey(keyB32.removePrefix("@"))
|
||||||
|
val note = LocalCache.getOrCreateNote(key.toHexKey())
|
||||||
|
|
||||||
|
addNoteToReplyTos(note)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags the text in the correct order.
|
||||||
val newMessage = message.text.split('\n').map { paragraph: String ->
|
val newMessage = message.text.split('\n').map { paragraph: String ->
|
||||||
paragraph.split(' ').map { word: String ->
|
paragraph.split(' ').map { word: String ->
|
||||||
try {
|
try {
|
||||||
@@ -78,15 +109,20 @@ class NewPostViewModel: ViewModel() {
|
|||||||
val key = decodePublicKey(keyB32.removePrefix("@"))
|
val key = decodePublicKey(keyB32.removePrefix("@"))
|
||||||
val user = LocalCache.getOrCreateUser(key.toHexKey())
|
val user = LocalCache.getOrCreateUser(key.toHexKey())
|
||||||
|
|
||||||
val index = addUserToMentionsIfNotInAndReturnIndex(user)
|
"#[${tagIndex(user)}]$restOfWord"
|
||||||
|
} else if (word.startsWith("@note") && word.length >= 64) {
|
||||||
|
val keyB32 = word.substring(0, 64)
|
||||||
|
val restOfWord = word.substring(64)
|
||||||
|
|
||||||
val newWord = "#[${index}]"
|
val key = decodePublicKey(keyB32.removePrefix("@"))
|
||||||
|
val note = LocalCache.getOrCreateNote(key.toHexKey())
|
||||||
|
|
||||||
newWord + restOfWord
|
"#[${tagIndex(note)}]$restOfWord"
|
||||||
} else {
|
} else {
|
||||||
word
|
word
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
// if it can't parse the key, don't try to change.
|
// if it can't parse the key, don't try to change.
|
||||||
word
|
word
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,12 +109,21 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
mutableStateOf<Note?>(null)
|
mutableStateOf<Note?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var wantsToQuote by remember {
|
||||||
|
mutableStateOf<Note?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
if (wantsToReplyTo != null)
|
if (wantsToReplyTo != null)
|
||||||
NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, account)
|
NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, null, account)
|
||||||
|
|
||||||
|
if (wantsToQuote != null)
|
||||||
|
NewPostView({ wantsToQuote = null }, null, wantsToQuote, account)
|
||||||
|
|
||||||
var wantsToZap by remember { mutableStateOf(false) }
|
var wantsToZap by remember { mutableStateOf(false) }
|
||||||
var wantsToChangeZapAmount by remember { mutableStateOf(false) }
|
var wantsToChangeZapAmount by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
var wantsToBoost by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = 8.dp)
|
.padding(top = 8.dp)
|
||||||
@@ -156,7 +165,7 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
modifier = Modifier.then(Modifier.size(20.dp)),
|
modifier = Modifier.then(Modifier.size(20.dp)),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (account.isWriteable())
|
if (account.isWriteable())
|
||||||
accountViewModel.boost(baseNote)
|
wantsToBoost = true
|
||||||
else
|
else
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
@@ -167,6 +176,20 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
if (wantsToBoost) {
|
||||||
|
BoostTypeChoicePopup(
|
||||||
|
baseNote,
|
||||||
|
accountViewModel,
|
||||||
|
onDismiss = {
|
||||||
|
wantsToBoost = false
|
||||||
|
},
|
||||||
|
onQuote = {
|
||||||
|
wantsToBoost = false
|
||||||
|
wantsToQuote = baseNote
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (boostedNote?.isBoostedBy(account.userProfile()) == true) {
|
if (boostedNote?.isBoostedBy(account.userProfile()) == true) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_retweeted),
|
painter = painterResource(R.drawable.ic_retweeted),
|
||||||
@@ -345,6 +368,52 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun BoostTypeChoicePopup(baseNote: Note, accountViewModel: AccountViewModel, onDismiss: () -> Unit, onQuote: () -> Unit) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
|
Popup(
|
||||||
|
alignment = Alignment.BottomCenter,
|
||||||
|
offset = IntOffset(0, -50),
|
||||||
|
onDismissRequest = { onDismiss() }
|
||||||
|
) {
|
||||||
|
FlowRow() {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
|
onClick = {
|
||||||
|
accountViewModel.boost(baseNote)
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
colors = ButtonDefaults
|
||||||
|
.buttonColors(
|
||||||
|
backgroundColor = MaterialTheme.colors.primary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Boost", color = Color.White, textAlign = TextAlign.Center)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
|
onClick = onQuote,
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
colors = ButtonDefaults
|
||||||
|
.buttonColors(
|
||||||
|
backgroundColor = MaterialTheme.colors.primary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Quote", color = Color.White, textAlign = TextAlign.Center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ZapAmountChoicePopup(baseNote: Note, accountViewModel: AccountViewModel, onDismiss: () -> Unit, onChangeAmount: () -> Unit) {
|
private fun ZapAmountChoicePopup(baseNote: Note, accountViewModel: AccountViewModel, onDismiss: () -> Unit, onChangeAmount: () -> Unit) {
|
||||||
|
|||||||
Reference in New Issue
Block a user