Nip05 Support
This commit is contained in:
@@ -0,0 +1,91 @@
|
|||||||
|
package com.vitorpamplona.amethyst.lnurl
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
import java.net.URLEncoder
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import nostr.postr.Bech32
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
|
||||||
|
class Nip05Verifier {
|
||||||
|
val client = OkHttpClient.Builder().build()
|
||||||
|
|
||||||
|
fun assembleUrl(nip05address: String): String? {
|
||||||
|
val parts = nip05address.split("@")
|
||||||
|
|
||||||
|
if (parts.size == 2) {
|
||||||
|
return "https://${parts[1]}/.well-known/nostr.json?name=${parts[0]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchNip05Json(lnaddress: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
|
||||||
|
val scope = CoroutineScope(Job() + Dispatchers.IO)
|
||||||
|
scope.launch {
|
||||||
|
fetchNip05JsonSuspend(lnaddress, onSuccess, onError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchNip05JsonSuspend(nip05: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
|
||||||
|
val url = assembleUrl(nip05)
|
||||||
|
|
||||||
|
if (url == null) {
|
||||||
|
onError("Could not assemble url from Nip05: \"${nip05}\". Check the user's setup")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val request: Request = Request.Builder().url(url).build()
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(object : Callback {
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
response.use {
|
||||||
|
if (it.isSuccessful)
|
||||||
|
onSuccess(it.body.string())
|
||||||
|
else
|
||||||
|
onError("Could not resolve ${nip05}. Error: ${it.code}. Check if the server up and if the address ${nip05} is correct")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call, e: java.io.IOException) {
|
||||||
|
onError("Could not resolve ${url}. Check if the server up and if the address ${nip05} is correct")
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyNip05(nip05: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
|
||||||
|
val mapper = jacksonObjectMapper()
|
||||||
|
|
||||||
|
fetchNip05Json(nip05,
|
||||||
|
onSuccess = {
|
||||||
|
val nip05url = try {
|
||||||
|
mapper.readTree(it)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
onError("Error Parsing JSON from Lightning Address. Check the user's lightning setup")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val user = nip05.split("@")[0]
|
||||||
|
|
||||||
|
val hexKey = nip05url?.get("names")?.get(user)?.asText()
|
||||||
|
|
||||||
|
if (hexKey == null) {
|
||||||
|
onError("Username not found in the NIP05 JSON")
|
||||||
|
} else {
|
||||||
|
onSuccess(hexKey)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError = onError
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,6 +72,10 @@ class User(val pubkeyHex: String) {
|
|||||||
return info?.displayName?.ifBlank { null } ?: info?.display_name?.ifBlank { null }
|
return info?.displayName?.ifBlank { null } ?: info?.display_name?.ifBlank { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun nip05(): String? {
|
||||||
|
return info?.nip05?.ifBlank { null }
|
||||||
|
}
|
||||||
|
|
||||||
fun profilePicture(): String? {
|
fun profilePicture(): String? {
|
||||||
if (info?.picture.isNullOrBlank()) info?.picture = null
|
if (info?.picture.isNullOrBlank()) info?.picture = null
|
||||||
return info?.picture
|
return info?.picture
|
||||||
@@ -347,7 +351,11 @@ class UserMetadata {
|
|||||||
var banner: String? = null
|
var banner: String? = null
|
||||||
var website: String? = null
|
var website: String? = null
|
||||||
var about: String? = null
|
var about: String? = null
|
||||||
|
|
||||||
var nip05: String? = null
|
var nip05: String? = null
|
||||||
|
var nip05Verified: Boolean = false
|
||||||
|
var nip05LastVerificationTime: Long? = 0
|
||||||
|
|
||||||
var domain: String? = null
|
var domain: String? = null
|
||||||
var lud06: String? = null
|
var lud06: String? = null
|
||||||
var lud16: String? = null
|
var lud16: String? = null
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ import com.vitorpamplona.amethyst.service.model.RepostEvent
|
|||||||
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
||||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.DisplayNip05Status
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.ObserveDisplayNip05Status
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Following
|
import com.vitorpamplona.amethyst.ui.theme.Following
|
||||||
import nostr.postr.events.TextNoteEvent
|
import nostr.postr.events.TextNoteEvent
|
||||||
@@ -114,10 +116,11 @@ fun NoteCompose(
|
|||||||
parentBackgroundColor ?: MaterialTheme.colors.background
|
parentBackgroundColor ?: MaterialTheme.colors.background
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(modifier = modifier.combinedClickable(
|
Column(modifier = modifier
|
||||||
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (noteEvent !is ChannelMessageEvent) {
|
if (noteEvent !is ChannelMessageEvent) {
|
||||||
navController.navigate("Note/${note.idHex}"){
|
navController.navigate("Note/${note.idHex}") {
|
||||||
launchSingleTop = true
|
launchSingleTop = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -127,7 +130,8 @@ fun NoteCompose(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClick = { popupExpanded = true }
|
onLongClick = { popupExpanded = true }
|
||||||
).background(backgroundColor)
|
)
|
||||||
|
.background(backgroundColor)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -246,6 +250,9 @@ fun NoteCompose(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (note.author != null)
|
||||||
|
ObserveDisplayNip05Status(note.author!!)
|
||||||
|
|
||||||
if (noteEvent is TextNoteEvent && (note.replyTo != null || note.mentions != null)) {
|
if (noteEvent is TextNoteEvent && (note.replyTo != null || note.mentions != null)) {
|
||||||
ReplyInformation(note.replyTo, note.mentions, account, navController)
|
ReplyInformation(note.replyTo, note.mentions, account, navController)
|
||||||
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || note.mentions != null)) {
|
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || note.mentions != null)) {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.gestures.scrollBy
|
import androidx.compose.foundation.gestures.scrollBy
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
@@ -9,10 +12,12 @@ import androidx.compose.foundation.text.ClickableText
|
|||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Bolt
|
import androidx.compose.material.icons.filled.Bolt
|
||||||
|
import androidx.compose.material.icons.filled.Downloading
|
||||||
import androidx.compose.material.icons.filled.EditNote
|
import androidx.compose.material.icons.filled.EditNote
|
||||||
import androidx.compose.material.icons.filled.Key
|
import androidx.compose.material.icons.filled.Key
|
||||||
import androidx.compose.material.icons.filled.Link
|
import androidx.compose.material.icons.filled.Link
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
import androidx.compose.material.icons.filled.Report
|
||||||
import androidx.compose.material.icons.filled.Share
|
import androidx.compose.material.icons.filled.Share
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
@@ -30,10 +35,13 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.compose.ui.platform.UriHandler
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -45,10 +53,11 @@ import com.google.accompanist.pager.ExperimentalPagerApi
|
|||||||
import com.google.accompanist.pager.HorizontalPager
|
import com.google.accompanist.pager.HorizontalPager
|
||||||
import com.google.accompanist.pager.pagerTabIndicatorOffset
|
import com.google.accompanist.pager.pagerTabIndicatorOffset
|
||||||
import com.google.accompanist.pager.rememberPagerState
|
import com.google.accompanist.pager.rememberPagerState
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.lnurl.Nip05Verifier
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.model.UserMetadata
|
||||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
||||||
@@ -61,10 +70,13 @@ import com.vitorpamplona.amethyst.ui.dal.UserProfileConversationsFeedFilter
|
|||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileZapsFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileZapsFeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.Keyboard
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.note.showAmount
|
import com.vitorpamplona.amethyst.ui.note.showAmount
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Nip05
|
||||||
|
import java.util.Date
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import nostr.postr.toNsec
|
import nostr.postr.toNsec
|
||||||
@@ -343,6 +355,45 @@ private fun ProfileHeader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun nip05VerificationAsAState(user: UserMetadata, pubkeyHex: String): State<Boolean?> {
|
||||||
|
var nip05Verified = remember { mutableStateOf<Boolean?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = user) {
|
||||||
|
user.nip05?.ifBlank { null }?.let { nip05 ->
|
||||||
|
val now = Date().time / 1000
|
||||||
|
if ((user.nip05LastVerificationTime ?: 0) > (now - 60*60)) { // 1hour
|
||||||
|
nip05Verified.value = user.nip05Verified
|
||||||
|
} else {
|
||||||
|
println("Checking NIP05 online")
|
||||||
|
Nip05Verifier().verifyNip05(
|
||||||
|
nip05,
|
||||||
|
onSuccess = {
|
||||||
|
// Marks user as verified
|
||||||
|
if (it == pubkeyHex) {
|
||||||
|
user.nip05Verified = true
|
||||||
|
user.nip05LastVerificationTime = now
|
||||||
|
nip05Verified.value = true
|
||||||
|
} else {
|
||||||
|
user.nip05Verified = false
|
||||||
|
user.nip05LastVerificationTime = 0
|
||||||
|
nip05Verified.value = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError = {
|
||||||
|
user.nip05LastVerificationTime = 0
|
||||||
|
user.nip05Verified = false
|
||||||
|
nip05Verified.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nip05Verified
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DrawAdditionalInfo(baseUser: User, account: Account) {
|
private fun DrawAdditionalInfo(baseUser: User, account: Account) {
|
||||||
val userState by baseUser.live().metadata.observeAsState()
|
val userState by baseUser.live().metadata.observeAsState()
|
||||||
@@ -350,21 +401,25 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account) {
|
|||||||
|
|
||||||
val uri = LocalUriHandler.current
|
val uri = LocalUriHandler.current
|
||||||
|
|
||||||
user.bestDisplayName()?.let {
|
Row(verticalAlignment = Alignment.Bottom) {
|
||||||
Text( "$it",
|
user.bestDisplayName()?.let {
|
||||||
modifier = Modifier.padding(top = 7.dp),
|
Text( "$it",
|
||||||
fontWeight = FontWeight.Bold,
|
modifier = Modifier.padding(top = 7.dp),
|
||||||
fontSize = 25.sp
|
fontWeight = FontWeight.Bold,
|
||||||
)
|
fontSize = 25.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
user.bestUsername()?.let {
|
||||||
|
Text(
|
||||||
|
"@$it",
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
|
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user.bestUsername()?.let {
|
DisplayNip05Status(user)
|
||||||
Text(
|
|
||||||
"@$it",
|
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
||||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val website = user.info?.website
|
val website = user.info?.website
|
||||||
if (!website.isNullOrEmpty()) {
|
if (!website.isNullOrEmpty()) {
|
||||||
@@ -402,7 +457,9 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account) {
|
|||||||
text = AnnotatedString(lud16),
|
text = AnnotatedString(lud16),
|
||||||
onClick = { ZapExpanded = !ZapExpanded },
|
onClick = { ZapExpanded = !ZapExpanded },
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
|
||||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp).weight(1f)
|
modifier = Modifier
|
||||||
|
.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
||||||
|
.weight(1f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,6 +481,104 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ObserveDisplayNip05Status(baseUser: User) {
|
||||||
|
val userState by baseUser.live().metadata.observeAsState()
|
||||||
|
val user = userState?.user ?: return
|
||||||
|
|
||||||
|
val uri = LocalUriHandler.current
|
||||||
|
|
||||||
|
user.nip05()?.let { nip05 ->
|
||||||
|
if (nip05.split("@").size == 2) {
|
||||||
|
val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(
|
||||||
|
text = AnnotatedString(nip05.split("@")[0]),
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
|
||||||
|
if (nip05Verified == null) {
|
||||||
|
Icon(
|
||||||
|
tint = Color.Yellow,
|
||||||
|
imageVector = Icons.Default.Downloading,
|
||||||
|
contentDescription = "Downloading",
|
||||||
|
modifier = Modifier.size(14.dp).padding(top = 1.dp)
|
||||||
|
)
|
||||||
|
} else if (nip05Verified == true) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_verified),
|
||||||
|
"NIP-05 Verified",
|
||||||
|
tint = Nip05.copy(0.52f),
|
||||||
|
modifier = Modifier.size(14.dp).padding(top = 1.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
tint = Color.Red,
|
||||||
|
imageVector = Icons.Default.Report,
|
||||||
|
contentDescription = "Invalid Nip05",
|
||||||
|
modifier = Modifier.size(14.dp).padding(top = 1.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ClickableText(
|
||||||
|
text = AnnotatedString(nip05.split("@")[1]),
|
||||||
|
onClick = { nip05.let { runCatching { uri.openUri("https://${it.split("@")[1]}") } } },
|
||||||
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(0.52f)),
|
||||||
|
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp),
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplayNip05Status(user: User) {
|
||||||
|
val uri = LocalUriHandler.current
|
||||||
|
|
||||||
|
user.nip05()?.let { nip05 ->
|
||||||
|
val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
if (nip05Verified == null) {
|
||||||
|
Icon(
|
||||||
|
tint = Color.Yellow,
|
||||||
|
imageVector = Icons.Default.Downloading,
|
||||||
|
contentDescription = "Downloading",
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
} else if (nip05Verified == true) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_verified),
|
||||||
|
"NIP-05 Verified",
|
||||||
|
tint = Nip05,
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
tint = Color.Red,
|
||||||
|
imageVector = Icons.Default.Report,
|
||||||
|
contentDescription = "Invalid Nip05",
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = AnnotatedString(nip05.split("@")[0] + "@"),
|
||||||
|
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
ClickableText(
|
||||||
|
text = AnnotatedString(nip05.split("@")[1]),
|
||||||
|
onClick = { nip05.let { runCatching { uri.openUri("https://${it.split("@")[1]}") } } },
|
||||||
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
|
||||||
|
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DrawBanner(baseUser: User) {
|
private fun DrawBanner(baseUser: User) {
|
||||||
val userState by baseUser.live().metadata.observeAsState()
|
val userState by baseUser.live().metadata.observeAsState()
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ val Teal200 = Color(0xFF03DAC5)
|
|||||||
val BitcoinOrange = Color (0xFFF7931A)
|
val BitcoinOrange = Color (0xFFF7931A)
|
||||||
|
|
||||||
val Following = Color(0xFF03DAC5)
|
val Following = Color(0xFF03DAC5)
|
||||||
|
val Nip05 = Color(0xFF01BAFF)
|
||||||
val FollowsFollow = Color.Yellow
|
val FollowsFollow = Color.Yellow
|
||||||
val NIP05Verified = Color.Blue
|
val NIP05Verified = Color.Blue
|
||||||
|
|||||||
Reference in New Issue
Block a user