Minimizes the dependency on the Account further by moving Tor settings to a tor Privacy State
This commit is contained in:
@@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.model.nip72Communities.CommunityListState
|
|||||||
import com.vitorpamplona.amethyst.model.nip78AppSpecific.AppSpecificState
|
import com.vitorpamplona.amethyst.model.nip78AppSpecific.AppSpecificState
|
||||||
import com.vitorpamplona.amethyst.model.nip96FileStorage.FileStorageServerListState
|
import com.vitorpamplona.amethyst.model.nip96FileStorage.FileStorageServerListState
|
||||||
import com.vitorpamplona.amethyst.model.nipB7Blossom.BlossomServerListState
|
import com.vitorpamplona.amethyst.model.nipB7Blossom.BlossomServerListState
|
||||||
|
import com.vitorpamplona.amethyst.model.privacyOptions.PrivacyState
|
||||||
import com.vitorpamplona.amethyst.model.serverList.MergedFollowListsState
|
import com.vitorpamplona.amethyst.model.serverList.MergedFollowListsState
|
||||||
import com.vitorpamplona.amethyst.model.serverList.MergedFollowPlusMineRelayListsState
|
import com.vitorpamplona.amethyst.model.serverList.MergedFollowPlusMineRelayListsState
|
||||||
import com.vitorpamplona.amethyst.model.serverList.MergedServerListState
|
import com.vitorpamplona.amethyst.model.serverList.MergedServerListState
|
||||||
@@ -79,9 +80,8 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
|
|||||||
import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxLoaderState
|
import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxLoaderState
|
||||||
import com.vitorpamplona.amethyst.model.torState.TorRelayState
|
import com.vitorpamplona.amethyst.model.torState.TorRelayState
|
||||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||||
import com.vitorpamplona.amethyst.service.ots.OtsResolverBuilder
|
import com.vitorpamplona.amethyst.service.ots.OkHttpOtsResolverBuilder
|
||||||
import com.vitorpamplona.amethyst.service.uploads.FileHeader
|
import com.vitorpamplona.amethyst.service.uploads.FileHeader
|
||||||
import com.vitorpamplona.amethyst.ui.tor.TorType
|
|
||||||
import com.vitorpamplona.quartz.experimental.bounties.BountyAddValueEvent
|
import com.vitorpamplona.quartz.experimental.bounties.BountyAddValueEvent
|
||||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent
|
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent
|
||||||
@@ -118,7 +118,6 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.downloadFirst
|
|||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.taggedAddresses
|
import com.vitorpamplona.quartz.nip01Core.tags.addressables.taggedAddresses
|
||||||
@@ -130,7 +129,6 @@ 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.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.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
|
||||||
import com.vitorpamplona.quartz.nip04Dm.messages.reply
|
import com.vitorpamplona.quartz.nip04Dm.messages.reply
|
||||||
@@ -302,14 +300,17 @@ class Account(
|
|||||||
|
|
||||||
val chatroomList = LocalCache.getOrCreateChatroomList(signer.pubKey)
|
val chatroomList = LocalCache.getOrCreateChatroomList(signer.pubKey)
|
||||||
|
|
||||||
val otsResolver: OtsResolver =
|
val privacyState = PrivacyState(settings)
|
||||||
OtsResolverBuilder().build(
|
val torRelayState = TorRelayState(trustedRelays, dmRelayList, settings, scope)
|
||||||
|
|
||||||
|
val otsResolverBuilder: OkHttpOtsResolverBuilder =
|
||||||
|
OkHttpOtsResolverBuilder(
|
||||||
Amethyst.instance.okHttpClients,
|
Amethyst.instance.okHttpClients,
|
||||||
::shouldUseTorForMoneyOperations,
|
privacyState::shouldUseTorForMoneyOperations,
|
||||||
Amethyst.instance.otsBlockHeightCache,
|
Amethyst.instance.otsBlockHeightCache,
|
||||||
)
|
)
|
||||||
|
|
||||||
val otsState = OtsState(signer, cache, otsResolver, scope, settings)
|
val otsState = OtsState(signer, cache, otsResolverBuilder, scope, settings)
|
||||||
|
|
||||||
val feedDecryptionCaches =
|
val feedDecryptionCaches =
|
||||||
FeedDecryptionCaches(
|
FeedDecryptionCaches(
|
||||||
@@ -389,8 +390,6 @@ class Account(
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
val torRelayState = TorRelayState(trustedRelays, dmRelayList, settings, scope)
|
|
||||||
|
|
||||||
fun isWriteable(): Boolean = settings.isWriteable()
|
fun isWriteable(): Boolean = settings.isWriteable()
|
||||||
|
|
||||||
suspend fun updateWarnReports(warnReports: Boolean): Boolean {
|
suspend fun updateWarnReports(warnReports: Boolean): Boolean {
|
||||||
@@ -463,67 +462,15 @@ class Account(
|
|||||||
|
|
||||||
private suspend fun sendNewAppSpecificData() = sendMyPublicAndPrivateOutbox(appSpecific.saveNewAppSpecificData())
|
private suspend fun sendNewAppSpecificData() = sendMyPublicAndPrivateOutbox(appSpecific.saveNewAppSpecificData())
|
||||||
|
|
||||||
suspend fun sendNewUserMetadata(
|
|
||||||
name: String? = null,
|
|
||||||
picture: String? = null,
|
|
||||||
banner: String? = null,
|
|
||||||
website: String? = null,
|
|
||||||
pronouns: String? = null,
|
|
||||||
about: String? = null,
|
|
||||||
nip05: String? = null,
|
|
||||||
lnAddress: String? = null,
|
|
||||||
lnURL: String? = null,
|
|
||||||
twitter: String? = null,
|
|
||||||
mastodon: String? = null,
|
|
||||||
github: String? = null,
|
|
||||||
) {
|
|
||||||
if (!isWriteable()) return
|
|
||||||
|
|
||||||
val userMetadataEvent =
|
|
||||||
userMetadata.sendNewUserMetadata(
|
|
||||||
name,
|
|
||||||
picture,
|
|
||||||
banner,
|
|
||||||
website,
|
|
||||||
pronouns,
|
|
||||||
about,
|
|
||||||
nip05,
|
|
||||||
lnAddress,
|
|
||||||
lnURL,
|
|
||||||
twitter,
|
|
||||||
mastodon,
|
|
||||||
github,
|
|
||||||
)
|
|
||||||
|
|
||||||
sendLiterallyEverywhere(userMetadataEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun reactionTo(
|
|
||||||
note: Note,
|
|
||||||
reaction: String,
|
|
||||||
): List<Note> = note.reactedBy(userProfile(), reaction)
|
|
||||||
|
|
||||||
fun hasBoosted(note: Note): Boolean = boostsTo(note).isNotEmpty()
|
|
||||||
|
|
||||||
fun boostsTo(note: Note): List<Note> = note.boostedBy(userProfile())
|
|
||||||
|
|
||||||
fun hasReacted(
|
|
||||||
note: Note,
|
|
||||||
reaction: String,
|
|
||||||
): Boolean = note.hasReacted(userProfile(), reaction)
|
|
||||||
|
|
||||||
suspend fun reactTo(
|
suspend fun reactTo(
|
||||||
note: Note,
|
note: Note,
|
||||||
reaction: String,
|
reaction: String,
|
||||||
) = ReactionAction.reactTo(
|
) = ReactionAction.reactTo(
|
||||||
note,
|
note = note,
|
||||||
reaction,
|
reaction = reaction,
|
||||||
userProfile(),
|
by = userProfile(),
|
||||||
signer,
|
signer = signer,
|
||||||
onPublic = {
|
onPublic = ::sendAutomatic,
|
||||||
client.send(it, computeMyReactionToNote(note, it))
|
|
||||||
cache.justConsumeMyOwnEvent(it)
|
|
||||||
},
|
|
||||||
onPrivate = ::broadcastPrivately,
|
onPrivate = ::broadcastPrivately,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -534,18 +481,15 @@ class Account(
|
|||||||
zapType: LnZapEvent.ZapType,
|
zapType: LnZapEvent.ZapType,
|
||||||
toUser: User?,
|
toUser: User?,
|
||||||
additionalRelays: Set<NormalizedRelayUrl>? = null,
|
additionalRelays: Set<NormalizedRelayUrl>? = null,
|
||||||
): LnZapRequestEvent {
|
) = LnZapRequestEvent.create(
|
||||||
val relays = nip65RelayList.inboxFlow.value + (additionalRelays ?: emptySet())
|
zappedEvent = event,
|
||||||
return LnZapRequestEvent.create(
|
relays = nip65RelayList.inboxFlow.value + (additionalRelays ?: emptySet()),
|
||||||
event,
|
signer = signer,
|
||||||
relays = relays.mapTo(mutableSetOf()) { it.url },
|
pollOption = pollOption,
|
||||||
signer,
|
message = message,
|
||||||
pollOption,
|
zapType = zapType,
|
||||||
message,
|
toUserPubHex = toUser?.pubkeyHex,
|
||||||
zapType,
|
)
|
||||||
toUser?.pubkeyHex,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun calculateIfNoteWasZappedByAccount(
|
suspend fun calculateIfNoteWasZappedByAccount(
|
||||||
zappedNote: Note?,
|
zappedNote: Note?,
|
||||||
@@ -570,12 +514,10 @@ class Account(
|
|||||||
message: String = "",
|
message: String = "",
|
||||||
zapType: LnZapEvent.ZapType,
|
zapType: LnZapEvent.ZapType,
|
||||||
): LnZapRequestEvent {
|
): LnZapRequestEvent {
|
||||||
val relays = nip65RelayList.inboxFlow.value + user.inboxRelays()
|
|
||||||
|
|
||||||
val zapRequest =
|
val zapRequest =
|
||||||
LnZapRequestEvent.create(
|
LnZapRequestEvent.create(
|
||||||
userHex = user.pubkeyHex,
|
userHex = user.pubkeyHex,
|
||||||
relays = relays,
|
relays = nip65RelayList.inboxFlow.value + user.inboxRelays(),
|
||||||
signer = signer,
|
signer = signer,
|
||||||
message = message,
|
message = message,
|
||||||
zapType = zapType,
|
zapType = zapType,
|
||||||
@@ -596,9 +538,7 @@ class Account(
|
|||||||
type: ReportType,
|
type: ReportType,
|
||||||
) = sendMyPublicAndPrivateOutbox(ReportAction.report(user, type, userProfile(), signer))
|
) = sendMyPublicAndPrivateOutbox(ReportAction.report(user, type, userProfile(), signer))
|
||||||
|
|
||||||
suspend fun delete(note: Note) {
|
suspend fun delete(note: Note) = delete(listOf(note))
|
||||||
delete(listOf(note))
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun delete(notes: List<Note>) {
|
suspend fun delete(notes: List<Note>) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
@@ -627,10 +567,7 @@ class Account(
|
|||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
if (event.pubKey != signer.pubKey) return
|
if (event.pubKey != signer.pubKey) return
|
||||||
|
|
||||||
val deletionEvent =
|
val deletionEvent = signer.sign(DeletionEvent.build(listOf(event)))
|
||||||
signer.sign(
|
|
||||||
DeletionEvent.build(listOf(event)),
|
|
||||||
)
|
|
||||||
client.send(deletionEvent, outboxRelays.flow.value + additionalRelays)
|
client.send(deletionEvent, outboxRelays.flow.value + additionalRelays)
|
||||||
cache.justConsumeMyOwnEvent(deletionEvent)
|
cache.justConsumeMyOwnEvent(deletionEvent)
|
||||||
}
|
}
|
||||||
@@ -1822,104 +1759,6 @@ class Account(
|
|||||||
|
|
||||||
fun markDonatedInThisVersion() = settings.markDonatedInThisVersion(BuildConfig.VERSION_NAME)
|
fun markDonatedInThisVersion() = settings.markDonatedInThisVersion(BuildConfig.VERSION_NAME)
|
||||||
|
|
||||||
fun shouldUseTorForImageDownload(url: String) =
|
|
||||||
shouldUseTorFor(
|
|
||||||
url,
|
|
||||||
settings.torSettings.torType.value,
|
|
||||||
settings.torSettings.imagesViaTor.value,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun shouldUseTorFor(
|
|
||||||
url: String,
|
|
||||||
torType: TorType,
|
|
||||||
imagesViaTor: Boolean,
|
|
||||||
) = when (torType) {
|
|
||||||
TorType.OFF -> false
|
|
||||||
TorType.INTERNAL -> shouldUseTor(url, imagesViaTor)
|
|
||||||
TorType.EXTERNAL -> shouldUseTor(url, imagesViaTor)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shouldUseTor(
|
|
||||||
normalizedUrl: String,
|
|
||||||
final: Boolean,
|
|
||||||
): Boolean =
|
|
||||||
if (RelayUrlNormalizer.isLocalHost(normalizedUrl)) {
|
|
||||||
false
|
|
||||||
} else if (RelayUrlNormalizer.isOnion(normalizedUrl)) {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
final
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldUseTorForVideoDownload() =
|
|
||||||
when (settings.torSettings.torType.value) {
|
|
||||||
TorType.OFF -> false
|
|
||||||
TorType.INTERNAL -> settings.torSettings.videosViaTor.value
|
|
||||||
TorType.EXTERNAL -> settings.torSettings.videosViaTor.value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldUseTorForVideoDownload(url: String) =
|
|
||||||
when (settings.torSettings.torType.value) {
|
|
||||||
TorType.OFF -> false
|
|
||||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.videosViaTor.value)
|
|
||||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.videosViaTor.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldUseTorForPreviewUrl(url: String) =
|
|
||||||
when (settings.torSettings.torType.value) {
|
|
||||||
TorType.OFF -> false
|
|
||||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.urlPreviewsViaTor.value)
|
|
||||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.urlPreviewsViaTor.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldUseTorForTrustedRelays() =
|
|
||||||
when (settings.torSettings.torType.value) {
|
|
||||||
TorType.OFF -> false
|
|
||||||
TorType.INTERNAL -> settings.torSettings.trustedRelaysViaTor.value
|
|
||||||
TorType.EXTERNAL -> settings.torSettings.trustedRelaysViaTor.value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldUseTorForClean(relay: NormalizedRelayUrl) = torRelayState.flow.value.useTor(relay)
|
|
||||||
|
|
||||||
private fun checkLocalHostOnionAndThen(
|
|
||||||
url: String,
|
|
||||||
final: Boolean,
|
|
||||||
): Boolean = checkLocalHostOnionAndThen(url, settings.torSettings.onionRelaysViaTor.value, final)
|
|
||||||
|
|
||||||
private fun checkLocalHostOnionAndThen(
|
|
||||||
normalizedUrl: String,
|
|
||||||
isOnionRelaysActive: Boolean,
|
|
||||||
final: Boolean,
|
|
||||||
): Boolean =
|
|
||||||
if (RelayUrlNormalizer.isLocalHost(normalizedUrl)) {
|
|
||||||
false
|
|
||||||
} else if (RelayUrlNormalizer.isOnion(normalizedUrl)) {
|
|
||||||
isOnionRelaysActive
|
|
||||||
} else {
|
|
||||||
final
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldUseTorForMoneyOperations(url: String) =
|
|
||||||
when (settings.torSettings.torType.value) {
|
|
||||||
TorType.OFF -> false
|
|
||||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.moneyOperationsViaTor.value)
|
|
||||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.moneyOperationsViaTor.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldUseTorForNIP05(url: String) =
|
|
||||||
when (settings.torSettings.torType.value) {
|
|
||||||
TorType.OFF -> false
|
|
||||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip05VerificationsViaTor.value)
|
|
||||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip05VerificationsViaTor.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldUseTorForNIP96(url: String) =
|
|
||||||
when (settings.torSettings.torType.value) {
|
|
||||||
TorType.OFF -> false
|
|
||||||
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip96UploadsViaTor.value)
|
|
||||||
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip96UploadsViaTor.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Log.d("AccountRegisterObservers", "Init")
|
Log.d("AccountRegisterObservers", "Init")
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUsers
|
|||||||
import com.vitorpamplona.quartz.nip01Core.verify
|
import com.vitorpamplona.quartz.nip01Core.verify
|
||||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolverBuilder
|
||||||
import com.vitorpamplona.quartz.nip03Timestamp.VerificationState
|
import com.vitorpamplona.quartz.nip03Timestamp.VerificationState
|
||||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||||
@@ -2102,7 +2102,7 @@ object LocalCache : ILocalCache {
|
|||||||
|
|
||||||
suspend fun findEarliestOtsForNote(
|
suspend fun findEarliestOtsForNote(
|
||||||
note: Note,
|
note: Note,
|
||||||
resolverBuilder: () -> OtsResolver,
|
resolverBuilder: OtsResolverBuilder,
|
||||||
): Long? {
|
): Long? {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
|||||||
@@ -745,14 +745,14 @@ open class Note(
|
|||||||
fun hasReacted(
|
fun hasReacted(
|
||||||
loggedIn: User,
|
loggedIn: User,
|
||||||
content: String,
|
content: String,
|
||||||
): Boolean = reactedBy(loggedIn, content).isNotEmpty()
|
): Boolean = allReactionsOfContentByAuthor(loggedIn, content).isNotEmpty()
|
||||||
|
|
||||||
fun reactedBy(
|
fun allReactionsOfContentByAuthor(
|
||||||
loggedIn: User,
|
loggedIn: User,
|
||||||
content: String,
|
content: String,
|
||||||
): List<Note> = reactions[content]?.filter { it.author == loggedIn } ?: emptyList()
|
): List<Note> = reactions[content]?.filter { it.author == loggedIn } ?: emptyList()
|
||||||
|
|
||||||
fun reactedBy(loggedIn: User): List<String> = reactions.filter { it.value.any { it.author == loggedIn } }.mapNotNull { it.key }
|
fun allReactionsByAuthor(loggedIn: User): List<String> = reactions.filter { it.value.any { it.author == loggedIn } }.mapNotNull { it.key }
|
||||||
|
|
||||||
fun hasBoostedInTheLast5Minutes(loggedIn: User): Boolean {
|
fun hasBoostedInTheLast5Minutes(loggedIn: User): Boolean {
|
||||||
val fiveMinsAgo = TimeUtils.fiveMinutesAgo()
|
val fiveMinsAgo = TimeUtils.fiveMinutesAgo()
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ import android.util.Log
|
|||||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.service.ots.OkHttpOtsResolverBuilder
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
@@ -35,7 +35,7 @@ import java.util.Base64
|
|||||||
class OtsState(
|
class OtsState(
|
||||||
val signer: NostrSigner,
|
val signer: NostrSigner,
|
||||||
val cache: LocalCache,
|
val cache: LocalCache,
|
||||||
val otsResolver: OtsResolver,
|
val otsResolver: OkHttpOtsResolverBuilder,
|
||||||
val scope: CoroutineScope,
|
val scope: CoroutineScope,
|
||||||
val settings: AccountSettings,
|
val settings: AccountSettings,
|
||||||
) {
|
) {
|
||||||
@@ -43,7 +43,7 @@ class OtsState(
|
|||||||
Log.d("Pending Attestations", "Updating ${settings.pendingAttestations.value.size} pending attestations")
|
Log.d("Pending Attestations", "Updating ${settings.pendingAttestations.value.size} pending attestations")
|
||||||
|
|
||||||
return settings.pendingAttestations.value.toList().mapNotNull { (key, value) ->
|
return settings.pendingAttestations.value.toList().mapNotNull { (key, value) ->
|
||||||
val otsState = OtsEvent.upgrade(Base64.getDecoder().decode(value), key, otsResolver)
|
val otsState = OtsEvent.upgrade(Base64.getDecoder().decode(value), key, otsResolver.build())
|
||||||
|
|
||||||
if (otsState != null) {
|
if (otsState != null) {
|
||||||
val hint = cache.getNoteIfExists(key)?.toEventHint<Event>()
|
val hint = cache.getNoteIfExists(key)?.toEventHint<Event>()
|
||||||
@@ -75,6 +75,15 @@ class OtsState(
|
|||||||
|
|
||||||
val id = note.event?.id ?: note.idHex
|
val id = note.event?.id ?: note.idHex
|
||||||
|
|
||||||
settings.addPendingAttestation(id, Base64.getEncoder().encodeToString(OtsEvent.stamp(id, otsResolver)))
|
settings.addPendingAttestation(
|
||||||
|
id = id,
|
||||||
|
stamp =
|
||||||
|
Base64.getEncoder().encodeToString(
|
||||||
|
OtsEvent.stamp(
|
||||||
|
id,
|
||||||
|
otsResolver.build(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+125
@@ -0,0 +1,125 @@
|
|||||||
|
/**
|
||||||
|
* 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.privacyOptions
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||||
|
import com.vitorpamplona.amethyst.ui.tor.TorType
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||||
|
|
||||||
|
class PrivacyState(
|
||||||
|
val settings: AccountSettings,
|
||||||
|
) {
|
||||||
|
fun shouldUseTorForImageDownload(url: String) =
|
||||||
|
shouldUseTorFor(
|
||||||
|
url,
|
||||||
|
settings.torSettings.torType.value,
|
||||||
|
settings.torSettings.imagesViaTor.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun shouldUseTorFor(
|
||||||
|
url: String,
|
||||||
|
torType: TorType,
|
||||||
|
imagesViaTor: Boolean,
|
||||||
|
) = when (torType) {
|
||||||
|
TorType.OFF -> false
|
||||||
|
TorType.INTERNAL -> shouldUseTor(url, imagesViaTor)
|
||||||
|
TorType.EXTERNAL -> shouldUseTor(url, imagesViaTor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldUseTor(
|
||||||
|
normalizedUrl: String,
|
||||||
|
final: Boolean,
|
||||||
|
): Boolean =
|
||||||
|
if (RelayUrlNormalizer.isLocalHost(normalizedUrl)) {
|
||||||
|
false
|
||||||
|
} else if (RelayUrlNormalizer.isOnion(normalizedUrl)) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
final
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shouldUseTorForVideoDownload() =
|
||||||
|
when (settings.torSettings.torType.value) {
|
||||||
|
TorType.OFF -> false
|
||||||
|
TorType.INTERNAL -> settings.torSettings.videosViaTor.value
|
||||||
|
TorType.EXTERNAL -> settings.torSettings.videosViaTor.value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shouldUseTorForVideoDownload(url: String) =
|
||||||
|
when (settings.torSettings.torType.value) {
|
||||||
|
TorType.OFF -> false
|
||||||
|
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.videosViaTor.value)
|
||||||
|
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.videosViaTor.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shouldUseTorForPreviewUrl(url: String) =
|
||||||
|
when (settings.torSettings.torType.value) {
|
||||||
|
TorType.OFF -> false
|
||||||
|
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.urlPreviewsViaTor.value)
|
||||||
|
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.urlPreviewsViaTor.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shouldUseTorForTrustedRelays() =
|
||||||
|
when (settings.torSettings.torType.value) {
|
||||||
|
TorType.OFF -> false
|
||||||
|
TorType.INTERNAL -> settings.torSettings.trustedRelaysViaTor.value
|
||||||
|
TorType.EXTERNAL -> settings.torSettings.trustedRelaysViaTor.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkLocalHostOnionAndThen(
|
||||||
|
url: String,
|
||||||
|
final: Boolean,
|
||||||
|
): Boolean = checkLocalHostOnionAndThen(url, settings.torSettings.onionRelaysViaTor.value, final)
|
||||||
|
|
||||||
|
private fun checkLocalHostOnionAndThen(
|
||||||
|
normalizedUrl: String,
|
||||||
|
isOnionRelaysActive: Boolean,
|
||||||
|
final: Boolean,
|
||||||
|
): Boolean =
|
||||||
|
if (RelayUrlNormalizer.isLocalHost(normalizedUrl)) {
|
||||||
|
false
|
||||||
|
} else if (RelayUrlNormalizer.isOnion(normalizedUrl)) {
|
||||||
|
isOnionRelaysActive
|
||||||
|
} else {
|
||||||
|
final
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shouldUseTorForMoneyOperations(url: String) =
|
||||||
|
when (settings.torSettings.torType.value) {
|
||||||
|
TorType.OFF -> false
|
||||||
|
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.moneyOperationsViaTor.value)
|
||||||
|
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.moneyOperationsViaTor.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shouldUseTorForNIP05(url: String) =
|
||||||
|
when (settings.torSettings.torType.value) {
|
||||||
|
TorType.OFF -> false
|
||||||
|
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip05VerificationsViaTor.value)
|
||||||
|
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip05VerificationsViaTor.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shouldUseTorForUploads(url: String) =
|
||||||
|
when (settings.torSettings.torType.value) {
|
||||||
|
TorType.OFF -> false
|
||||||
|
TorType.INTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip96UploadsViaTor.value)
|
||||||
|
TorType.EXTERNAL -> checkLocalHostOnionAndThen(url, settings.torSettings.nip96UploadsViaTor.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,10 +65,6 @@ class ChatroomList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createChatroom(withKey: ChatroomKey) {
|
|
||||||
getOrCreatePrivateChatroom(withKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeMessage(
|
fun removeMessage(
|
||||||
user: User,
|
user: User,
|
||||||
msg: Note,
|
msg: Note,
|
||||||
|
|||||||
@@ -115,4 +115,6 @@ class TorRelayState(
|
|||||||
dmRelayList = dmRelayState.flow.value,
|
dmRelayList = dmRelayState.flow.value,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun shouldUseTorForClean(relay: NormalizedRelayUrl) = flow.value.useTor(relay)
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-14
@@ -22,8 +22,13 @@ package com.vitorpamplona.amethyst.service.ots
|
|||||||
|
|
||||||
import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManager
|
import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManager
|
||||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||||
|
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolverBuilder
|
||||||
|
|
||||||
class OtsResolverBuilder {
|
class OkHttpOtsResolverBuilder(
|
||||||
|
val okHttpClients: DualHttpClientManager,
|
||||||
|
val shouldUseTorForUrl: (String) -> Boolean,
|
||||||
|
val cache: OtsBlockHeightCache,
|
||||||
|
) : OtsResolverBuilder {
|
||||||
fun getAPI(usingTor: Boolean) =
|
fun getAPI(usingTor: Boolean) =
|
||||||
if (usingTor) {
|
if (usingTor) {
|
||||||
OkHttpBitcoinExplorer.MEMPOOL_API_URL
|
OkHttpBitcoinExplorer.MEMPOOL_API_URL
|
||||||
@@ -31,22 +36,20 @@ class OtsResolverBuilder {
|
|||||||
OkHttpBitcoinExplorer.BLOCKSTREAM_API_URL
|
OkHttpBitcoinExplorer.BLOCKSTREAM_API_URL
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build(
|
override fun build(): OtsResolver {
|
||||||
okHttpClients: DualHttpClientManager,
|
|
||||||
shouldUseTorForUrl: (String) -> Boolean,
|
|
||||||
cache: OtsBlockHeightCache,
|
|
||||||
): OtsResolver {
|
|
||||||
val shouldUseTor = shouldUseTorForUrl(OkHttpBitcoinExplorer.MEMPOOL_API_URL)
|
val shouldUseTor = shouldUseTorForUrl(OkHttpBitcoinExplorer.MEMPOOL_API_URL)
|
||||||
|
|
||||||
return OtsResolver(
|
return OtsResolver(
|
||||||
OkHttpBitcoinExplorer(
|
explorer =
|
||||||
getAPI(shouldUseTor),
|
OkHttpBitcoinExplorer(
|
||||||
okHttpClients.getHttpClient(shouldUseTor),
|
baseAPI = getAPI(usingTor = shouldUseTor),
|
||||||
cache,
|
client = okHttpClients.getHttpClient(shouldUseTor),
|
||||||
),
|
cache = cache,
|
||||||
OkHttpCalendarBuilder {
|
),
|
||||||
okHttpClients.getHttpClient(shouldUseTorForUrl(it))
|
calendarBuilder =
|
||||||
},
|
OkHttpCalendarBuilder {
|
||||||
|
okHttpClients.getHttpClient(shouldUseTorForUrl(it))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+4
-4
@@ -152,7 +152,7 @@ class UploadOrchestrator {
|
|||||||
alt = alt,
|
alt = alt,
|
||||||
sensitiveContent = contentWarningReason,
|
sensitiveContent = contentWarningReason,
|
||||||
serverBaseUrl = serverBaseUrl,
|
serverBaseUrl = serverBaseUrl,
|
||||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||||
onProgress = { percent: Float ->
|
onProgress = { percent: Float ->
|
||||||
updateState(0.2 + (0.2 * percent), UploadingState.Uploading)
|
updateState(0.2 + (0.2 * percent), UploadingState.Uploading)
|
||||||
},
|
},
|
||||||
@@ -165,7 +165,7 @@ class UploadOrchestrator {
|
|||||||
localContentType = contentType,
|
localContentType = contentType,
|
||||||
originalContentType = contentTypeForResult,
|
originalContentType = contentTypeForResult,
|
||||||
originalHash = originalHash,
|
originalHash = originalHash,
|
||||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||||
)
|
)
|
||||||
} catch (_: SignerExceptions.ReadOnlyException) {
|
} catch (_: SignerExceptions.ReadOnlyException) {
|
||||||
error(R.string.login_with_a_private_key_to_be_able_to_upload)
|
error(R.string.login_with_a_private_key_to_be_able_to_upload)
|
||||||
@@ -198,7 +198,7 @@ class UploadOrchestrator {
|
|||||||
alt = alt,
|
alt = alt,
|
||||||
sensitiveContent = contentWarningReason,
|
sensitiveContent = contentWarningReason,
|
||||||
serverBaseUrl = serverBaseUrl,
|
serverBaseUrl = serverBaseUrl,
|
||||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||||
httpAuth = account::createBlossomUploadAuth,
|
httpAuth = account::createBlossomUploadAuth,
|
||||||
context = context,
|
context = context,
|
||||||
)
|
)
|
||||||
@@ -206,7 +206,7 @@ class UploadOrchestrator {
|
|||||||
verifyHeader(
|
verifyHeader(
|
||||||
uploadResult = result,
|
uploadResult = result,
|
||||||
localContentType = contentType,
|
localContentType = contentType,
|
||||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||||
originalHash = originalHash,
|
originalHash = originalHash,
|
||||||
originalContentType = contentTypeForResult,
|
originalContentType = contentTypeForResult,
|
||||||
)
|
)
|
||||||
|
|||||||
+3
-2
@@ -58,10 +58,11 @@ fun NewUserMetadataScreen(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val postViewModel: NewUserMetadataViewModel = viewModel()
|
val postViewModel: NewUserMetadataViewModel = viewModel()
|
||||||
|
postViewModel.init(accountViewModel)
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(accountViewModel) {
|
||||||
postViewModel.load(accountViewModel.account)
|
postViewModel.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|||||||
+30
-24
@@ -25,7 +25,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
@@ -35,17 +34,17 @@ import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
|
|||||||
import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader
|
import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader
|
||||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions
|
import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions
|
||||||
import com.vitorpamplona.quartz.nip39ExtIdentities.GitHubIdentity
|
import com.vitorpamplona.quartz.nip39ExtIdentities.GitHubIdentity
|
||||||
import com.vitorpamplona.quartz.nip39ExtIdentities.MastodonIdentity
|
import com.vitorpamplona.quartz.nip39ExtIdentities.MastodonIdentity
|
||||||
import com.vitorpamplona.quartz.nip39ExtIdentities.TwitterIdentity
|
import com.vitorpamplona.quartz.nip39ExtIdentities.TwitterIdentity
|
||||||
import com.vitorpamplona.quartz.nip39ExtIdentities.identityClaims
|
import com.vitorpamplona.quartz.nip39ExtIdentities.identityClaims
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
|
|
||||||
class NewUserMetadataViewModel : ViewModel() {
|
class NewUserMetadataViewModel : ViewModel() {
|
||||||
|
private lateinit var accountViewModel: AccountViewModel
|
||||||
private lateinit var account: Account
|
private lateinit var account: Account
|
||||||
|
|
||||||
// val userName = mutableStateOf("")
|
// val userName = mutableStateOf("")
|
||||||
@@ -68,9 +67,12 @@ class NewUserMetadataViewModel : ViewModel() {
|
|||||||
var isUploadingImageForPicture by mutableStateOf(false)
|
var isUploadingImageForPicture by mutableStateOf(false)
|
||||||
var isUploadingImageForBanner by mutableStateOf(false)
|
var isUploadingImageForBanner by mutableStateOf(false)
|
||||||
|
|
||||||
fun load(account: Account) {
|
fun init(accountViewModel: AccountViewModel) {
|
||||||
this.account = account
|
this.accountViewModel = accountViewModel
|
||||||
|
this.account = accountViewModel.account
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load() {
|
||||||
account.userProfile().let {
|
account.userProfile().let {
|
||||||
// userName.value = it.bestUsername() ?: ""
|
// userName.value = it.bestUsername() ?: ""
|
||||||
displayName.value = it.info?.bestName() ?: ""
|
displayName.value = it.info?.bestName() ?: ""
|
||||||
@@ -100,21 +102,25 @@ class NewUserMetadataViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun create() {
|
fun create() {
|
||||||
// Tries to not delete any existing attribute that we do not work with.
|
// Tries to not delete any existing attribute that we do not work with.
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
accountViewModel.runIOCatching {
|
||||||
account.sendNewUserMetadata(
|
val metadata =
|
||||||
name = displayName.value,
|
account.userMetadata.sendNewUserMetadata(
|
||||||
picture = picture.value,
|
name = displayName.value,
|
||||||
banner = banner.value,
|
picture = picture.value,
|
||||||
website = website.value,
|
banner = banner.value,
|
||||||
pronouns = pronouns.value,
|
website = website.value,
|
||||||
about = about.value,
|
pronouns = pronouns.value,
|
||||||
nip05 = nip05.value,
|
about = about.value,
|
||||||
lnAddress = lnAddress.value,
|
nip05 = nip05.value,
|
||||||
lnURL = lnURL.value,
|
lnAddress = lnAddress.value,
|
||||||
twitter = twitter.value,
|
lnURL = lnURL.value,
|
||||||
mastodon = mastodon.value,
|
twitter = twitter.value,
|
||||||
github = github.value,
|
mastodon = mastodon.value,
|
||||||
)
|
github = github.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
account.sendLiterallyEverywhere(metadata)
|
||||||
|
|
||||||
clear()
|
clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,7 +145,7 @@ class NewUserMetadataViewModel : ViewModel() {
|
|||||||
context: Context,
|
context: Context,
|
||||||
onError: (String, String) -> Unit,
|
onError: (String, String) -> Unit,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
accountViewModel.runIOCatching {
|
||||||
upload(
|
upload(
|
||||||
uri,
|
uri,
|
||||||
context,
|
context,
|
||||||
@@ -155,7 +161,7 @@ class NewUserMetadataViewModel : ViewModel() {
|
|||||||
context: Context,
|
context: Context,
|
||||||
onError: (String, String) -> Unit,
|
onError: (String, String) -> Unit,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
accountViewModel.runIOCatching {
|
||||||
upload(
|
upload(
|
||||||
uri,
|
uri,
|
||||||
context,
|
context,
|
||||||
@@ -187,7 +193,7 @@ class NewUserMetadataViewModel : ViewModel() {
|
|||||||
alt = null,
|
alt = null,
|
||||||
sensitiveContent = null,
|
sensitiveContent = null,
|
||||||
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
||||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||||
onProgress = {},
|
onProgress = {},
|
||||||
httpAuth = account::createHTTPAuthorization,
|
httpAuth = account::createHTTPAuthorization,
|
||||||
context = context,
|
context = context,
|
||||||
@@ -200,7 +206,7 @@ class NewUserMetadataViewModel : ViewModel() {
|
|||||||
alt = null,
|
alt = null,
|
||||||
sensitiveContent = null,
|
sensitiveContent = null,
|
||||||
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
||||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||||
httpAuth = account::createBlossomUploadAuth,
|
httpAuth = account::createBlossomUploadAuth,
|
||||||
context = context,
|
context = context,
|
||||||
)
|
)
|
||||||
|
|||||||
+3
-3
@@ -79,7 +79,7 @@ fun routeFor(
|
|||||||
}
|
}
|
||||||
} else if (innerEvent is ChatroomKeyable) {
|
} else if (innerEvent is ChatroomKeyable) {
|
||||||
val room = innerEvent.chatroomKey(loggedIn.userProfile().pubkeyHex)
|
val room = innerEvent.chatroomKey(loggedIn.userProfile().pubkeyHex)
|
||||||
loggedIn.chatroomList.createChatroom(room)
|
loggedIn.chatroomList.getOrCreatePrivateChatroom(room)
|
||||||
return Route.Room(room)
|
return Route.Room(room)
|
||||||
} else if (innerEvent is AddressableEvent) {
|
} else if (innerEvent is AddressableEvent) {
|
||||||
return Route.Note(noteEvent.aTag().toTag())
|
return Route.Note(noteEvent.aTag().toTag())
|
||||||
@@ -104,7 +104,7 @@ fun routeFor(
|
|||||||
}
|
}
|
||||||
} else if (noteEvent is ChatroomKeyable) {
|
} else if (noteEvent is ChatroomKeyable) {
|
||||||
val room = noteEvent.chatroomKey(loggedIn.userProfile().pubkeyHex)
|
val room = noteEvent.chatroomKey(loggedIn.userProfile().pubkeyHex)
|
||||||
loggedIn.chatroomList.createChatroom(room)
|
loggedIn.chatroomList.getOrCreatePrivateChatroom(room)
|
||||||
return Route.Room(room)
|
return Route.Room(room)
|
||||||
} else if (noteEvent is CommunityDefinitionEvent) {
|
} else if (noteEvent is CommunityDefinitionEvent) {
|
||||||
return Route.Community(noteEvent.kind, noteEvent.pubKey, noteEvent.dTag())
|
return Route.Community(noteEvent.kind, noteEvent.pubKey, noteEvent.dTag())
|
||||||
@@ -169,7 +169,7 @@ fun routeToMessage(
|
|||||||
draftId: HexKey? = null,
|
draftId: HexKey? = null,
|
||||||
account: Account,
|
account: Account,
|
||||||
): Route {
|
): Route {
|
||||||
account.chatroomList.createChatroom(room)
|
account.chatroomList.getOrCreatePrivateChatroom(room)
|
||||||
|
|
||||||
return Route.Room(room, draftMessage, replyId, draftId)
|
return Route.Room(room, draftMessage, replyId, draftId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1355,7 +1355,7 @@ fun ReactionChoicePopup(
|
|||||||
val iconSizePx = with(LocalDensity.current) { -iconSize.toPx().toInt() }
|
val iconSizePx = with(LocalDensity.current) { -iconSize.toPx().toInt() }
|
||||||
|
|
||||||
val reactions by accountViewModel.reactionChoicesFlow().collectAsStateWithLifecycle()
|
val reactions by accountViewModel.reactionChoicesFlow().collectAsStateWithLifecycle()
|
||||||
val toRemove = remember { baseNote.reactedBy(accountViewModel.userProfile()).toImmutableSet() }
|
val toRemove = remember { baseNote.allReactionsByAuthor(accountViewModel.userProfile()).toImmutableSet() }
|
||||||
|
|
||||||
Popup(
|
Popup(
|
||||||
alignment = Alignment.BottomCenter,
|
alignment = Alignment.BottomCenter,
|
||||||
|
|||||||
+1
-1
@@ -623,7 +623,7 @@ open class CommentPostViewModel :
|
|||||||
iMetaAttachments.downloadAndPrepare(item.link.url) {
|
iMetaAttachments.downloadAndPrepare(item.link.url) {
|
||||||
Amethyst.Companion.instance.okHttpClients
|
Amethyst.Companion.instance.okHttpClients
|
||||||
.getHttpClient(
|
.getHttpClient(
|
||||||
accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url)
|
accountViewModel?.account?.privacyState?.shouldUseTorForImageDownload(item.link.url)
|
||||||
?: false,
|
?: false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-63
@@ -98,7 +98,6 @@ import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
|||||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag
|
import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||||
import com.vitorpamplona.quartz.nip11RelayInfo.Nip11RelayInformation
|
import com.vitorpamplona.quartz.nip11RelayInfo.Nip11RelayInformation
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||||
@@ -266,13 +265,6 @@ class AccountViewModel(
|
|||||||
|
|
||||||
fun userProfile(): User = account.userProfile()
|
fun userProfile(): User = account.userProfile()
|
||||||
|
|
||||||
suspend fun reactTo(
|
|
||||||
note: Note,
|
|
||||||
reaction: String,
|
|
||||||
) {
|
|
||||||
account.reactTo(note, reaction)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Event> observeByETag(
|
fun <T : Event> observeByETag(
|
||||||
kind: Int,
|
kind: Int,
|
||||||
eTag: HexKey,
|
eTag: HexKey,
|
||||||
@@ -287,8 +279,8 @@ class AccountViewModel(
|
|||||||
note: Note,
|
note: Note,
|
||||||
reaction: String,
|
reaction: String,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
runIOCatching {
|
||||||
val currentReactions = account.reactionTo(note, reaction)
|
val currentReactions = note.allReactionsOfContentByAuthor(userProfile(), reaction)
|
||||||
if (currentReactions.isNotEmpty()) {
|
if (currentReactions.isNotEmpty()) {
|
||||||
account.delete(currentReactions)
|
account.delete(currentReactions)
|
||||||
} else {
|
} else {
|
||||||
@@ -298,14 +290,8 @@ class AccountViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun reactToOrDelete(note: Note) {
|
fun reactToOrDelete(note: Note) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
val reaction = reactionChoices().first()
|
||||||
val reaction = reactionChoices().first()
|
reactToOrDelete(note, reaction)
|
||||||
if (hasReactedTo(note, reaction)) {
|
|
||||||
deleteReactionTo(note, reaction)
|
|
||||||
} else {
|
|
||||||
reactTo(note, reaction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -402,24 +388,6 @@ class AccountViewModel(
|
|||||||
noteMustShowExpandButtonFlows.put(note, it)
|
noteMustShowExpandButtonFlows.put(note, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasReactedTo(
|
|
||||||
baseNote: Note,
|
|
||||||
reaction: String,
|
|
||||||
): Boolean = account.hasReacted(baseNote, reaction)
|
|
||||||
|
|
||||||
suspend fun deleteReactionTo(
|
|
||||||
note: Note,
|
|
||||||
reaction: String,
|
|
||||||
) {
|
|
||||||
account.delete(account.reactionTo(note, reaction))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hasBoosted(baseNote: Note): Boolean = account.hasBoosted(baseNote)
|
|
||||||
|
|
||||||
fun deleteBoostsTo(note: Note) {
|
|
||||||
runIOCatching { account.delete(account.boostsTo(note)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun calculateIfNoteWasZappedByAccount(
|
suspend fun calculateIfNoteWasZappedByAccount(
|
||||||
zappedNote: Note,
|
zappedNote: Note,
|
||||||
onWasZapped: (Boolean) -> Unit,
|
onWasZapped: (Boolean) -> Unit,
|
||||||
@@ -977,7 +945,7 @@ class AccountViewModel(
|
|||||||
.verifyNip05(
|
.verifyNip05(
|
||||||
nip05,
|
nip05,
|
||||||
okHttpClient = {
|
okHttpClient = {
|
||||||
app.okHttpClients.getHttpClient(account.shouldUseTorForNIP05(it))
|
app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForNIP05(it))
|
||||||
},
|
},
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
// Marks user as verified
|
// Marks user as verified
|
||||||
@@ -1103,7 +1071,7 @@ class AccountViewModel(
|
|||||||
) {
|
) {
|
||||||
onResult(
|
onResult(
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
LocalCache.findEarliestOtsForNote(note, account::otsResolver)
|
LocalCache.findEarliestOtsForNote(note, account.otsResolverBuilder)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1228,21 +1196,7 @@ class AccountViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createChatRoomFor(
|
fun setTorSettings(newTorSettings: TorSettings) = runIOCatching { account.settings.setTorSettings(newTorSettings) }
|
||||||
user: User,
|
|
||||||
then: (ChatroomKey) -> Unit,
|
|
||||||
) {
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
val withKey = ChatroomKey(persistentSetOf(user.pubkeyHex))
|
|
||||||
account.chatroomList.createChatroom(withKey)
|
|
||||||
then(withKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setTorSettings(newTorSettings: TorSettings) =
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
account.settings.setTorSettings(newTorSettings)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Factory(
|
class Factory(
|
||||||
val accountSettings: AccountSettings,
|
val accountSettings: AccountSettings,
|
||||||
@@ -1357,8 +1311,11 @@ class AccountViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isWriteable()) {
|
if (isWriteable()) {
|
||||||
if (hasBoosted(baseNote)) {
|
val boosts = baseNote.boostedBy(userProfile())
|
||||||
deleteBoostsTo(baseNote)
|
if (boosts.isNotEmpty()) {
|
||||||
|
runIOCatching {
|
||||||
|
account.delete(boosts)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
onMore()
|
onMore()
|
||||||
}
|
}
|
||||||
@@ -1496,21 +1453,21 @@ class AccountViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun proxyPortFor(url: String): Int? = app.okHttpClients.getCurrentProxyPort(account.shouldUseTorForVideoDownload(url))
|
fun proxyPortFor(url: String): Int? = app.okHttpClients.getCurrentProxyPort(account.privacyState.shouldUseTorForVideoDownload(url))
|
||||||
|
|
||||||
fun okHttpClientForNip96(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(url))
|
fun okHttpClientForNip96(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(url))
|
||||||
|
|
||||||
fun okHttpClientForImage(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForImageDownload(url))
|
fun okHttpClientForImage(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForImageDownload(url))
|
||||||
|
|
||||||
fun okHttpClientForVideo(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForVideoDownload(url))
|
fun okHttpClientForVideo(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForVideoDownload(url))
|
||||||
|
|
||||||
fun okHttpClientForMoney(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForMoneyOperations(url))
|
fun okHttpClientForMoney(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForMoneyOperations(url))
|
||||||
|
|
||||||
fun okHttpClientForPreview(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForPreviewUrl(url))
|
fun okHttpClientForPreview(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForPreviewUrl(url))
|
||||||
|
|
||||||
fun okHttpClientForClean(url: NormalizedRelayUrl): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForClean(url))
|
fun okHttpClientForClean(url: NormalizedRelayUrl): OkHttpClient = app.okHttpClients.getHttpClient(account.torRelayState.shouldUseTorForClean(url))
|
||||||
|
|
||||||
fun okHttpClientForTrustedRelays(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForTrustedRelays())
|
fun okHttpClientForTrustedRelays(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForTrustedRelays())
|
||||||
|
|
||||||
fun dataSources() = app.sources
|
fun dataSources() = app.sources
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -85,7 +85,7 @@ class DecryptAndIndexProcessor(
|
|||||||
|
|
||||||
is OtsEvent -> {
|
is OtsEvent -> {
|
||||||
// verifies new OTS upon arrival
|
// verifies new OTS upon arrival
|
||||||
Amethyst.instance.otsVerifCache.cacheVerify(event, account::otsResolver)
|
Amethyst.instance.otsVerifCache.cacheVerify(event, account.otsResolverBuilder)
|
||||||
}
|
}
|
||||||
|
|
||||||
is DraftEvent -> {
|
is DraftEvent -> {
|
||||||
|
|||||||
+1
-1
@@ -130,7 +130,7 @@ fun SetProxyDeterminator(accountViewModel: AccountViewModel) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun ObserveImageLoadingTor(accountViewModel: AccountViewModel) {
|
fun ObserveImageLoadingTor(accountViewModel: AccountViewModel) {
|
||||||
LaunchedEffect(accountViewModel) {
|
LaunchedEffect(accountViewModel) {
|
||||||
Amethyst.instance.setImageLoader(accountViewModel.account::shouldUseTorForImageDownload)
|
Amethyst.instance.setImageLoader(accountViewModel.account.privacyState::shouldUseTorForImageDownload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -640,7 +640,7 @@ class ChatNewMessageViewModel :
|
|||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
iMetaAttachments.downloadAndPrepare(item.link.url) {
|
iMetaAttachments.downloadAndPrepare(item.link.url) {
|
||||||
Amethyst.instance.okHttpClients.getHttpClient(accountViewModel.account.shouldUseTorForImageDownload(item.link.url))
|
Amethyst.instance.okHttpClients.getHttpClient(accountViewModel.account.privacyState.shouldUseTorForImageDownload(item.link.url))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -210,7 +210,7 @@ class ChannelMetadataViewModel : ViewModel() {
|
|||||||
alt = null,
|
alt = null,
|
||||||
sensitiveContent = null,
|
sensitiveContent = null,
|
||||||
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
||||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||||
onProgress = {},
|
onProgress = {},
|
||||||
httpAuth = account::createHTTPAuthorization,
|
httpAuth = account::createHTTPAuthorization,
|
||||||
context = context,
|
context = context,
|
||||||
@@ -223,7 +223,7 @@ class ChannelMetadataViewModel : ViewModel() {
|
|||||||
alt = null,
|
alt = null,
|
||||||
sensitiveContent = null,
|
sensitiveContent = null,
|
||||||
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
serverBaseUrl = account.settings.defaultFileServer.baseUrl,
|
||||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(it)) },
|
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.privacyState.shouldUseTorForUploads(it)) },
|
||||||
httpAuth = account::createBlossomUploadAuth,
|
httpAuth = account::createBlossomUploadAuth,
|
||||||
context = context,
|
context = context,
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -588,7 +588,7 @@ open class ChannelNewMessageViewModel :
|
|||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
iMetaAttachments.downloadAndPrepare(
|
iMetaAttachments.downloadAndPrepare(
|
||||||
item.link.url,
|
item.link.url,
|
||||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url) ?: false) },
|
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.privacyState?.shouldUseTorForImageDownload(item.link.url) ?: false) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -541,7 +541,7 @@ open class NewProductViewModel :
|
|||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
iMetaDescription.downloadAndPrepare(
|
iMetaDescription.downloadAndPrepare(
|
||||||
item.link.url,
|
item.link.url,
|
||||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url) ?: false) },
|
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.privacyState?.shouldUseTorForImageDownload(item.link.url) ?: false) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -786,7 +786,7 @@ open class ShortNotePostViewModel :
|
|||||||
item.link.url,
|
item.link.url,
|
||||||
) {
|
) {
|
||||||
Amethyst.instance.okHttpClients.getHttpClient(
|
Amethyst.instance.okHttpClients.getHttpClient(
|
||||||
accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url)
|
accountViewModel?.account?.privacyState?.shouldUseTorForImageDownload(item.link.url)
|
||||||
?: false,
|
?: false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material3.FilledTonalButton
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.OutlinedTextFieldDefaults.contentPadding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -36,6 +37,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
|
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
|
||||||
|
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageButton(
|
fun MessageButton(
|
||||||
@@ -49,12 +51,10 @@ fun MessageButton(
|
|||||||
.padding(horizontal = 3.dp)
|
.padding(horizontal = 3.dp)
|
||||||
.width(50.dp),
|
.width(50.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
accountViewModel.createChatRoomFor(user) { chatroomKey ->
|
nav.nav {
|
||||||
nav.nav(
|
routeToMessage(
|
||||||
routeToMessage(
|
room = ChatroomKey(setOf(user.pubkeyHex)),
|
||||||
room = chatroomKey,
|
account = accountViewModel.account,
|
||||||
account = accountViewModel.account,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ abstract class BasicRelaySetupInfoModel : ViewModel() {
|
|||||||
Nip11CachedRetriever.loadRelayInfo(
|
Nip11CachedRetriever.loadRelayInfo(
|
||||||
relay = item.relay,
|
relay = item.relay,
|
||||||
okHttpClient = {
|
okHttpClient = {
|
||||||
Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForClean(item.relay))
|
Amethyst.instance.okHttpClients.getHttpClient(account.torRelayState.shouldUseTorForClean(item.relay))
|
||||||
},
|
},
|
||||||
onInfo = {
|
onInfo = {
|
||||||
togglePaidRelay(item, it.limitation?.payment_required ?: false)
|
togglePaidRelay(item, it.limitation?.payment_required ?: false)
|
||||||
|
|||||||
+2
-2
@@ -93,7 +93,7 @@ class Nip65RelayListViewModel : ViewModel() {
|
|||||||
_homeRelays.value.forEach { item ->
|
_homeRelays.value.forEach { item ->
|
||||||
Nip11CachedRetriever.loadRelayInfo(
|
Nip11CachedRetriever.loadRelayInfo(
|
||||||
relay = item.relay,
|
relay = item.relay,
|
||||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForClean(item.relay)) },
|
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.torRelayState.shouldUseTorForClean(item.relay)) },
|
||||||
onInfo = {
|
onInfo = {
|
||||||
toggleHomePaidRelay(item, it.limitation?.payment_required ?: false)
|
toggleHomePaidRelay(item, it.limitation?.payment_required ?: false)
|
||||||
},
|
},
|
||||||
@@ -104,7 +104,7 @@ class Nip65RelayListViewModel : ViewModel() {
|
|||||||
_notificationRelays.value.forEach { item ->
|
_notificationRelays.value.forEach { item ->
|
||||||
Nip11CachedRetriever.loadRelayInfo(
|
Nip11CachedRetriever.loadRelayInfo(
|
||||||
relay = item.relay,
|
relay = item.relay,
|
||||||
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForClean(item.relay)) },
|
okHttpClient = { Amethyst.instance.okHttpClients.getHttpClient(account.torRelayState.shouldUseTorForClean(item.relay)) },
|
||||||
onInfo = {
|
onInfo = {
|
||||||
toggleNotifPaidRelay(item, it.limitation?.payment_required ?: false)
|
toggleNotifPaidRelay(item, it.limitation?.payment_required ?: false)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class PrivateZapTests {
|
|||||||
val privateZapRequest =
|
val privateZapRequest =
|
||||||
runBlocking {
|
runBlocking {
|
||||||
LnZapRequestEvent.create(
|
LnZapRequestEvent.create(
|
||||||
originalNote = poll,
|
zappedEvent = poll,
|
||||||
relays = setOf("wss://relay.damus.io/"),
|
relays = setOf("wss://relay.damus.io/"),
|
||||||
signer = loggedIn,
|
signer = loggedIn,
|
||||||
pollOption = 0,
|
pollOption = 0,
|
||||||
@@ -152,7 +152,7 @@ class PrivateZapTests {
|
|||||||
val privateZapRequest =
|
val privateZapRequest =
|
||||||
runBlocking {
|
runBlocking {
|
||||||
LnZapRequestEvent.create(
|
LnZapRequestEvent.create(
|
||||||
originalNote = textNote,
|
zappedEvent = textNote,
|
||||||
relays = setOf("wss://relay.damus.io/", "wss://relay.damus2.io/", "wss://relay.damus3.io/"),
|
relays = setOf("wss://relay.damus.io/", "wss://relay.damus2.io/", "wss://relay.damus3.io/"),
|
||||||
signer = loggedIn,
|
signer = loggedIn,
|
||||||
pollOption = null,
|
pollOption = null,
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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.quartz.nip03Timestamp
|
||||||
|
|
||||||
|
interface OtsResolverBuilder {
|
||||||
|
fun build(): OtsResolver
|
||||||
|
}
|
||||||
+4
-4
@@ -29,15 +29,15 @@ class VerificationStateCache {
|
|||||||
|
|
||||||
fun verify(
|
fun verify(
|
||||||
event: OtsEvent,
|
event: OtsEvent,
|
||||||
resolverBuilder: () -> OtsResolver,
|
resolverBuilder: OtsResolverBuilder,
|
||||||
): VerificationState {
|
): VerificationState {
|
||||||
cache.put(event.id, VerificationState.Verifying)
|
cache.put(event.id, VerificationState.Verifying)
|
||||||
return event.verifyState(resolverBuilder()).also { cache.put(event.id, it) }
|
return event.verifyState(resolverBuilder.build()).also { cache.put(event.id, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cacheVerify(
|
fun cacheVerify(
|
||||||
event: OtsEvent,
|
event: OtsEvent,
|
||||||
resolverBuilder: () -> OtsResolver,
|
resolverBuilder: OtsResolverBuilder,
|
||||||
): VerificationState =
|
): VerificationState =
|
||||||
when (val verif = cache[event.id]) {
|
when (val verif = cache[event.id]) {
|
||||||
is VerificationState.Verifying -> verif
|
is VerificationState.Verifying -> verif
|
||||||
@@ -45,7 +45,7 @@ class VerificationStateCache {
|
|||||||
is VerificationState.NetworkError -> {
|
is VerificationState.NetworkError -> {
|
||||||
// try again in 5 mins
|
// try again in 5 mins
|
||||||
if (verif.time < TimeUtils.fiveMinutesAgo()) {
|
if (verif.time < TimeUtils.fiveMinutesAgo()) {
|
||||||
event.verifyState(resolverBuilder()).also { cache.put(event.id, it) }
|
event.verifyState(resolverBuilder.build()).also { cache.put(event.id, it) }
|
||||||
} else {
|
} else {
|
||||||
verify(event, resolverBuilder)
|
verify(event, resolverBuilder)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ class LnZapRequestEvent(
|
|||||||
const val ALT = "Zap request"
|
const val ALT = "Zap request"
|
||||||
|
|
||||||
suspend fun create(
|
suspend fun create(
|
||||||
originalNote: Event,
|
zappedEvent: Event,
|
||||||
relays: Set<String>,
|
relays: Set<NormalizedRelayUrl>,
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
pollOption: Int?,
|
pollOption: Int?,
|
||||||
message: String,
|
message: String,
|
||||||
@@ -98,13 +98,13 @@ class LnZapRequestEvent(
|
|||||||
): LnZapRequestEvent {
|
): LnZapRequestEvent {
|
||||||
var tags =
|
var tags =
|
||||||
listOf(
|
listOf(
|
||||||
arrayOf("e", originalNote.id),
|
arrayOf("e", zappedEvent.id),
|
||||||
arrayOf("p", toUserPubHex ?: originalNote.pubKey),
|
arrayOf("p", toUserPubHex ?: zappedEvent.pubKey),
|
||||||
arrayOf("relays") + relays,
|
arrayOf("relays") + relays.map { it.url },
|
||||||
AltTag.assemble(ALT),
|
AltTag.assemble(ALT),
|
||||||
)
|
)
|
||||||
if (originalNote is AddressableEvent) {
|
if (zappedEvent is AddressableEvent) {
|
||||||
tags = tags + listOf(arrayOf("a", originalNote.aTag().toTag()))
|
tags = tags + listOf(arrayOf("a", zappedEvent.aTag().toTag()))
|
||||||
}
|
}
|
||||||
if (pollOption != null && pollOption >= 0) {
|
if (pollOption != null && pollOption >= 0) {
|
||||||
tags = tags + listOf(arrayOf(PollOptionTag.TAG_NAME, pollOption.toString()))
|
tags = tags + listOf(arrayOf(PollOptionTag.TAG_NAME, pollOption.toString()))
|
||||||
|
|||||||
Reference in New Issue
Block a user