add/remove private bookmarks

This commit is contained in:
greenart7c3 2023-08-25 08:51:23 -03:00
parent 46571a6029
commit e817f94045
10 changed files with 264 additions and 34 deletions

View File

@ -8,6 +8,7 @@ import androidx.core.os.ConfigurationCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.distinctUntilChanged
import com.vitorpamplona.amethyst.OptOutFromFilters
import com.vitorpamplona.amethyst.service.AmberUtils
import com.vitorpamplona.amethyst.service.FileHeader
import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource
import com.vitorpamplona.amethyst.service.relays.Client
@ -1237,6 +1238,96 @@ class Account(
LocalCache.consume(event)
}
fun addPrivateBookmark(note: Note, decryptedContent: String): BookmarkListEvent? {
val bookmarks = userProfile().latestBookmarkList
val privTags = mutableListOf<List<String>>()
val privEvents = if (note is AddressableNote) {
bookmarks?.privateTaggedEvents(decryptedContent) ?: emptyList()
} else {
bookmarks?.privateTaggedEvents(decryptedContent)?.plus(note.idHex) ?: listOf(note.idHex)
}
val privUsers = bookmarks?.privateTaggedUsers(decryptedContent) ?: emptyList()
val privAddresses = if (note is AddressableNote) {
bookmarks?.privateTaggedAddresses(decryptedContent)?.plus(note.address) ?: listOf(note.address)
} else {
bookmarks?.privateTaggedAddresses(decryptedContent) ?: emptyList()
}
privEvents.forEach {
privTags.add(listOf("e", it))
}
privUsers.forEach {
privTags.add(listOf("p", it))
}
privAddresses.forEach {
privTags.add(listOf("a", it.toTag()))
}
val msg = Event.mapper.writeValueAsString(privTags)
AmberUtils.encryptBookmark(msg, keyPair.pubKey.toHexKey())
if (AmberUtils.content.isBlank()) {
return null
}
return BookmarkListEvent.create(
"bookmark",
bookmarks?.taggedEvents() ?: emptyList(),
bookmarks?.taggedUsers() ?: emptyList(),
bookmarks?.taggedAddresses() ?: emptyList(),
AmberUtils.content,
keyPair.pubKey.toHexKey()
)
}
fun removePrivateBookmark(note: Note, decryptedContent: String): BookmarkListEvent? {
val bookmarks = userProfile().latestBookmarkList
val privTags = mutableListOf<List<String>>()
val privEvents = if (note is AddressableNote) {
bookmarks?.privateTaggedEvents(decryptedContent) ?: emptyList()
} else {
bookmarks?.privateTaggedEvents(decryptedContent)?.minus(note.idHex) ?: listOf(note.idHex)
}
val privUsers = bookmarks?.privateTaggedUsers(decryptedContent) ?: emptyList()
val privAddresses = if (note is AddressableNote) {
bookmarks?.privateTaggedAddresses(decryptedContent)?.minus(note.address) ?: listOf(note.address)
} else {
bookmarks?.privateTaggedAddresses(decryptedContent) ?: emptyList()
}
privEvents.forEach {
privTags.add(listOf("e", it))
}
privUsers.forEach {
privTags.add(listOf("p", it))
}
privAddresses.forEach {
privTags.add(listOf("a", it.toTag()))
}
val msg = Event.mapper.writeValueAsString(privTags)
AmberUtils.encryptBookmark(msg, keyPair.pubKey.toHexKey())
if (AmberUtils.content.isBlank()) {
return null
}
return BookmarkListEvent.create(
"bookmark",
bookmarks?.taggedEvents() ?: emptyList(),
bookmarks?.taggedUsers() ?: emptyList(),
bookmarks?.taggedAddresses() ?: emptyList(),
AmberUtils.content,
keyPair.pubKey.toHexKey()
)
}
fun addPrivateBookmark(note: Note) {
if (!isWriteable()) return
@ -1392,14 +1483,24 @@ class Account(
}
fun isInPrivateBookmarks(note: Note): Boolean {
if (!isWriteable()) return false
if (!isWriteable() && !loginWithAmber) return false
if (note is AddressableNote) {
return userProfile().latestBookmarkList?.privateTaggedAddresses(keyPair.privKey!!)
?.contains(note.address) == true
if (loginWithAmber) {
return if (note is AddressableNote) {
userProfile().latestBookmarkList?.privateTaggedAddresses(userProfile().latestBookmarkList?.decryptedContent ?: "")
?.contains(note.address) == true
} else {
userProfile().latestBookmarkList?.privateTaggedEvents(userProfile().latestBookmarkList?.decryptedContent ?: "")
?.contains(note.idHex) == true
}
} else {
return userProfile().latestBookmarkList?.privateTaggedEvents(keyPair.privKey!!)
?.contains(note.idHex) == true
return if (note is AddressableNote) {
userProfile().latestBookmarkList?.privateTaggedAddresses(keyPair.privKey!!)
?.contains(note.address) == true
} else {
userProfile().latestBookmarkList?.privateTaggedEvents(keyPair.privKey!!)
?.contains(note.idHex) == true
}
}
}

View File

@ -4,10 +4,10 @@ import android.util.Log
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.service.AmberUtils
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.components.BundledInsert
import com.vitorpamplona.amethyst.ui.dal.BookmarkPrivateFeedFilter
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.encoders.HexKey
@ -207,7 +207,7 @@ object LocalCache {
if (hexKey != null) {
val pubKey = Hex.encode(hexKey)
if (pubKey == event.pubKey) {
BookmarkPrivateFeedFilter.content = ""
AmberUtils.content = ""
}
}
user.updateBookmark(event)

View File

@ -0,0 +1,40 @@
package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.ui.actions.SignerType
import com.vitorpamplona.amethyst.ui.actions.openAmber
import com.vitorpamplona.quartz.encoders.HexKey
object AmberUtils {
var content: String = ""
var isActivityRunning: Boolean = false
fun decryptBookmark(encryptedContent: String, pubKey: HexKey) {
if (content.isBlank()) {
isActivityRunning = true
openAmber(
encryptedContent,
SignerType.NIP04_DECRYPT,
IntentUtils.decryptActivityResultLauncher,
pubKey
)
while (isActivityRunning) {
Thread.sleep(250)
}
}
}
fun encryptBookmark(decryptedContent: String, pubKey: HexKey) {
if (content.isBlank()) {
isActivityRunning = true
openAmber(
decryptedContent,
SignerType.NIP04_ENCRYPT,
IntentUtils.decryptActivityResultLauncher,
pubKey
)
while (isActivityRunning) {
Thread.sleep(250)
}
}
}
}

View File

@ -25,6 +25,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.ServiceManager
import com.vitorpamplona.amethyst.service.AmberUtils
import com.vitorpamplona.amethyst.service.IntentUtils
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
import com.vitorpamplona.amethyst.service.notifications.PushNotificationUtils
@ -32,7 +33,6 @@ import com.vitorpamplona.amethyst.service.notifications.RegisterAccounts
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.components.DefaultMutedSetting
import com.vitorpamplona.amethyst.ui.components.keepPlayingMutex
import com.vitorpamplona.amethyst.ui.dal.BookmarkPrivateFeedFilter
import com.vitorpamplona.amethyst.ui.navigation.Route
import com.vitorpamplona.amethyst.ui.navigation.debugState
import com.vitorpamplona.amethyst.ui.note.Nip47
@ -122,13 +122,13 @@ class MainActivity : AppCompatActivity() {
Toast.LENGTH_SHORT
).show()
}
BookmarkPrivateFeedFilter.isActivityRunning = false
AmberUtils.isActivityRunning = false
return@registerForActivityResult
}
val event = it.data?.getStringExtra("signature") ?: ""
BookmarkPrivateFeedFilter.content = event
BookmarkPrivateFeedFilter.isActivityRunning = false
AmberUtils.content = event
AmberUtils.isActivityRunning = false
}
super.onCreate(savedInstanceState)

View File

@ -3,15 +3,11 @@ package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.IntentUtils
import com.vitorpamplona.amethyst.ui.actions.SignerType
import com.vitorpamplona.amethyst.ui.actions.openAmber
import com.vitorpamplona.amethyst.service.AmberUtils
import com.vitorpamplona.quartz.encoders.toHexKey
object BookmarkPrivateFeedFilter : FeedFilter<Note>() {
lateinit var account: Account
var content: String = ""
var isActivityRunning: Boolean = false
override fun feedKey(): String {
return account.userProfile().latestBookmarkList?.id ?: ""
@ -21,22 +17,14 @@ object BookmarkPrivateFeedFilter : FeedFilter<Note>() {
val bookmarks = account.userProfile().latestBookmarkList
if (account.loginWithAmber) {
if (content.isBlank()) {
isActivityRunning = true
openAmber(
bookmarks?.content ?: "",
SignerType.NIP04_DECRYPT,
IntentUtils.decryptActivityResultLauncher,
account.keyPair.pubKey.toHexKey()
)
while (isActivityRunning) {
Thread.sleep(250)
}
if (AmberUtils.content.isBlank()) {
AmberUtils.decryptBookmark(bookmarks?.content ?: "", account.keyPair.pubKey.toHexKey())
bookmarks?.decryptedContent = AmberUtils.content
}
val notes = bookmarks?.privateTaggedEvents(content)
val notes = bookmarks?.privateTaggedEvents(bookmarks.decryptedContent)
?.mapNotNull { LocalCache.checkGetOrCreateNote(it) } ?: emptyList()
val addresses = bookmarks?.privateTaggedAddresses(content)
val addresses = bookmarks?.privateTaggedAddresses(bookmarks.decryptedContent)
?.map { LocalCache.getOrCreateAddressableNote(it) } ?: emptyList()
return notes.plus(addresses).toSet()

View File

@ -41,13 +41,19 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.AmberUtils
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.Event
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -390,6 +396,25 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
val scope = rememberCoroutineScope()
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
onDismiss()
}
},
data = event!!.toJson()
)
}
if (!state.isFollowingAuthor) {
DropdownMenuItem(onClick = {
accountViewModel.follow(
@ -447,11 +472,47 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
}
Divider()
if (state.isPrivateBookmarkNote) {
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.removePrivateBookmark(note); onDismiss() } }) {
DropdownMenuItem(
onClick = {
scope.launch(Dispatchers.IO) {
if (accountViewModel.loggedInWithAmber()) {
val bookmarks = accountViewModel.userProfile().latestBookmarkList
AmberUtils.decryptBookmark(
bookmarks?.content ?: "",
accountViewModel.account.keyPair.pubKey.toHexKey()
)
bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = ""
event = accountViewModel.removePrivateBookmark(note, bookmarks?.decryptedContent ?: "")
} else {
accountViewModel.removePrivateBookmark(note)
onDismiss()
}
}
}
) {
Text(stringResource(R.string.remove_from_private_bookmarks))
}
} else {
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.addPrivateBookmark(note); onDismiss() } }) {
DropdownMenuItem(
onClick = {
scope.launch(Dispatchers.IO) {
if (accountViewModel.loggedInWithAmber()) {
val bookmarks = accountViewModel.userProfile().latestBookmarkList
AmberUtils.decryptBookmark(
bookmarks?.content ?: "",
accountViewModel.account.keyPair.pubKey.toHexKey()
)
bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = ""
event = accountViewModel.addPrivateBookmark(note, bookmarks?.decryptedContent ?: "")
} else {
accountViewModel.addPrivateBookmark(note)
onDismiss()
}
}
}
) {
Text(stringResource(R.string.add_to_private_bookmarks))
}
}

View File

@ -21,6 +21,7 @@ import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.UserState
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.BookmarkListEvent
import com.vitorpamplona.quartz.events.DeletionEvent
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.GiftWrapEvent
@ -231,10 +232,18 @@ class AccountViewModel(val account: Account) : ViewModel() {
account.addPrivateBookmark(note)
}
fun addPrivateBookmark(note: Note, decryptedContent: String): BookmarkListEvent? {
return account.addPrivateBookmark(note, decryptedContent)
}
fun addPublicBookmark(note: Note) {
account.addPublicBookmark(note)
}
fun removePrivateBookmark(note: Note, decryptedContent: String): BookmarkListEvent? {
return account.removePrivateBookmark(note, decryptedContent)
}
fun removePrivateBookmark(note: Note) {
account.removePrivateBookmark(note)
}

View File

@ -21,6 +21,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.AmberUtils
import com.vitorpamplona.amethyst.ui.dal.BookmarkPrivateFeedFilter
import com.vitorpamplona.amethyst.ui.dal.BookmarkPublicFeedFilter
import com.vitorpamplona.amethyst.ui.screen.NostrBookmarkPrivateFeedViewModel
@ -95,7 +96,7 @@ fun BookmarkListScreen(accountViewModel: AccountViewModel, nav: (String) -> Unit
DisposableEffect(Unit) {
onDispose {
BookmarkPrivateFeedFilter.content = ""
AmberUtils.content = ""
}
}
}

View File

@ -16,9 +16,37 @@ class BookmarkListEvent(
content: String,
sig: HexKey
) : GeneralListEvent(id, pubKey, createdAt, kind, tags, content, sig) {
var decryptedContent = ""
companion object {
const val kind = 30001
fun create(
name: String = "",
events: List<String>? = null,
users: List<String>? = null,
addresses: List<ATag>? = null,
content: String,
pubKey: HexKey,
createdAt: Long = TimeUtils.now()
): BookmarkListEvent {
val tags = mutableListOf<List<String>>()
tags.add(listOf("d", name))
events?.forEach {
tags.add(listOf("e", it))
}
users?.forEach {
tags.add(listOf("p", it))
}
addresses?.forEach {
tags.add(listOf("a", it.toTag()))
}
val id = generateId(pubKey, createdAt, kind, tags, content)
return BookmarkListEvent(id.toHexKey(), pubKey, createdAt, tags, content, "")
}
fun create(
name: String = "",

View File

@ -71,9 +71,11 @@ abstract class GeneralListEvent(
}
fun privateTaggedUsers(privKey: ByteArray) = privateTags(privKey)?.filter { it.size > 1 && it[0] == "p" }?.map { it[1] }
fun privateTaggedUsers(content: String) = privateTags(content)?.filter { it.size > 1 && it[0] == "p" }?.map { it[1] }
fun privateHashtags(privKey: ByteArray) = privateTags(privKey)?.filter { it.size > 1 && it[0] == "t" }?.map { it[1] }
fun privateHashtags(content: String) = privateTags(content)?.filter { it.size > 1 && it[0] == "t" }?.map { it[1] }
fun privateGeohashes(privKey: ByteArray) = privateTags(privKey)?.filter { it.size > 1 && it[0] == "g" }?.map { it[1] }
fun privateGeohashes(content: String) = privateTags(content)?.filter { it.size > 1 && it[0] == "g" }?.map { it[1] }
fun privateTaggedEvents(privKey: ByteArray) = privateTags(privKey)?.filter { it.size > 1 && it[0] == "e" }?.map { it[1] }
fun privateTaggedEvents(content: String) = privateTags(content)?.filter { it.size > 1 && it[0] == "e" }?.map { it[1] }