add signerDialog in the profile screen follow button

This commit is contained in:
greenart7c3
2023-08-07 17:16:05 -03:00
parent 41fd2e087c
commit 3a08db2dfb
6 changed files with 140 additions and 24 deletions
@@ -460,7 +460,11 @@ class Account(
if (followingCommunities.isNotEmpty()) { if (followingCommunities.isNotEmpty()) {
followingCommunities.forEach { followingCommunities.forEach {
ATag.parse(it, null)?.let { ATag.parse(it, null)?.let {
returningContactList = ContactListEvent.followAddressableEvent(returningContactList, it, keyPair.privKey!!) returningContactList = if (keyPair.privKey == null) {
ContactListEvent.followAddressableEvent(returningContactList, it, keyPair.pubKey.toHexKey())
} else {
ContactListEvent.followAddressableEvent(returningContactList, it, keyPair.privKey)
}
} }
} }
followingCommunities = emptySet() followingCommunities = emptySet()
@@ -468,7 +472,11 @@ class Account(
if (followingChannels.isNotEmpty()) { if (followingChannels.isNotEmpty()) {
followingChannels.forEach { followingChannels.forEach {
returningContactList = ContactListEvent.followEvent(returningContactList, it, keyPair.privKey!!) returningContactList = if (keyPair.privKey == null) {
ContactListEvent.followEvent(returningContactList, it, keyPair.pubKey.toHexKey())
} else {
ContactListEvent.followEvent(returningContactList, it, keyPair.privKey)
}
} }
followingChannels = emptySet() followingChannels = emptySet()
} }
@@ -476,13 +484,17 @@ class Account(
return returningContactList return returningContactList
} }
fun follow(user: User) { fun follow(user: User, signEvent: Boolean = true): ContactListEvent? {
if (!isWriteable()) return if (!isWriteable() && signEvent) return null
val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList) val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList)
val event = if (contactList != null) { val event = if (contactList != null) {
ContactListEvent.followUser(contactList, user.pubkeyHex, keyPair.privKey!!) if (signEvent) {
ContactListEvent.followUser(contactList, user.pubkeyHex, keyPair.privKey!!)
} else {
ContactListEvent.followUser(contactList, user.pubkeyHex, keyPair.pubKey.toHexKey())
}
} else { } else {
ContactListEvent.createFromScratch( ContactListEvent.createFromScratch(
followUsers = listOf(Contact(user.pubkeyHex, null)), followUsers = listOf(Contact(user.pubkeyHex, null)),
@@ -491,12 +503,18 @@ class Account(
followCommunities = emptyList(), followCommunities = emptyList(),
followEvents = DefaultChannels.toList(), followEvents = DefaultChannels.toList(),
relayUse = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) }, relayUse = Constants.defaultRelays.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) },
privateKey = keyPair.privKey!! privateKey = keyPair.privKey!!,
publicKey = keyPair.pubKey
) )
} }
if (!signEvent) {
return event
}
Client.send(event) Client.send(event)
LocalCache.consume(event) LocalCache.consume(event)
return null
} }
fun follow(channel: Channel) { fun follow(channel: Channel) {
@@ -0,0 +1,9 @@
package com.vitorpamplona.amethyst.service
import android.content.Context
object PackageUtils {
fun isPackageInstalled(context: Context, target: String): Boolean {
return context.packageManager.getInstalledApplications(0).find { info -> info.packageName == target } != null
}
}
@@ -98,7 +98,8 @@ class ContactListEvent(
followEvents: List<String>, followEvents: List<String>,
relayUse: Map<String, ReadWrite>?, relayUse: Map<String, ReadWrite>?,
privateKey: ByteArray, privateKey: ByteArray,
createdAt: Long = TimeUtils.now() createdAt: Long = TimeUtils.now(),
publicKey: ByteArray? = null
): ContactListEvent { ): ContactListEvent {
val content = if (relayUse != null) { val content = if (relayUse != null) {
gson.toJson(relayUse) gson.toJson(relayUse)
@@ -126,10 +127,29 @@ class ContactListEvent(
listOf("g", it) listOf("g", it)
} }
if (publicKey == null) {
return create(
content = content,
tags = tags,
privateKey = privateKey,
createdAt = createdAt
)
}
return create( return create(
content = content, content = content,
tags = tags, tags = tags,
privateKey = privateKey, pubKey = publicKey.toHexKey(),
createdAt = createdAt
)
}
fun followUser(earlierVersion: ContactListEvent, pubKeyHex: String, pubKey: HexKey, createdAt: Long = TimeUtils.now()): ContactListEvent {
if (earlierVersion.isTaggedUser(pubKeyHex)) return earlierVersion
return create(
content = earlierVersion.content,
tags = earlierVersion.tags.plus(element = listOf("p", pubKeyHex)),
pubKey = pubKey,
createdAt = createdAt createdAt = createdAt
) )
} }
@@ -211,6 +231,17 @@ class ContactListEvent(
) )
} }
fun followEvent(earlierVersion: ContactListEvent, idHex: String, pubKey: HexKey, createdAt: Long = TimeUtils.now()): ContactListEvent {
if (earlierVersion.isTaggedEvent(idHex)) return earlierVersion
return create(
content = earlierVersion.content,
tags = earlierVersion.tags.plus(element = listOf("e", idHex)),
pubKey = pubKey,
createdAt = createdAt
)
}
fun unfollowEvent(earlierVersion: ContactListEvent, idHex: String, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ContactListEvent { fun unfollowEvent(earlierVersion: ContactListEvent, idHex: String, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ContactListEvent {
if (!earlierVersion.isTaggedEvent(idHex)) return earlierVersion if (!earlierVersion.isTaggedEvent(idHex)) return earlierVersion
@@ -233,6 +264,17 @@ class ContactListEvent(
) )
} }
fun followAddressableEvent(earlierVersion: ContactListEvent, aTag: ATag, pubKey: HexKey, createdAt: Long = TimeUtils.now()): ContactListEvent {
if (earlierVersion.isTaggedAddressableNote(aTag.toTag())) return earlierVersion
return create(
content = earlierVersion.content,
tags = earlierVersion.tags.plus(element = listOfNotNull("a", aTag.toTag(), aTag.relay)),
pubKey = pubKey,
createdAt = createdAt
)
}
fun unfollowAddressableEvent(earlierVersion: ContactListEvent, aTag: ATag, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ContactListEvent { fun unfollowAddressableEvent(earlierVersion: ContactListEvent, aTag: ATag, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ContactListEvent {
if (!earlierVersion.isTaggedAddressableNote(aTag.toTag())) return earlierVersion if (!earlierVersion.isTaggedAddressableNote(aTag.toTag())) return earlierVersion
@@ -259,6 +301,11 @@ class ContactListEvent(
) )
} }
fun create(content: String, tags: List<List<String>>, pubKey: HexKey, createdAt: Long = TimeUtils.now()): ContactListEvent {
val id = generateId(pubKey, createdAt, kind, tags, content)
return ContactListEvent(id.toHexKey(), pubKey, createdAt, tags, content, "")
}
fun create(content: String, tags: List<List<String>>, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ContactListEvent { fun create(content: String, tags: List<List<String>>, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ContactListEvent {
val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey() val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
val id = generateId(pubKey, createdAt, kind, tags, content) val id = generateId(pubKey, createdAt, kind, tags, content)
@@ -59,7 +59,7 @@ fun openAmber(
@Composable @Composable
fun SignerDialog( fun SignerDialog(
onClose: () -> Unit, onClose: () -> Unit,
onPost: () -> Unit, onPost: (signedEvent: Event) -> Unit,
event: Event event: Event
) { ) {
var signature by remember { mutableStateOf("") } var signature by remember { mutableStateOf("") }
@@ -120,7 +120,26 @@ fun SignerDialog(
PostButton( PostButton(
onPost = { onPost = {
onPost() val signedEvent = Event(
event.id,
event.pubKey,
event.createdAt,
event.kind,
event.tags,
event.content,
signature
)
if (!signedEvent.hasValidSignature()) {
scope.launch {
Toast.makeText(
context,
"Invalid signature",
Toast.LENGTH_SHORT
).show()
}
return@PostButton
}
onPost(signedEvent)
}, },
isActive = true isActive = true
) )
@@ -59,16 +59,21 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
import com.vitorpamplona.amethyst.service.PackageUtils
import com.vitorpamplona.amethyst.service.model.ATag import com.vitorpamplona.amethyst.service.model.ATag
import com.vitorpamplona.amethyst.service.model.AppDefinitionEvent import com.vitorpamplona.amethyst.service.model.AppDefinitionEvent
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
import com.vitorpamplona.amethyst.service.model.BadgeProfilesEvent import com.vitorpamplona.amethyst.service.model.BadgeProfilesEvent
import com.vitorpamplona.amethyst.service.model.ContactListEvent
import com.vitorpamplona.amethyst.service.model.Event
import com.vitorpamplona.amethyst.service.model.IdentityClaim import com.vitorpamplona.amethyst.service.model.IdentityClaim
import com.vitorpamplona.amethyst.service.model.PayInvoiceErrorResponse import com.vitorpamplona.amethyst.service.model.PayInvoiceErrorResponse
import com.vitorpamplona.amethyst.service.model.PayInvoiceSuccessResponse import com.vitorpamplona.amethyst.service.model.PayInvoiceSuccessResponse
import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.ReportEvent
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus
@@ -742,6 +747,24 @@ private fun DisplayFollowUnfollowButton(
it.user.isFollowing(accountViewModel.account.userProfile()) it.user.isFollowing(accountViewModel.account.userProfile())
}.distinctUntilChanged().observeAsState(initial = baseUser.isFollowing(accountViewModel.account.userProfile())) }.distinctUntilChanged().observeAsState(initial = baseUser.isFollowing(accountViewModel.account.userProfile()))
var event by remember { mutableStateOf<ContactListEvent?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
Client.send(it)
LocalCache.verifyAndConsume(it, null)
event = null
}
},
event = event!!
)
}
if (isLoggedInFollowingUser) { if (isLoggedInFollowingUser) {
UnfollowButton { UnfollowButton {
if (!accountViewModel.isWriteable()) { if (!accountViewModel.isWriteable()) {
@@ -782,14 +805,18 @@ private fun DisplayFollowUnfollowButton(
} else { } else {
FollowButton(R.string.follow) { FollowButton(R.string.follow) {
if (!accountViewModel.isWriteable()) { if (!accountViewModel.isWriteable()) {
scope.launch { if (PackageUtils.isPackageInstalled(context, "com.greenart7c3.nostrsigner")) {
Toast event = accountViewModel.account.follow(baseUser, false)
.makeText( } else {
context, scope.launch {
context.getString(R.string.login_with_a_private_key_to_be_able_to_follow), Toast
Toast.LENGTH_SHORT .makeText(
) context,
.show() context.getString(R.string.login_with_a_private_key_to_be_able_to_follow),
Toast.LENGTH_SHORT
)
.show()
}
} }
} else { } else {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
@@ -1,6 +1,5 @@
package com.vitorpamplona.amethyst.ui.screen.loggedOff package com.vitorpamplona.amethyst.ui.screen.loggedOff
import android.content.Context
import android.util.Log import android.util.Log
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@@ -37,6 +36,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.PackageUtils
import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
@@ -229,7 +229,7 @@ fun LoginPage(
} }
} }
if (isPackageInstalled(context, "org.torproject.android")) { if (PackageUtils.isPackageInstalled(context, "org.torproject.android")) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox( Checkbox(
checked = useProxy.value, checked = useProxy.value,
@@ -315,7 +315,3 @@ fun LoginPage(
) )
} }
} }
fun isPackageInstalled(context: Context, target: String): Boolean {
return context.packageManager.getInstalledApplications(0).find { info -> info.packageName == target } != null
}