mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-30 12:36:00 +02:00
NIP-39 Support
This commit is contained in:
parent
56433a3ad3
commit
3b582636f4
@ -9,6 +9,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||
import com.vitorpamplona.amethyst.service.model.Contact
|
||||
import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
||||
import com.vitorpamplona.amethyst.service.model.DeletionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.IdentityClaim
|
||||
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
||||
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||
@ -106,11 +107,11 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
fun sendNewUserMetadata(toString: String) {
|
||||
fun sendNewUserMetadata(toString: String, identities: List<IdentityClaim>) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
loggedIn.privKey?.let {
|
||||
val event = MetadataEvent.create(toString, loggedIn.privKey!!)
|
||||
val event = MetadataEvent.create(toString, identities, loggedIn.privKey!!)
|
||||
Client.send(event)
|
||||
LocalCache.consume(event)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.service.model
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.HexKey
|
||||
import com.vitorpamplona.amethyst.model.toHexKey
|
||||
import nostr.postr.Utils
|
||||
@ -14,6 +15,127 @@ data class ContactMetaData(
|
||||
val nip05: String?
|
||||
)
|
||||
|
||||
abstract class IdentityClaim(
|
||||
var identity: String,
|
||||
var proof: String
|
||||
) {
|
||||
abstract fun toProofUrl(): String
|
||||
abstract fun toIcon(): Int
|
||||
abstract fun toDescriptor(): Int
|
||||
abstract fun platform(): String
|
||||
|
||||
fun platformIdentity() = "${platform()}:$identity"
|
||||
|
||||
companion object {
|
||||
fun create(platformIdentity: String, proof: String): IdentityClaim? {
|
||||
val platformIdentity = platformIdentity.split(':')
|
||||
val platform = platformIdentity[0]
|
||||
val identity = platformIdentity[1]
|
||||
|
||||
return when (platform.lowercase()) {
|
||||
GitHubIdentity.platform -> GitHubIdentity(identity, proof)
|
||||
TwitterIdentity.platform -> TwitterIdentity(identity, proof)
|
||||
TelegramIdentity.platform -> TelegramIdentity(identity, proof)
|
||||
MastodonIdentity.platform -> MastodonIdentity(identity, proof)
|
||||
else -> throw IllegalArgumentException("Platform $platform not supported")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GitHubIdentity(
|
||||
identity: String,
|
||||
proof: String
|
||||
) : IdentityClaim(identity, proof) {
|
||||
override fun toProofUrl() = "https://gist.github.com/$identity/$proof"
|
||||
|
||||
override fun platform() = platform
|
||||
override fun toIcon() = R.drawable.github
|
||||
override fun toDescriptor() = R.string.github
|
||||
|
||||
companion object {
|
||||
val platform = "github"
|
||||
|
||||
fun parseProofUrl(proofUrl: String): GitHubIdentity? {
|
||||
return try {
|
||||
if (proofUrl.isBlank()) return null
|
||||
val path = proofUrl.removePrefix("https://gist.github.com/").split("?")[0].split("/")
|
||||
|
||||
GitHubIdentity(path[0], path[1])
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TwitterIdentity(
|
||||
identity: String,
|
||||
proof: String
|
||||
) : IdentityClaim(identity, proof) {
|
||||
override fun toProofUrl() = "https://twitter.com/$identity/status/$proof"
|
||||
|
||||
override fun platform() = platform
|
||||
override fun toIcon() = R.drawable.twitter
|
||||
override fun toDescriptor() = R.string.twitter
|
||||
|
||||
companion object {
|
||||
val platform = "twitter"
|
||||
|
||||
fun parseProofUrl(proofUrl: String): TwitterIdentity? {
|
||||
return try {
|
||||
if (proofUrl.isBlank()) return null
|
||||
val path = proofUrl.removePrefix("https://twitter.com/").split("?")[0].split("/")
|
||||
|
||||
TwitterIdentity(path[0], path[2])
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TelegramIdentity(
|
||||
identity: String,
|
||||
proof: String
|
||||
) : IdentityClaim(identity, proof) {
|
||||
override fun toProofUrl() = "https://t.me/$proof"
|
||||
|
||||
override fun platform() = platform
|
||||
override fun toIcon() = R.drawable.telegram
|
||||
override fun toDescriptor() = R.string.telegram
|
||||
|
||||
companion object {
|
||||
val platform = "telegram"
|
||||
}
|
||||
}
|
||||
|
||||
class MastodonIdentity(
|
||||
identity: String,
|
||||
proof: String
|
||||
) : IdentityClaim(identity, proof) {
|
||||
override fun toProofUrl() = "https://$identity/$proof"
|
||||
|
||||
override fun platform() = platform
|
||||
override fun toIcon() = R.drawable.mastodon
|
||||
override fun toDescriptor() = R.string.mastodon
|
||||
|
||||
companion object {
|
||||
val platform = "mastodon"
|
||||
|
||||
fun parseProofUrl(proofUrl: String): MastodonIdentity? {
|
||||
return try {
|
||||
if (proofUrl.isBlank()) return null
|
||||
val path = proofUrl.removePrefix("https://").split("?")[0].split("/")
|
||||
|
||||
return MastodonIdentity(path[0], path[1])
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MetadataEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
@ -29,18 +151,31 @@ class MetadataEvent(
|
||||
null
|
||||
}
|
||||
|
||||
fun identityClaims() = tags.filter { it.firstOrNull() == "i" }.mapNotNull {
|
||||
try {
|
||||
IdentityClaim.create(it.get(1), it.get(2))
|
||||
} catch (e: Exception) {
|
||||
Log.e("MetadataEvent", "Can't parse identity [${it.joinToString { "," }}]", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val kind = 0
|
||||
val gson = Gson()
|
||||
|
||||
fun create(contactMetaData: ContactMetaData, privateKey: ByteArray, createdAt: Long = Date().time / 1000): MetadataEvent {
|
||||
return create(gson.toJson(contactMetaData), privateKey, createdAt = createdAt)
|
||||
fun create(contactMetaData: ContactMetaData, identities: List<IdentityClaim>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): MetadataEvent {
|
||||
return create(gson.toJson(contactMetaData), identities, privateKey, createdAt = createdAt)
|
||||
}
|
||||
|
||||
fun create(contactMetaData: String, privateKey: ByteArray, createdAt: Long = Date().time / 1000): MetadataEvent {
|
||||
fun create(contactMetaData: String, identities: List<IdentityClaim>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): MetadataEvent {
|
||||
val content = contactMetaData
|
||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||
val tags = listOf<List<String>>()
|
||||
val tags = mutableListOf<List<String>>()
|
||||
identities?.forEach {
|
||||
tags.add(listOf("i", it.platformIdentity(), it.proof))
|
||||
}
|
||||
|
||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||
val sig = Utils.sign(id, privateKey)
|
||||
return MetadataEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||
|
@ -7,7 +7,9 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Surface
|
||||
@ -15,7 +17,6 @@ import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
@ -26,7 +27,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
val postViewModel: NewUserMetadataViewModel = viewModel()
|
||||
@ -66,158 +66,207 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp).verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(1f),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.display_name)) },
|
||||
modifier = Modifier.weight(1f),
|
||||
value = postViewModel.displayName.value,
|
||||
onValueChange = { postViewModel.displayName.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.my_display_name),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences
|
||||
),
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Text("@", Modifier.padding(5.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.username)) },
|
||||
modifier = Modifier.weight(1f),
|
||||
value = postViewModel.userName.value,
|
||||
onValueChange = { postViewModel.userName.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.my_username),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
Row(modifier = Modifier.fillMaxWidth(1f), verticalAlignment = Alignment.CenterVertically) {
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.display_name)) },
|
||||
modifier = Modifier.weight(1f),
|
||||
value = postViewModel.displayName.value,
|
||||
onValueChange = { postViewModel.displayName.value = it },
|
||||
label = { Text(text = stringResource(R.string.about_me)) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(100.dp),
|
||||
value = postViewModel.about.value,
|
||||
onValueChange = { postViewModel.about.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.my_display_name),
|
||||
text = stringResource(id = R.string.about_me),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences
|
||||
),
|
||||
singleLine = true
|
||||
maxLines = 10
|
||||
)
|
||||
|
||||
Text("@", Modifier.padding(5.dp))
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.username)) },
|
||||
modifier = Modifier.weight(1f),
|
||||
value = postViewModel.userName.value,
|
||||
onValueChange = { postViewModel.userName.value = it },
|
||||
label = { Text(text = stringResource(R.string.avatar_url)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.picture.value,
|
||||
onValueChange = { postViewModel.picture.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.my_username),
|
||||
text = "https://mywebsite.com/me.jpg",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.banner_url)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.banner.value,
|
||||
onValueChange = { postViewModel.banner.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "https://mywebsite.com/mybanner.jpg",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.website_url)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.website.value,
|
||||
onValueChange = { postViewModel.website.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "https://mywebsite.com",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.nip_05)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.nip05.value,
|
||||
onValueChange = { postViewModel.nip05.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "_@mywebsite.com",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.ln_address)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.lnAddress.value,
|
||||
onValueChange = { postViewModel.lnAddress.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "me@mylightiningnode.com",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.ln_url_outdated)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.lnURL.value,
|
||||
onValueChange = { postViewModel.lnURL.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.lnurl),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.twitter)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.twitter.value,
|
||||
onValueChange = { postViewModel.twitter.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.twitter_proof_url_template),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.mastodon)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.mastodon.value,
|
||||
onValueChange = { postViewModel.mastodon.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.mastodon_proof_url_template),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.github)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.github.value,
|
||||
onValueChange = { postViewModel.github.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.github_proof_url_template),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.about_me)) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(100.dp),
|
||||
value = postViewModel.about.value,
|
||||
onValueChange = { postViewModel.about.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.about_me),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences
|
||||
),
|
||||
maxLines = 10
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.avatar_url)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.picture.value,
|
||||
onValueChange = { postViewModel.picture.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "https://mywebsite.com/me.jpg",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.banner_url)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.banner.value,
|
||||
onValueChange = { postViewModel.banner.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "https://mywebsite.com/mybanner.jpg",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.website_url)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.website.value,
|
||||
onValueChange = { postViewModel.website.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "https://mywebsite.com",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.nip_05)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.nip05.value,
|
||||
onValueChange = { postViewModel.nip05.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "_@mywebsite.com",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.ln_address)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.lnAddress.value,
|
||||
onValueChange = { postViewModel.lnAddress.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "me@mylightiningnode.com",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.ln_url_outdated)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.lnURL.value,
|
||||
onValueChange = { postViewModel.lnURL.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.lnurl),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,9 @@ import androidx.lifecycle.ViewModel
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.model.GitHubIdentity
|
||||
import com.vitorpamplona.amethyst.service.model.MastodonIdentity
|
||||
import com.vitorpamplona.amethyst.service.model.TwitterIdentity
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.StringWriter
|
||||
|
||||
@ -23,6 +26,10 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
val lnAddress = mutableStateOf("")
|
||||
val lnURL = mutableStateOf("")
|
||||
|
||||
val twitter = mutableStateOf("")
|
||||
val github = mutableStateOf("")
|
||||
val mastodon = mutableStateOf("")
|
||||
|
||||
fun load(account: Account) {
|
||||
this.account = account
|
||||
|
||||
@ -36,6 +43,19 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
nip05.value = it.info?.nip05 ?: ""
|
||||
lnAddress.value = it.info?.lud16 ?: ""
|
||||
lnURL.value = it.info?.lud06 ?: ""
|
||||
|
||||
twitter.value = ""
|
||||
github.value = ""
|
||||
mastodon.value = ""
|
||||
|
||||
// TODO: Validate Telegram input, somehow.
|
||||
it.info?.latestMetadata?.identityClaims()?.forEach {
|
||||
when (it) {
|
||||
is TwitterIdentity -> twitter.value = it.toProofUrl()
|
||||
is GitHubIdentity -> github.value = it.toProofUrl()
|
||||
is MastodonIdentity -> mastodon.value = it.toProofUrl()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,10 +81,34 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
currentJson.put("lud16", lnAddress.value.trim())
|
||||
currentJson.put("lud06", lnURL.value.trim())
|
||||
|
||||
var claims = latest?.identityClaims() ?: emptyList()
|
||||
|
||||
if (twitter.value.isBlank()) {
|
||||
// delete twitter
|
||||
claims = claims.filter { it !is TwitterIdentity }
|
||||
}
|
||||
|
||||
if (github.value.isBlank()) {
|
||||
// delete github
|
||||
claims = claims.filter { it !is GitHubIdentity }
|
||||
}
|
||||
|
||||
if (mastodon.value.isBlank()) {
|
||||
// delete mastodon
|
||||
claims = claims.filter { it !is MastodonIdentity }
|
||||
}
|
||||
|
||||
// Updates while keeping other identities intact
|
||||
val newClaims = listOfNotNull(
|
||||
TwitterIdentity.parseProofUrl(twitter.value),
|
||||
GitHubIdentity.parseProofUrl(github.value),
|
||||
MastodonIdentity.parseProofUrl(mastodon.value)
|
||||
) + claims.filter { it !is TwitterIdentity && it !is GitHubIdentity && it !is MastodonIdentity }
|
||||
|
||||
val writer = StringWriter()
|
||||
ObjectMapper().writeValue(writer, currentJson)
|
||||
|
||||
account.sendNewUserMetadata(writer.buffer.toString())
|
||||
account.sendNewUserMetadata(writer.buffer.toString(), newClaims)
|
||||
|
||||
clear()
|
||||
}
|
||||
@ -79,5 +123,8 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
nip05.value = ""
|
||||
lnAddress.value = ""
|
||||
lnURL.value = ""
|
||||
twitter.value = ""
|
||||
github.value = ""
|
||||
mastodon.value = ""
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.BadgeProfilesEvent
|
||||
import com.vitorpamplona.amethyst.service.model.IdentityClaim
|
||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
|
||||
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
||||
@ -431,6 +432,24 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, navController:
|
||||
}
|
||||
}
|
||||
|
||||
userBadge.acceptedBadges?.let { note ->
|
||||
(note.event as? BadgeProfilesEvent)?.let { event ->
|
||||
FlowRow(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 5.dp)) {
|
||||
event.badgeAwardEvents().forEach { badgeAwardEvent ->
|
||||
val baseNote = LocalCache.notes[badgeAwardEvent]
|
||||
if (baseNote != null) {
|
||||
val badgeAwardState by baseNote.live().metadata.observeAsState()
|
||||
val baseBadgeDefinition = badgeAwardState?.note?.replyTo?.firstOrNull()
|
||||
|
||||
if (baseBadgeDefinition != null) {
|
||||
BadgeThumb(baseBadgeDefinition, navController, 50.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisplayNip05ProfileStatus(user)
|
||||
|
||||
val website = user.info?.website
|
||||
@ -484,20 +503,25 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, navController:
|
||||
}
|
||||
}
|
||||
|
||||
userBadge.acceptedBadges?.let { note ->
|
||||
(note.event as? BadgeProfilesEvent)?.let { event ->
|
||||
FlowRow(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 5.dp)) {
|
||||
event.badgeAwardEvents().forEach { badgeAwardEvent ->
|
||||
val baseNote = LocalCache.notes[badgeAwardEvent]
|
||||
if (baseNote != null) {
|
||||
val badgeAwardState by baseNote.live().metadata.observeAsState()
|
||||
val baseBadgeDefinition = badgeAwardState?.note?.replyTo?.firstOrNull()
|
||||
val identities = user.info?.latestMetadata?.identityClaims()
|
||||
if (!identities.isNullOrEmpty()) {
|
||||
identities.forEach { identity: IdentityClaim ->
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
tint = Color.Unspecified,
|
||||
painter = painterResource(id = identity.toIcon()),
|
||||
contentDescription = stringResource(identity.toDescriptor()),
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
|
||||
if (baseBadgeDefinition != null) {
|
||||
BadgeThumb(baseBadgeDefinition, navController, 50.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
ClickableText(
|
||||
text = AnnotatedString(identity.identity),
|
||||
onClick = { runCatching { uri.openUri(identity.toProofUrl()) } },
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
|
||||
modifier = Modifier
|
||||
.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
||||
.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
app/src/main/res/drawable/github.xml
Normal file
4
app/src/main/res/drawable/github.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<vector android:height="128dp" android:viewportHeight="512"
|
||||
android:viewportWidth="512" android:width="128dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#3A6CAC" android:fillType="evenOdd" android:pathData="M296.13,354.17c49.88,-5.89 102.94,-24.03 102.94,-110.19c0,-24.49 -8.62,-44.45 -22.67,-59.87c2.27,-5.89 9.52,-28.11 -2.73,-58.95c0,0 -18.14,-5.9 -60.76,22.67c-18.14,-4.98 -38.09,-8.16 -56.68,-8.16c-19.05,0 -39.01,3.18 -56.7,8.16c-43.08,-28.57 -61.22,-22.67 -61.22,-22.67c-12.24,30.83 -4.98,53.06 -2.72,58.95c-14.06,15.42 -22.68,35.38 -22.68,59.87c0,86.16 53.06,104.3 102.94,110.19c-6.34,5.45 -12.24,15.87 -14.51,30.39c-12.7,5.44 -45.81,15.87 -65.76,-18.59c0,0 -11.8,-21.31 -34.01,-22.67c0,0 -22.22,-0.45 -1.81,13.59c0,0 14.96,6.81 24.94,32.65c0,0 13.6,43.09 76.18,29.48v38.54c0,5.91 -4.53,12.7 -15.86,10.89C96.14,438.98 32.2,354.63 32.2,255.77c0,-123.81 100.22,-224.02 224.03,-224.02c123.35,0 224.02,100.22 223.57,224.02c0,98.86 -63.95,182.75 -152.83,212.69c-11.34,2.27 -15.87,-4.53 -15.87,-10.89V395.45C311.1,374.58 304.29,360.98 296.13,354.17L296.13,354.17zM512,256.23C512,114.73 397.26,0 256.23,0C114.73,0 0,114.73 0,256.23C0,397.26 114.73,512 256.23,512C397.26,512 512,397.26 512,256.23L512,256.23z"/>
|
||||
</vector>
|
14
app/src/main/res/drawable/mastodon.xml
Normal file
14
app/src/main/res/drawable/mastodon.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<vector android:height="128dp" android:viewportHeight="512"
|
||||
android:viewportWidth="512" android:width="128dp"
|
||||
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:pathData="M317,381q-124,28 -123,-39 69,15 149,2 67,-13 72,-80 3,-101 -3,-116 -19,-49 -72,-58 -98,-10 -162,0 -56,10 -75,58 -12,31 -3,147 3,32 9,53 13,46 70,69 83,23 138,-9">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:endX="418" android:endY="440"
|
||||
android:startX="91" android:startY="80" android:type="linear">
|
||||
<item android:color="#FF6364FF" android:offset="0"/>
|
||||
<item android:color="#FF563ACC" android:offset="1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:fillColor="#fff" android:pathData="M360,293h-36v-93q-1,-26 -29,-23 -20,3 -20,34v47h-36v-47q0,-31 -20,-34 -30,-3 -30,28v88h-36v-91q1,-51 44,-60 33,-5 51,21l9,15 9,-15q16,-26 51,-21 43,9 43,60"/>
|
||||
</vector>
|
6
app/src/main/res/drawable/telegram.xml
Normal file
6
app/src/main/res/drawable/telegram.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<vector android:height="128dp" android:viewportHeight="512"
|
||||
android:viewportWidth="512" android:width="128dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#c8daea" android:pathData="M199,404c-11,0 -10,-4 -13,-14l-32,-105 245,-144"/>
|
||||
<path android:fillColor="#a9c9dd" android:pathData="M199,404c7,0 11,-4 16,-8l45,-43 -56,-34"/>
|
||||
<path android:fillColor="#37aee2" android:pathData="M204,319l135,99c14,9 26,4 30,-14l55,-258c5,-22 -9,-32 -24,-25L79,245c-21,8 -21,21 -4,26l83,26 190,-121c9,-5 17,-3 11,4"/>
|
||||
</vector>
|
7
app/src/main/res/drawable/twitter.xml
Normal file
7
app/src/main/res/drawable/twitter.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="128dp" android:viewportHeight="512"
|
||||
android:viewportWidth="512" android:width="128dp">
|
||||
<path
|
||||
android:pathData="M437,152a72,72 0,0 1,-40 12a72,72 0,0 0,32 -40a72,72 0,0 1,-45 17a72,72 0,0 0,-122 65a200,200 0,0 1,-145 -74a72,72 0,0 0,22 94a72,72 0,0 1,-32 -7a72,72 0,0 0,56 69a72,72 0,0 1,-32 1a72,72 0,0 0,67 50a200,200 0,0 1,-105 29a200,200 0,0 0,309 -179a200,200 0,0 0,35 -37"
|
||||
android:fillColor="#1da1f2"/>
|
||||
</vector>
|
@ -208,4 +208,17 @@
|
||||
<string name="quick_action_follow">Follow</string>
|
||||
<string name="quick_action_request_deletion_alert_title">Request Deletion</string>
|
||||
<string name="quick_action_request_deletion_alert_body">Amethyst will request that your note be deleted from the relays you are currently connected to. There is no guarantee that your note will be permanently deleted from those relays, or from other relays where it may be stored.</string>
|
||||
|
||||
<string name="github" translatable="false">Github Gist w/ Proof</string>
|
||||
<string name="telegram" translatable="false">Telegram</string>
|
||||
<string name="mastodon" translatable="false">Mastodon Post ID w/ Proof</string>
|
||||
<string name="twitter" translatable="false">Twitter Status w/ Proof</string>
|
||||
|
||||
<string name="github_proof_url_template" translatable="false">https://gist.github.com/<user>/<gist></string>
|
||||
<string name="telegram_proof_url_template" translatable="false">https://t.me/<proof post></string>
|
||||
<string name="mastodon_proof_url_template" translatable="false">https://<server>/<user>/<proof post></string>
|
||||
<string name="twitter_proof_url_template" translatable="false">https://twitter.com/<user>/status/<proof post></string>
|
||||
|
||||
|
||||
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user