From 3a08db2dfb939d7e2c433aa6f4503906d49666a1 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 7 Aug 2023 17:16:05 -0300 Subject: [PATCH] add signerDialog in the profile screen follow button --- .../vitorpamplona/amethyst/model/Account.kt | 30 ++++++++--- .../amethyst/service/PackageUtils.kt | 9 ++++ .../service/model/ContactListEvent.kt | 51 ++++++++++++++++++- .../amethyst/ui/actions/SignerDialog.kt | 23 ++++++++- .../ui/screen/loggedIn/ProfileScreen.kt | 43 +++++++++++++--- .../ui/screen/loggedOff/LoginScreen.kt | 8 +-- 6 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/PackageUtils.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index d3b78418a..188386fc3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -460,7 +460,11 @@ class Account( if (followingCommunities.isNotEmpty()) { followingCommunities.forEach { 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() @@ -468,7 +472,11 @@ class Account( if (followingChannels.isNotEmpty()) { 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() } @@ -476,13 +484,17 @@ class Account( return returningContactList } - fun follow(user: User) { - if (!isWriteable()) return + fun follow(user: User, signEvent: Boolean = true): ContactListEvent? { + if (!isWriteable() && signEvent) return null val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList) 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 { ContactListEvent.createFromScratch( followUsers = listOf(Contact(user.pubkeyHex, null)), @@ -491,12 +503,18 @@ class Account( followCommunities = emptyList(), followEvents = DefaultChannels.toList(), 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) LocalCache.consume(event) + return null } fun follow(channel: Channel) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/PackageUtils.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/PackageUtils.kt new file mode 100644 index 000000000..9740c806b --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/PackageUtils.kt @@ -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 + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ContactListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ContactListEvent.kt index 800618b9d..9bbfd75fc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ContactListEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ContactListEvent.kt @@ -98,7 +98,8 @@ class ContactListEvent( followEvents: List, relayUse: Map?, privateKey: ByteArray, - createdAt: Long = TimeUtils.now() + createdAt: Long = TimeUtils.now(), + publicKey: ByteArray? = null ): ContactListEvent { val content = if (relayUse != null) { gson.toJson(relayUse) @@ -126,10 +127,29 @@ class ContactListEvent( listOf("g", it) } + if (publicKey == null) { + return create( + content = content, + tags = tags, + privateKey = privateKey, + createdAt = createdAt + ) + } return create( content = content, 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 ) } @@ -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 { 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 { if (!earlierVersion.isTaggedAddressableNote(aTag.toTag())) return earlierVersion @@ -259,6 +301,11 @@ class ContactListEvent( ) } + fun create(content: String, tags: List>, 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>, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ContactListEvent { val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey() val id = generateId(pubKey, createdAt, kind, tags, content) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/SignerDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/SignerDialog.kt index 33aa3e41d..d4c4e6bcb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/SignerDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/SignerDialog.kt @@ -59,7 +59,7 @@ fun openAmber( @Composable fun SignerDialog( onClose: () -> Unit, - onPost: () -> Unit, + onPost: (signedEvent: Event) -> Unit, event: Event ) { var signature by remember { mutableStateOf("") } @@ -120,7 +120,26 @@ fun SignerDialog( PostButton( 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 ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index a83254357..a242b8d94 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -59,16 +59,21 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User 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.AppDefinitionEvent import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent 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.PayInvoiceErrorResponse import com.vitorpamplona.amethyst.service.model.PayInvoiceSuccessResponse 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.NewUserMetadataView +import com.vitorpamplona.amethyst.ui.actions.SignerDialog import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus @@ -742,6 +747,24 @@ private fun DisplayFollowUnfollowButton( it.user.isFollowing(accountViewModel.account.userProfile()) }.distinctUntilChanged().observeAsState(initial = baseUser.isFollowing(accountViewModel.account.userProfile())) + var event by remember { mutableStateOf(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) { UnfollowButton { if (!accountViewModel.isWriteable()) { @@ -782,14 +805,18 @@ private fun DisplayFollowUnfollowButton( } else { FollowButton(R.string.follow) { if (!accountViewModel.isWriteable()) { - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.login_with_a_private_key_to_be_able_to_follow), - Toast.LENGTH_SHORT - ) - .show() + if (PackageUtils.isPackageInstalled(context, "com.greenart7c3.nostrsigner")) { + event = accountViewModel.account.follow(baseUser, false) + } else { + scope.launch { + Toast + .makeText( + context, + context.getString(R.string.login_with_a_private_key_to_be_able_to_follow), + Toast.LENGTH_SHORT + ) + .show() + } } } else { scope.launch(Dispatchers.IO) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt index 5bf87bbf7..10873bfd9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt @@ -1,6 +1,5 @@ package com.vitorpamplona.amethyst.ui.screen.loggedOff -import android.content.Context import android.util.Log import androidx.compose.foundation.Image 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.unit.dp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.service.PackageUtils import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel 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) { Checkbox( 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 -}