Moves Ots to its own state class
This commit is contained in:
@@ -36,6 +36,7 @@ import com.vitorpamplona.amethyst.model.nip01UserMetadata.UserMetadataState
|
|||||||
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListOutboxRelays
|
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListOutboxRelays
|
||||||
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState
|
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListState
|
||||||
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowsPerOutboxRelay
|
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowsPerOutboxRelay
|
||||||
|
import com.vitorpamplona.amethyst.model.nip03Timestamp.OtsState
|
||||||
import com.vitorpamplona.amethyst.model.nip17Dms.DmInboxRelayState
|
import com.vitorpamplona.amethyst.model.nip17Dms.DmInboxRelayState
|
||||||
import com.vitorpamplona.amethyst.model.nip17Dms.DmRelayListState
|
import com.vitorpamplona.amethyst.model.nip17Dms.DmRelayListState
|
||||||
import com.vitorpamplona.amethyst.model.nip18Reposts.RepostAction
|
import com.vitorpamplona.amethyst.model.nip18Reposts.RepostAction
|
||||||
@@ -127,10 +128,8 @@ import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohashes
|
|||||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
|
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.people.hasAnyTaggedUser
|
import com.vitorpamplona.quartz.nip01Core.tags.people.hasAnyTaggedUser
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUserIds
|
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUserIds
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.references.references
|
import com.vitorpamplona.quartz.nip01Core.tags.references.references
|
||||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
|
||||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDMCache
|
import com.vitorpamplona.quartz.nip04Dm.PrivateDMCache
|
||||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||||
@@ -207,10 +206,8 @@ import kotlinx.coroutines.flow.SharingStarted
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.util.Base64
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
@@ -305,6 +302,15 @@ class Account(
|
|||||||
|
|
||||||
val chatroomList = LocalCache.getOrCreateChatroomList(signer.pubKey)
|
val chatroomList = LocalCache.getOrCreateChatroomList(signer.pubKey)
|
||||||
|
|
||||||
|
val otsResolver: OtsResolver =
|
||||||
|
OtsResolverBuilder().build(
|
||||||
|
Amethyst.instance.okHttpClients,
|
||||||
|
::shouldUseTorForMoneyOperations,
|
||||||
|
Amethyst.instance.otsBlockHeightCache,
|
||||||
|
)
|
||||||
|
|
||||||
|
val otsState = OtsState(signer, cache, otsResolver, scope, settings)
|
||||||
|
|
||||||
val feedDecryptionCaches =
|
val feedDecryptionCaches =
|
||||||
FeedDecryptionCaches(
|
FeedDecryptionCaches(
|
||||||
peopleListCache = peopleListDecryptionCache,
|
peopleListCache = peopleListDecryptionCache,
|
||||||
@@ -457,10 +463,6 @@ class Account(
|
|||||||
|
|
||||||
private suspend fun sendNewAppSpecificData() = sendMyPublicAndPrivateOutbox(appSpecific.saveNewAppSpecificData())
|
private suspend fun sendNewAppSpecificData() = sendMyPublicAndPrivateOutbox(appSpecific.saveNewAppSpecificData())
|
||||||
|
|
||||||
suspend fun countFollowersOf(pubkey: HexKey): Int = cache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkey) ?: false }
|
|
||||||
|
|
||||||
suspend fun followerCount(): Int = countFollowersOf(signer.pubKey)
|
|
||||||
|
|
||||||
suspend fun sendNewUserMetadata(
|
suspend fun sendNewUserMetadata(
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
picture: String? = null,
|
picture: String? = null,
|
||||||
@@ -493,7 +495,7 @@ class Account(
|
|||||||
github,
|
github,
|
||||||
)
|
)
|
||||||
|
|
||||||
sendEverywhere(userMetadataEvent)
|
sendLiterallyEverywhere(userMetadataEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reactionTo(
|
fun reactionTo(
|
||||||
@@ -877,47 +879,7 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateAttestations() {
|
suspend fun updateAttestations() = sendAutomatic(otsState.updateAttestations())
|
||||||
Log.d("Pending Attestations", "Updating ${settings.pendingAttestations.value.size} pending attestations")
|
|
||||||
|
|
||||||
val otsResolver = otsResolver()
|
|
||||||
|
|
||||||
settings.pendingAttestations.value.toList().forEach { (key, value) ->
|
|
||||||
val otsState = OtsEvent.upgrade(Base64.getDecoder().decode(value), key, otsResolver)
|
|
||||||
|
|
||||||
if (otsState != null) {
|
|
||||||
val hint = cache.getNoteIfExists(key)?.toEventHint<Event>()
|
|
||||||
val template =
|
|
||||||
if (hint != null) {
|
|
||||||
OtsEvent.build(hint, otsState)
|
|
||||||
} else {
|
|
||||||
OtsEvent.build(key, otsState)
|
|
||||||
}
|
|
||||||
|
|
||||||
val otsEvent = signer.sign(template)
|
|
||||||
|
|
||||||
cache.justConsumeMyOwnEvent(otsEvent)
|
|
||||||
client.send(otsEvent, computeRelayListToBroadcast(otsEvent))
|
|
||||||
|
|
||||||
settings.pendingAttestations.update { it - key }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hasPendingAttestations(note: Note): Boolean {
|
|
||||||
val id = note.event?.id ?: note.idHex
|
|
||||||
return settings.pendingAttestations.value[id] != null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun timestamp(note: Note) {
|
|
||||||
if (!isWriteable()) return
|
|
||||||
if (note.isDraft()) return
|
|
||||||
|
|
||||||
val id = note.event?.id ?: note.idHex
|
|
||||||
val otsResolver = otsResolver()
|
|
||||||
|
|
||||||
settings.addPendingAttestation(id, Base64.getEncoder().encodeToString(OtsEvent.stamp(id, otsResolver)))
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun follow(user: User) = sendMyPublicAndPrivateOutbox(kind3FollowList.follow(user))
|
suspend fun follow(user: User) = sendMyPublicAndPrivateOutbox(kind3FollowList.follow(user))
|
||||||
|
|
||||||
@@ -943,6 +905,14 @@ class Account(
|
|||||||
|
|
||||||
suspend fun unfollowGeohash(geohash: String) = sendMyPublicAndPrivateOutbox(geohashList.unfollow(geohash))
|
suspend fun unfollowGeohash(geohash: String) = sendMyPublicAndPrivateOutbox(geohashList.unfollow(geohash))
|
||||||
|
|
||||||
|
fun sendAutomatic(events: List<Event>) = events.forEach { sendAutomatic(it) }
|
||||||
|
|
||||||
|
fun sendAutomatic(event: Event?) {
|
||||||
|
if (event == null) return
|
||||||
|
cache.justConsumeMyOwnEvent(event)
|
||||||
|
client.send(event, computeRelayListToBroadcast(event))
|
||||||
|
}
|
||||||
|
|
||||||
fun sendMyPublicAndPrivateOutbox(event: Event?) {
|
fun sendMyPublicAndPrivateOutbox(event: Event?) {
|
||||||
if (event == null) return
|
if (event == null) return
|
||||||
client.send(event, outboxRelays.flow.value)
|
client.send(event, outboxRelays.flow.value)
|
||||||
@@ -956,7 +926,7 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendEverywhere(event: Event) {
|
fun sendLiterallyEverywhere(event: Event) {
|
||||||
client.send(event, outboxRelays.flow.value + client.relayStatusFlow().value.available)
|
client.send(event, outboxRelays.flow.value + client.relayStatusFlow().value.available)
|
||||||
cache.justConsumeMyOwnEvent(event)
|
cache.justConsumeMyOwnEvent(event)
|
||||||
}
|
}
|
||||||
@@ -1620,16 +1590,11 @@ class Account(
|
|||||||
delete(note)
|
delete(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isInPrivateBookmarks(note: Note) = bookmarkState.isInPrivateBookmarks(note)
|
|
||||||
|
|
||||||
fun isInPublicBookmarks(note: Note) = bookmarkState.isInPublicBookmarks(note)
|
|
||||||
|
|
||||||
suspend fun addBookmark(
|
suspend fun addBookmark(
|
||||||
note: Note,
|
note: Note,
|
||||||
isPrivate: Boolean,
|
isPrivate: Boolean,
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable() || note.isDraft()) return
|
||||||
if (note.isDraft()) return
|
|
||||||
|
|
||||||
sendMyPublicAndPrivateOutbox(bookmarkState.addBookmark(note, isPrivate))
|
sendMyPublicAndPrivateOutbox(bookmarkState.addBookmark(note, isPrivate))
|
||||||
}
|
}
|
||||||
@@ -1638,8 +1603,7 @@ class Account(
|
|||||||
note: Note,
|
note: Note,
|
||||||
isPrivate: Boolean,
|
isPrivate: Boolean,
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable() || note.isDraft()) return
|
||||||
if (note.isDraft()) return
|
|
||||||
|
|
||||||
val event = bookmarkState.removeBookmark(note, isPrivate)
|
val event = bookmarkState.removeBookmark(note, isPrivate)
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
@@ -1735,16 +1699,7 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun decryptZapOrNull(event: LnZapRequestEvent): LnZapPrivateEvent? =
|
suspend fun decryptZapOrNull(event: LnZapRequestEvent): LnZapPrivateEvent? = if (event.isPrivateZap() && isWriteable()) privateZapsDecryptionCache.decryptPrivateZap(event) else null
|
||||||
if (event.isPrivateZap()) {
|
|
||||||
if (isWriteable()) {
|
|
||||||
privateZapsDecryptionCache.decryptPrivateZap(event)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isAllHidden(users: Set<HexKey>): Boolean = users.all { isHidden(it) }
|
fun isAllHidden(users: Set<HexKey>): Boolean = users.all { isHidden(it) }
|
||||||
|
|
||||||
@@ -1816,7 +1771,7 @@ class Account(
|
|||||||
).toSet()
|
).toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun saveDMRelayList(dmRelays: List<NormalizedRelayUrl>) = sendEverywhere(dmRelayList.saveRelayList(dmRelays))
|
suspend fun saveDMRelayList(dmRelays: List<NormalizedRelayUrl>) = sendLiterallyEverywhere(dmRelayList.saveRelayList(dmRelays))
|
||||||
|
|
||||||
suspend fun savePrivateOutboxRelayList(relays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(privateStorageRelayList.saveRelayList(relays))
|
suspend fun savePrivateOutboxRelayList(relays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(privateStorageRelayList.saveRelayList(relays))
|
||||||
|
|
||||||
@@ -1826,7 +1781,7 @@ class Account(
|
|||||||
|
|
||||||
suspend fun saveBlockedRelayList(blockedRelays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(blockedRelayList.saveRelayList(blockedRelays))
|
suspend fun saveBlockedRelayList(blockedRelays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(blockedRelayList.saveRelayList(blockedRelays))
|
||||||
|
|
||||||
suspend fun sendNip65RelayList(relays: List<AdvertisedRelayInfo>) = sendEverywhere(nip65RelayList.saveRelayList(relays))
|
suspend fun sendNip65RelayList(relays: List<AdvertisedRelayInfo>) = sendLiterallyEverywhere(nip65RelayList.saveRelayList(relays))
|
||||||
|
|
||||||
suspend fun sendFileServersList(servers: List<String>) = sendMyPublicAndPrivateOutbox(fileStorageServers.saveFileServersList(servers))
|
suspend fun sendFileServersList(servers: List<String>) = sendMyPublicAndPrivateOutbox(fileStorageServers.saveFileServersList(servers))
|
||||||
|
|
||||||
@@ -1965,13 +1920,6 @@ class Account(
|
|||||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip96UploadsViaTor.value)
|
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip96UploadsViaTor.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun otsResolver(): OtsResolver =
|
|
||||||
OtsResolverBuilder().build(
|
|
||||||
Amethyst.instance.okHttpClients,
|
|
||||||
::shouldUseTorForMoneyOperations,
|
|
||||||
Amethyst.instance.otsBlockHeightCache,
|
|
||||||
)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Log.d("AccountRegisterObservers", "Init")
|
Log.d("AccountRegisterObservers", "Init")
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
package com.vitorpamplona.amethyst.model.nip03Timestamp
|
package com.vitorpamplona.amethyst.model.nip03Timestamp
|
||||||
|
|
||||||
class OtsState {
|
import android.util.Log
|
||||||
}
|
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||||
|
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||||
|
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import java.util.Base64
|
||||||
|
|
||||||
|
class OtsState(
|
||||||
|
val signer: NostrSigner,
|
||||||
|
val cache: LocalCache,
|
||||||
|
val otsResolver: OtsResolver,
|
||||||
|
val scope: CoroutineScope,
|
||||||
|
val settings: AccountSettings,
|
||||||
|
) {
|
||||||
|
suspend fun updateAttestations(): List<OtsEvent> {
|
||||||
|
Log.d("Pending Attestations", "Updating ${settings.pendingAttestations.value.size} pending attestations")
|
||||||
|
|
||||||
|
return settings.pendingAttestations.value.toList().mapNotNull { (key, value) ->
|
||||||
|
val otsState = OtsEvent.upgrade(Base64.getDecoder().decode(value), key, otsResolver)
|
||||||
|
|
||||||
|
if (otsState != null) {
|
||||||
|
val hint = cache.getNoteIfExists(key)?.toEventHint<Event>()
|
||||||
|
val template =
|
||||||
|
if (hint != null) {
|
||||||
|
OtsEvent.build(hint, otsState)
|
||||||
|
} else {
|
||||||
|
OtsEvent.build(key, otsState)
|
||||||
|
}
|
||||||
|
|
||||||
|
val otsEvent = signer.sign(template)
|
||||||
|
|
||||||
|
settings.pendingAttestations.update { it - key }
|
||||||
|
|
||||||
|
otsEvent
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasPendingAttestations(note: Note): Boolean {
|
||||||
|
val id = note.event?.id ?: note.idHex
|
||||||
|
return settings.pendingAttestations.value[id] != null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun timestamp(note: Note) {
|
||||||
|
if (note.isDraft()) return
|
||||||
|
|
||||||
|
val id = note.event?.id ?: note.idHex
|
||||||
|
|
||||||
|
settings.addPendingAttestation(id, Base64.getEncoder().encodeToString(OtsEvent.stamp(id, otsResolver)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ fun NoteDropDownMenu(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
HorizontalDivider(thickness = DividerThickness)
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
if (accountViewModel.account.hasPendingAttestations(note)) {
|
if (accountViewModel.account.otsState.hasPendingAttestations(note)) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringRes(R.string.timestamp_pending)) },
|
text = { Text(stringRes(R.string.timestamp_pending)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -363,8 +363,8 @@ fun WatchBookmarksFollowsAndAccount(
|
|||||||
val newState =
|
val newState =
|
||||||
DropDownParams(
|
DropDownParams(
|
||||||
isFollowingAuthor = accountViewModel.isFollowing(note.author),
|
isFollowingAuthor = accountViewModel.isFollowing(note.author),
|
||||||
isPrivateBookmarkNote = accountViewModel.isInPrivateBookmarks(note),
|
isPrivateBookmarkNote = accountViewModel.account.bookmarkState.isInPrivateBookmarks(note),
|
||||||
isPublicBookmarkNote = accountViewModel.isInPublicBookmarks(note),
|
isPublicBookmarkNote = accountViewModel.account.bookmarkState.isInPublicBookmarks(note),
|
||||||
isLoggedUser = accountViewModel.isLoggedUser(note.author),
|
isLoggedUser = accountViewModel.isLoggedUser(note.author),
|
||||||
isSensitive = note.event?.isSensitiveOrNSFW() ?: false,
|
isSensitive = note.event?.isSensitiveOrNSFW() ?: false,
|
||||||
showSensitiveContent = showSensitiveContent,
|
showSensitiveContent = showSensitiveContent,
|
||||||
|
|||||||
+6
-22
@@ -741,33 +741,17 @@ class AccountViewModel(
|
|||||||
|
|
||||||
fun bookmarks(user: User): Note = LocalCache.getOrCreateAddressableNote(BookmarkListEvent.createBookmarkAddress(user.pubkeyHex))
|
fun bookmarks(user: User): Note = LocalCache.getOrCreateAddressableNote(BookmarkListEvent.createBookmarkAddress(user.pubkeyHex))
|
||||||
|
|
||||||
fun addPrivateBookmark(note: Note) {
|
fun addPrivateBookmark(note: Note) = runIOCatching { account.addBookmark(note, true) }
|
||||||
runIOCatching { account.addBookmark(note, true) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addPublicBookmark(note: Note) {
|
fun addPublicBookmark(note: Note) = runIOCatching { account.addBookmark(note, false) }
|
||||||
runIOCatching { account.addBookmark(note, false) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removePrivateBookmark(note: Note) {
|
fun removePrivateBookmark(note: Note) = runIOCatching { account.removeBookmark(note, true) }
|
||||||
runIOCatching { account.removeBookmark(note, true) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removePublicBookmark(note: Note) {
|
fun removePublicBookmark(note: Note) = runIOCatching { account.removeBookmark(note, false) }
|
||||||
runIOCatching { account.removeBookmark(note, false) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isInPrivateBookmarks(note: Note): Boolean = account.isInPrivateBookmarks(note)
|
fun broadcast(note: Note) = runIOCatching { account.broadcast(note) }
|
||||||
|
|
||||||
fun isInPublicBookmarks(note: Note): Boolean = account.isInPublicBookmarks(note)
|
fun timestamp(note: Note) = runIOCatching { account.otsState.timestamp(note) }
|
||||||
|
|
||||||
fun broadcast(note: Note) {
|
|
||||||
runIOCatching { account.broadcast(note) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun timestamp(note: Note) {
|
|
||||||
runIOCatching { account.timestamp(note) }
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastTimeItTriedToUpdateAttestations: Long = 0
|
var lastTimeItTriedToUpdateAttestations: Long = 0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user