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.FollowListState
|
||||
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.DmRelayListState
|
||||
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.people.PTag
|
||||
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.references.references
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDMCache
|
||||
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.flowOn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
import java.util.Base64
|
||||
import java.util.Locale
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@@ -305,6 +302,15 @@ class Account(
|
||||
|
||||
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 =
|
||||
FeedDecryptionCaches(
|
||||
peopleListCache = peopleListDecryptionCache,
|
||||
@@ -457,10 +463,6 @@ class Account(
|
||||
|
||||
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(
|
||||
name: String? = null,
|
||||
picture: String? = null,
|
||||
@@ -493,7 +495,7 @@ class Account(
|
||||
github,
|
||||
)
|
||||
|
||||
sendEverywhere(userMetadataEvent)
|
||||
sendLiterallyEverywhere(userMetadataEvent)
|
||||
}
|
||||
|
||||
fun reactionTo(
|
||||
@@ -877,47 +879,7 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun 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 updateAttestations() = sendAutomatic(otsState.updateAttestations())
|
||||
|
||||
suspend fun follow(user: User) = sendMyPublicAndPrivateOutbox(kind3FollowList.follow(user))
|
||||
|
||||
@@ -943,6 +905,14 @@ class Account(
|
||||
|
||||
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?) {
|
||||
if (event == null) return
|
||||
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)
|
||||
cache.justConsumeMyOwnEvent(event)
|
||||
}
|
||||
@@ -1620,16 +1590,11 @@ class Account(
|
||||
delete(note)
|
||||
}
|
||||
|
||||
fun isInPrivateBookmarks(note: Note) = bookmarkState.isInPrivateBookmarks(note)
|
||||
|
||||
fun isInPublicBookmarks(note: Note) = bookmarkState.isInPublicBookmarks(note)
|
||||
|
||||
suspend fun addBookmark(
|
||||
note: Note,
|
||||
isPrivate: Boolean,
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
if (note.isDraft()) return
|
||||
if (!isWriteable() || note.isDraft()) return
|
||||
|
||||
sendMyPublicAndPrivateOutbox(bookmarkState.addBookmark(note, isPrivate))
|
||||
}
|
||||
@@ -1638,8 +1603,7 @@ class Account(
|
||||
note: Note,
|
||||
isPrivate: Boolean,
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
if (note.isDraft()) return
|
||||
if (!isWriteable() || note.isDraft()) return
|
||||
|
||||
val event = bookmarkState.removeBookmark(note, isPrivate)
|
||||
if (event != null) {
|
||||
@@ -1735,16 +1699,7 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun decryptZapOrNull(event: LnZapRequestEvent): LnZapPrivateEvent? =
|
||||
if (event.isPrivateZap()) {
|
||||
if (isWriteable()) {
|
||||
privateZapsDecryptionCache.decryptPrivateZap(event)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
suspend fun decryptZapOrNull(event: LnZapRequestEvent): LnZapPrivateEvent? = if (event.isPrivateZap() && isWriteable()) privateZapsDecryptionCache.decryptPrivateZap(event) else null
|
||||
|
||||
fun isAllHidden(users: Set<HexKey>): Boolean = users.all { isHidden(it) }
|
||||
|
||||
@@ -1816,7 +1771,7 @@ class Account(
|
||||
).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))
|
||||
|
||||
@@ -1826,7 +1781,7 @@ class Account(
|
||||
|
||||
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))
|
||||
|
||||
@@ -1965,13 +1920,6 @@ class Account(
|
||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip96UploadsViaTor.value)
|
||||
}
|
||||
|
||||
fun otsResolver(): OtsResolver =
|
||||
OtsResolverBuilder().build(
|
||||
Amethyst.instance.okHttpClients,
|
||||
::shouldUseTorForMoneyOperations,
|
||||
Amethyst.instance.otsBlockHeightCache,
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
if (accountViewModel.account.hasPendingAttestations(note)) {
|
||||
if (accountViewModel.account.otsState.hasPendingAttestations(note)) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringRes(R.string.timestamp_pending)) },
|
||||
onClick = {
|
||||
@@ -363,8 +363,8 @@ fun WatchBookmarksFollowsAndAccount(
|
||||
val newState =
|
||||
DropDownParams(
|
||||
isFollowingAuthor = accountViewModel.isFollowing(note.author),
|
||||
isPrivateBookmarkNote = accountViewModel.isInPrivateBookmarks(note),
|
||||
isPublicBookmarkNote = accountViewModel.isInPublicBookmarks(note),
|
||||
isPrivateBookmarkNote = accountViewModel.account.bookmarkState.isInPrivateBookmarks(note),
|
||||
isPublicBookmarkNote = accountViewModel.account.bookmarkState.isInPublicBookmarks(note),
|
||||
isLoggedUser = accountViewModel.isLoggedUser(note.author),
|
||||
isSensitive = note.event?.isSensitiveOrNSFW() ?: false,
|
||||
showSensitiveContent = showSensitiveContent,
|
||||
|
||||
+6
-22
@@ -741,33 +741,17 @@ class AccountViewModel(
|
||||
|
||||
fun bookmarks(user: User): Note = LocalCache.getOrCreateAddressableNote(BookmarkListEvent.createBookmarkAddress(user.pubkeyHex))
|
||||
|
||||
fun addPrivateBookmark(note: Note) {
|
||||
runIOCatching { account.addBookmark(note, true) }
|
||||
}
|
||||
fun addPrivateBookmark(note: Note) = runIOCatching { account.addBookmark(note, true) }
|
||||
|
||||
fun addPublicBookmark(note: Note) {
|
||||
runIOCatching { account.addBookmark(note, false) }
|
||||
}
|
||||
fun addPublicBookmark(note: Note) = runIOCatching { account.addBookmark(note, false) }
|
||||
|
||||
fun removePrivateBookmark(note: Note) {
|
||||
runIOCatching { account.removeBookmark(note, true) }
|
||||
}
|
||||
fun removePrivateBookmark(note: Note) = runIOCatching { account.removeBookmark(note, true) }
|
||||
|
||||
fun removePublicBookmark(note: Note) {
|
||||
runIOCatching { account.removeBookmark(note, false) }
|
||||
}
|
||||
fun removePublicBookmark(note: Note) = 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 broadcast(note: Note) {
|
||||
runIOCatching { account.broadcast(note) }
|
||||
}
|
||||
|
||||
fun timestamp(note: Note) {
|
||||
runIOCatching { account.timestamp(note) }
|
||||
}
|
||||
fun timestamp(note: Note) = runIOCatching { account.otsState.timestamp(note) }
|
||||
|
||||
var lastTimeItTriedToUpdateAttestations: Long = 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user