kopia lustrzana https://github.com/vitorpamplona/amethyst
created dialog to show relay information
rodzic
fbe0f584af
commit
4506ff9c93
|
@ -0,0 +1,250 @@
|
|||
package com.vitorpamplona.amethyst.model
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class RelayInformation(
|
||||
val name: String?,
|
||||
val description: String?,
|
||||
val pubkey: String?,
|
||||
val contact: String?,
|
||||
val supported_nips: List<Int>?,
|
||||
val supported_nip_extensions: List<String>?,
|
||||
val software: String?,
|
||||
val version: String?,
|
||||
val limitation: RelayInformationLimitation?,
|
||||
val relay_countries: List<String>?,
|
||||
val language_tags: List<String>?,
|
||||
val tags: List<String>?,
|
||||
val posting_policy: String?,
|
||||
val payments_url: String?,
|
||||
val fees: RelayInformationFees?,
|
||||
) {
|
||||
companion object {
|
||||
val gson: Gson = GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.registerTypeAdapter(RelayInformation::class.java, RelayInformationSerializer())
|
||||
.registerTypeAdapter(RelayInformationLimitation::class.java, RelayInformationLimitationSerializer())
|
||||
.registerTypeAdapter(RelayInformationFees::class.java, RelayInformationFeesSerializer())
|
||||
.registerTypeAdapter(RelayInformationFee::class.java, RelayInformationFeeSerializer())
|
||||
.create()
|
||||
|
||||
fun fromJson(json: String): RelayInformation = gson.fromJson(json, RelayInformation::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
class RelayInformationFee(
|
||||
val amount: Int?,
|
||||
val unit: String?,
|
||||
val period: Int?,
|
||||
val kinds: List<Int>?
|
||||
) {
|
||||
companion object {
|
||||
val gson: Gson = GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.registerTypeAdapter(RelayInformationFee::class.java, RelayInformationFeeSerializer())
|
||||
.create()
|
||||
|
||||
fun fromJson(json: String): RelayInformationFee = gson.fromJson(json, RelayInformationFee::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private class RelayInformationFeeSerializer : JsonSerializer<RelayInformationFee> {
|
||||
override fun serialize(
|
||||
src: RelayInformationFee,
|
||||
typeOfSrc: Type?,
|
||||
context: JsonSerializationContext?
|
||||
): JsonElement {
|
||||
return JsonObject().apply {
|
||||
addProperty("amount", src.amount)
|
||||
addProperty("unit", src.unit)
|
||||
addProperty("period", src.period)
|
||||
add(
|
||||
"kinds",
|
||||
JsonArray().also { kinds ->
|
||||
src.kinds?.forEach { kind ->
|
||||
kinds.add(
|
||||
kind
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RelayInformationFees(
|
||||
val admission: List<RelayInformationFee>?,
|
||||
val subscription: List<RelayInformationFee>?,
|
||||
val publication: List<RelayInformationFee>?,
|
||||
) {
|
||||
companion object {
|
||||
val gson: Gson = GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.registerTypeAdapter(RelayInformationFees::class.java, RelayInformationFeesSerializer())
|
||||
.create()
|
||||
|
||||
fun fromJson(json: String): RelayInformationFees = gson.fromJson(json, RelayInformationFees::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private class RelayInformationFeesSerializer : JsonSerializer<RelayInformationFees> {
|
||||
override fun serialize(
|
||||
src: RelayInformationFees,
|
||||
typeOfSrc: Type?,
|
||||
context: JsonSerializationContext?
|
||||
): JsonElement {
|
||||
return JsonObject().apply {
|
||||
add(
|
||||
"admission",
|
||||
JsonArray().also { admissions ->
|
||||
src.admission?.forEach { admission ->
|
||||
admissions.add(
|
||||
admission.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
add(
|
||||
"publication",
|
||||
JsonArray().also { publications ->
|
||||
src.publication?.forEach { publication ->
|
||||
publications.add(
|
||||
publication.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
add(
|
||||
"subscription",
|
||||
JsonArray().also { subscriptions ->
|
||||
src.subscription?.forEach { subscription ->
|
||||
subscriptions.add(
|
||||
subscription.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RelayInformationLimitation(
|
||||
val max_message_length: Int?,
|
||||
val max_subscriptions: Int?,
|
||||
val max_filters: Int?,
|
||||
val max_limit: Int?,
|
||||
val max_subid_length: Int?,
|
||||
val min_prefix: Int?,
|
||||
val max_event_tags: Int?,
|
||||
val max_content_length: Int?,
|
||||
val min_pow_difficulty: Int?,
|
||||
val auth_required: Boolean?,
|
||||
val payment_required: Boolean?
|
||||
) {
|
||||
companion object {
|
||||
val gson: Gson = GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.registerTypeAdapter(RelayInformationLimitation::class.java, RelayInformationLimitationSerializer())
|
||||
.create()
|
||||
|
||||
fun fromJson(json: String): RelayInformationLimitation = gson.fromJson(json, RelayInformationLimitation::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private class RelayInformationLimitationSerializer : JsonSerializer<RelayInformationLimitation> {
|
||||
override fun serialize(
|
||||
src: RelayInformationLimitation,
|
||||
typeOfSrc: Type?,
|
||||
context: JsonSerializationContext?
|
||||
): JsonElement {
|
||||
return JsonObject().apply {
|
||||
addProperty("max_message_length", src.max_message_length)
|
||||
addProperty("max_subscriptions", src.max_subscriptions)
|
||||
addProperty("max_filters", src.max_filters)
|
||||
addProperty("max_limit", src.max_limit)
|
||||
addProperty("max_subid_length", src.max_subid_length)
|
||||
addProperty("min_prefix", src.min_prefix)
|
||||
addProperty("max_event_tags", src.max_event_tags)
|
||||
addProperty("max_content_length", src.max_content_length)
|
||||
addProperty("min_pow_difficulty", src.min_pow_difficulty)
|
||||
addProperty("auth_required", src.auth_required)
|
||||
addProperty("payment_required", src.payment_required)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RelayInformationSerializer : JsonSerializer<RelayInformation> {
|
||||
override fun serialize(
|
||||
src: RelayInformation,
|
||||
typeOfSrc: Type?,
|
||||
context: JsonSerializationContext?
|
||||
): JsonElement {
|
||||
return JsonObject().apply {
|
||||
addProperty("name", src.name)
|
||||
addProperty("description", src.description)
|
||||
addProperty("pubkey", src.pubkey)
|
||||
addProperty("contact", src.contact)
|
||||
add(
|
||||
"supported_nip_extensions",
|
||||
JsonArray().also { supported_nip_extensions ->
|
||||
src.supported_nip_extensions?.forEach { nip ->
|
||||
supported_nip_extensions.add(
|
||||
nip
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
add(
|
||||
"supported_nips",
|
||||
JsonArray().also { supported_nips ->
|
||||
src.supported_nips?.forEach { nip ->
|
||||
supported_nips.add(
|
||||
nip
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addProperty("software", src.software)
|
||||
addProperty("version", src.version)
|
||||
add(
|
||||
"relay_countries",
|
||||
JsonArray().also { relay_countries ->
|
||||
src.relay_countries?.forEach { country ->
|
||||
relay_countries.add(
|
||||
country
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
add(
|
||||
"language_tags",
|
||||
JsonArray().also { language_tags ->
|
||||
src.language_tags?.forEach { language_tag ->
|
||||
language_tags.add(
|
||||
language_tag
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
add(
|
||||
"tags",
|
||||
JsonArray().also { tags ->
|
||||
src.tags?.forEach { tag ->
|
||||
tags.add(
|
||||
tag
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addProperty("posting_policy", src.posting_policy)
|
||||
addProperty("payments_url", src.payments_url)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.actions
|
|||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -53,17 +54,25 @@ import androidx.compose.ui.window.Dialog
|
|||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
||||
import com.vitorpamplona.amethyst.service.HttpClient
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import java.lang.Math.round
|
||||
|
||||
@Composable
|
||||
fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, relayToAdd: String = "") {
|
||||
fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, relayToAdd: String = "", nav: (String) -> Unit) {
|
||||
val postViewModel: NewRelayListViewModel = viewModel()
|
||||
val feedState by postViewModel.relays.collectAsState()
|
||||
|
||||
|
@ -125,7 +134,9 @@ fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, re
|
|||
onToggleGlobal = { postViewModel.toggleGlobal(it) },
|
||||
onToggleSearch = { postViewModel.toggleSearch(it) },
|
||||
|
||||
onDelete = { postViewModel.deleteRelay(it) }
|
||||
onDelete = { postViewModel.deleteRelay(it) },
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -222,10 +233,31 @@ fun ServerConfig(
|
|||
onToggleGlobal: (RelaySetupInfo) -> Unit,
|
||||
onToggleSearch: (RelaySetupInfo) -> Unit,
|
||||
|
||||
onDelete: (RelaySetupInfo) -> Unit
|
||||
onDelete: (RelaySetupInfo) -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var relayInfo: RelayInformation? by remember { mutableStateOf(null) }
|
||||
|
||||
if (relayInfo != null) {
|
||||
val user = LocalCache.getOrCreateUser(relayInfo!!.pubkey ?: "")
|
||||
NostrUserProfileDataSource.loadUserProfile(user)
|
||||
NostrUserProfileDataSource.start()
|
||||
RelayInformationDialog(
|
||||
onClose = {
|
||||
relayInfo = null
|
||||
NostrUserProfileDataSource.loadUserProfile(null)
|
||||
NostrUserProfileDataSource.stop()
|
||||
},
|
||||
relayInfo = relayInfo!!,
|
||||
user,
|
||||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
}
|
||||
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
@ -251,7 +283,55 @@ fun ServerConfig(
|
|||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = item.url.removePrefix("wss://"),
|
||||
modifier = Modifier.weight(1f),
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.clickable {
|
||||
val client = HttpClient.getHttpClient()
|
||||
val url = item.url
|
||||
.replace("wss://", "https://")
|
||||
.replace("ws://", "http://")
|
||||
val request: Request = Request
|
||||
.Builder()
|
||||
.header("Accept", "application/nostr+json")
|
||||
.url(url)
|
||||
.build()
|
||||
client
|
||||
.newCall(request)
|
||||
.enqueue(object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
response.use {
|
||||
if (it.isSuccessful) {
|
||||
relayInfo =
|
||||
RelayInformation.fromJson(it.body.string())
|
||||
} else {
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
"An error ocurred trying to get relay information",
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: java.io.IOException) {
|
||||
e.printStackTrace()
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
"An error ocurred trying to get relay information",
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
@ -275,11 +355,13 @@ fun ServerConfig(
|
|||
onClick = { onToggleFollows(item) },
|
||||
onLongClick = {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.home_feed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.home_feed),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
),
|
||||
|
@ -306,11 +388,13 @@ fun ServerConfig(
|
|||
onClick = { onTogglePrivateDMs(item) },
|
||||
onLongClick = {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.private_message_feed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.private_message_feed),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
),
|
||||
|
@ -337,11 +421,13 @@ fun ServerConfig(
|
|||
onClick = { onTogglePublicChats(item) },
|
||||
onLongClick = {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.public_chat_feed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.public_chat_feed),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
),
|
||||
|
@ -368,11 +454,13 @@ fun ServerConfig(
|
|||
onClick = { onToggleGlobal(item) },
|
||||
onLongClick = {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.global_feed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.global_feed),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
),
|
||||
|
@ -400,11 +488,13 @@ fun ServerConfig(
|
|||
onClick = { onToggleSearch(item) },
|
||||
onLongClick = {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.search_feed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.search_feed),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
),
|
||||
|
@ -436,11 +526,13 @@ fun ServerConfig(
|
|||
onClick = { onToggleDownload(item) },
|
||||
onLongClick = {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.read_from_relay),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.read_from_relay),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
),
|
||||
|
@ -476,11 +568,13 @@ fun ServerConfig(
|
|||
onClick = { onToggleUpload(item) },
|
||||
onLongClick = {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.write_to_relay),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.write_to_relay),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
),
|
||||
|
@ -512,11 +606,13 @@ fun ServerConfig(
|
|||
onClick = { },
|
||||
onLongClick = {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.errors),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.errors),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
),
|
||||
|
@ -541,11 +637,13 @@ fun ServerConfig(
|
|||
onClick = { },
|
||||
onLongClick = {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.spam),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.spam),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
),
|
||||
|
|
|
@ -0,0 +1,365 @@
|
|||
package com.vitorpamplona.amethyst.ui.actions
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Downloading
|
||||
import androidx.compose.material.icons.filled.Report
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableEmail
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.nip05VerificationAsAState
|
||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DisplayLNAddress
|
||||
import com.vitorpamplona.amethyst.ui.theme.Nip05
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun Section(text: String) {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Text(
|
||||
text = text,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 25.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SectionContent(text: String) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 10.dp),
|
||||
text = text
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun RelayInformationDialog(onClose: () -> Unit, relayInfo: RelayInformation, baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val userState by baseUser.live().metadata.observeAsState()
|
||||
val user = remember(userState) { userState?.user } ?: return
|
||||
val tags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() }
|
||||
val lud16 = remember(userState) { user.info?.lud16?.trim() ?: user.info?.lud06?.trim() }
|
||||
val pubkeyHex = remember { baseUser.pubkeyHex }
|
||||
val uri = LocalUriHandler.current
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = { onClose() },
|
||||
properties = DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
dismissOnClickOutside = false
|
||||
)
|
||||
) {
|
||||
Surface {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
CloseButton(onCancel = {
|
||||
onClose()
|
||||
})
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Section(relayInfo.name ?: "")
|
||||
}
|
||||
|
||||
SectionContent(relayInfo.description ?: "")
|
||||
|
||||
Section("Owner")
|
||||
|
||||
Row {
|
||||
UserPicture(
|
||||
baseUser = user,
|
||||
accountViewModel = accountViewModel,
|
||||
size = 100.dp,
|
||||
modifier = Modifier.border(
|
||||
3.dp,
|
||||
MaterialTheme.colors.background,
|
||||
CircleShape
|
||||
),
|
||||
onClick = {
|
||||
nav("User/${user.pubkeyHex}")
|
||||
}
|
||||
)
|
||||
|
||||
Column(Modifier.padding(start = 10.dp)) {
|
||||
(user.bestDisplayName() ?: user.bestUsername())?.let {
|
||||
Row(verticalAlignment = Alignment.Bottom, modifier = Modifier.padding(top = 7.dp)) {
|
||||
CreateTextWithEmoji(
|
||||
text = it,
|
||||
tags = tags,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 25.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (user.bestDisplayName() != null) {
|
||||
user.bestUsername()?.let {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp)
|
||||
) {
|
||||
CreateTextWithEmoji(
|
||||
text = "@$it",
|
||||
tags = tags,
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user.nip05()?.let { nip05 ->
|
||||
if (nip05.split("@").size == 2) {
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
var domainPadStart = 5.dp
|
||||
|
||||
if (nip05.split("@")[0] != "_") {
|
||||
Text(
|
||||
text = AnnotatedString(nip05.split("@")[0] + "@"),
|
||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
domainPadStart = 0.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, start = domainPadStart),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisplayLNAddress(lud16, pubkeyHex, accountViewModel.account)
|
||||
|
||||
user.info?.about?.let {
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)
|
||||
) {
|
||||
val defaultBackground = MaterialTheme.colors.background
|
||||
val background = remember {
|
||||
mutableStateOf(defaultBackground)
|
||||
}
|
||||
|
||||
TranslatableRichTextViewer(
|
||||
content = it,
|
||||
canPreview = false,
|
||||
tags = remember { ImmutableListOfLists(emptyList()) },
|
||||
backgroundColor = background,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section("Software")
|
||||
|
||||
val url = (relayInfo.software ?: "").replace("git+", "")
|
||||
Box(modifier = Modifier.padding(start = 10.dp)) {
|
||||
ClickableUrl(
|
||||
urlText = url,
|
||||
url = url
|
||||
)
|
||||
}
|
||||
|
||||
Section("Version")
|
||||
|
||||
SectionContent(relayInfo.version ?: "")
|
||||
|
||||
Section("Contact")
|
||||
|
||||
Box(modifier = Modifier.padding(start = 10.dp)) {
|
||||
ClickableEmail(relayInfo.contact ?: "")
|
||||
}
|
||||
|
||||
Section("Supports")
|
||||
|
||||
FlowRow {
|
||||
relayInfo.supported_nips?.forEach { item ->
|
||||
val text = item.toString().padStart(2, '0')
|
||||
Box(Modifier.padding(10.dp)) {
|
||||
ClickableUrl(
|
||||
urlText = "Nip-$text",
|
||||
url = "https://github.com/nostr-protocol/nips/blob/master/$text.md"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
relayInfo.supported_nip_extensions?.forEach { item ->
|
||||
val text = item.padStart(2, '0')
|
||||
Box(Modifier.padding(10.dp)) {
|
||||
ClickableUrl(
|
||||
urlText = "Nip-$text",
|
||||
url = "https://github.com/nostr-protocol/nips/blob/master/$text.md"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relayInfo.fees?.admission?.let {
|
||||
if (it.isNotEmpty()) {
|
||||
Section("Admission Fees")
|
||||
|
||||
it.forEach { item ->
|
||||
SectionContent("${item.amount?.div(1000) ?: 0} sats")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relayInfo.payments_url?.let {
|
||||
Section("Payments url")
|
||||
|
||||
Box(modifier = Modifier.padding(start = 10.dp)) {
|
||||
ClickableUrl(
|
||||
urlText = it,
|
||||
url = it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
relayInfo.limitation?.let {
|
||||
Section("Limitations")
|
||||
|
||||
Column {
|
||||
SectionContent("Message length: ${it.max_message_length ?: 0}")
|
||||
SectionContent("Subscriptions: ${it.max_subscriptions ?: 0}")
|
||||
SectionContent("Filters: ${it.max_subscriptions ?: 0}")
|
||||
SectionContent("Subscription id length: ${it.max_subid_length ?: 0}")
|
||||
SectionContent("Minimum prefix: ${it.min_prefix ?: 0}")
|
||||
SectionContent("Maximum event tags: ${it.max_event_tags ?: 0}")
|
||||
SectionContent("Content length: ${it.max_content_length ?: 0}")
|
||||
SectionContent("Minimum PoW: ${it.min_pow_difficulty ?: 0}")
|
||||
SectionContent("Auth: ${it.auth_required ?: false}")
|
||||
SectionContent("Payment: ${it.payment_required ?: false}")
|
||||
}
|
||||
}
|
||||
|
||||
relayInfo.relay_countries?.let {
|
||||
Section("Countries")
|
||||
|
||||
FlowRow {
|
||||
it.forEach { item ->
|
||||
SectionContent(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relayInfo.language_tags?.let {
|
||||
Section("Languages")
|
||||
|
||||
FlowRow {
|
||||
it.forEach { item ->
|
||||
SectionContent(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relayInfo.tags?.let {
|
||||
Section("Tags")
|
||||
|
||||
FlowRow {
|
||||
it.forEach { item ->
|
||||
SectionContent(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relayInfo.posting_policy?.let {
|
||||
Section("Posting policy")
|
||||
|
||||
Box(Modifier.padding(10.dp)) {
|
||||
ClickableUrl(
|
||||
it,
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -96,7 +96,8 @@ fun AppTopBar(
|
|||
followLists: FollowListViewModel,
|
||||
navEntryState: State<NavBackStackEntry?>,
|
||||
scaffoldState: ScaffoldState,
|
||||
accountViewModel: AccountViewModel
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
val currentRoute by remember(navEntryState.value) {
|
||||
derivedStateOf {
|
||||
|
@ -104,7 +105,7 @@ fun AppTopBar(
|
|||
}
|
||||
}
|
||||
|
||||
RenderTopRouteBar(currentRoute, followLists, scaffoldState, accountViewModel)
|
||||
RenderTopRouteBar(currentRoute, followLists, scaffoldState, accountViewModel, nav)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -112,16 +113,17 @@ private fun RenderTopRouteBar(
|
|||
currentRoute: String?,
|
||||
followLists: FollowListViewModel,
|
||||
scaffoldState: ScaffoldState,
|
||||
accountViewModel: AccountViewModel
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
when (currentRoute) {
|
||||
Route.Channel.base -> NoTopBar()
|
||||
Route.Room.base -> NoTopBar()
|
||||
// Route.Profile.route -> TopBarWithBackButton(nav)
|
||||
Route.Home.base -> HomeTopBar(followLists, scaffoldState, accountViewModel)
|
||||
Route.Video.base -> StoriesTopBar(followLists, scaffoldState, accountViewModel)
|
||||
Route.Notification.base -> NotificationTopBar(followLists, scaffoldState, accountViewModel)
|
||||
else -> MainTopBar(scaffoldState, accountViewModel)
|
||||
Route.Home.base -> HomeTopBar(followLists, scaffoldState, accountViewModel, nav)
|
||||
Route.Video.base -> StoriesTopBar(followLists, scaffoldState, accountViewModel, nav)
|
||||
Route.Notification.base -> NotificationTopBar(followLists, scaffoldState, accountViewModel, nav)
|
||||
else -> MainTopBar(scaffoldState, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,8 +153,8 @@ fun StoriesTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun HomeTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
|
||||
GenericTopBar(scaffoldState, accountViewModel) { accountViewModel ->
|
||||
fun HomeTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
GenericTopBar(scaffoldState, accountViewModel, nav) { accountViewModel ->
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
|
||||
val list by remember(accountState) {
|
||||
|
@ -172,8 +174,8 @@ fun HomeTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, a
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun NotificationTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
|
||||
GenericTopBar(scaffoldState, accountViewModel) { accountViewModel ->
|
||||
fun NotificationTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
GenericTopBar(scaffoldState, accountViewModel, nav) { accountViewModel ->
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
|
||||
val list by remember(accountState) {
|
||||
|
@ -193,15 +195,15 @@ fun NotificationTopBar(followLists: FollowListViewModel, scaffoldState: Scaffold
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
|
||||
GenericTopBar(scaffoldState, accountViewModel) {
|
||||
fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
GenericTopBar(scaffoldState, accountViewModel, nav) {
|
||||
AmethystIcon()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(coil.annotation.ExperimentalCoilApi::class)
|
||||
@Composable
|
||||
fun GenericTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel, content: @Composable (AccountViewModel) -> Unit) {
|
||||
fun GenericTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel, nav: (String) -> Unit, content: @Composable (AccountViewModel) -> Unit) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var wantsToEditRelays by remember {
|
||||
|
@ -209,7 +211,7 @@ fun GenericTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewMod
|
|||
}
|
||||
|
||||
if (wantsToEditRelays) {
|
||||
NewRelayListView({ wantsToEditRelays = false }, accountViewModel)
|
||||
NewRelayListView({ wantsToEditRelays = false }, accountViewModel, nav = nav)
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.height(50.dp)) {
|
||||
|
|
|
@ -1200,7 +1200,7 @@ fun DisplayRelaySet(
|
|||
)
|
||||
|
||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||
RelayOptionsAction(relay, accountViewModel)
|
||||
RelayOptionsAction(relay, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1235,7 +1235,8 @@ fun DisplayRelaySet(
|
|||
@Composable
|
||||
private fun RelayOptionsAction(
|
||||
relay: String,
|
||||
accountViewModel: AccountViewModel
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
val userStateRelayInfo by accountViewModel.account.userProfile().live().relayInfo.observeAsState()
|
||||
val isCurrentlyOnTheUsersList by remember(userStateRelayInfo) {
|
||||
|
@ -1249,7 +1250,7 @@ private fun RelayOptionsAction(
|
|||
}
|
||||
|
||||
if (wantsToAddRelay.isNotEmpty()) {
|
||||
NewRelayListView({ wantsToAddRelay = "" }, accountViewModel, wantsToAddRelay)
|
||||
NewRelayListView({ wantsToAddRelay = "" }, accountViewModel, wantsToAddRelay, nav = nav)
|
||||
}
|
||||
|
||||
if (isCurrentlyOnTheUsersList) {
|
||||
|
|
|
@ -98,6 +98,7 @@ class RelayFeedViewModel : ViewModel() {
|
|||
fun RelayFeedView(
|
||||
viewModel: RelayFeedViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
enablePullRefresh: Boolean = true
|
||||
) {
|
||||
val feedState by viewModel.feedContent.collectAsState()
|
||||
|
@ -107,7 +108,7 @@ fun RelayFeedView(
|
|||
}
|
||||
|
||||
if (wantsToAddRelay.isNotEmpty()) {
|
||||
NewRelayListView({ wantsToAddRelay = "" }, accountViewModel, wantsToAddRelay)
|
||||
NewRelayListView({ wantsToAddRelay = "" }, accountViewModel, wantsToAddRelay, nav = nav)
|
||||
}
|
||||
|
||||
var refreshing by remember { mutableStateOf(false) }
|
||||
|
|
|
@ -173,7 +173,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
|
|||
AppBottomBar(accountViewModel, navState, navBottomRow)
|
||||
},
|
||||
topBar = {
|
||||
AppTopBar(followLists, navState, scaffoldState, accountViewModel)
|
||||
AppTopBar(followLists, navState, scaffoldState, accountViewModel, nav = nav)
|
||||
},
|
||||
drawerContent = {
|
||||
DrawerContent(nav, scaffoldState, sheetState, accountViewModel)
|
||||
|
|
|
@ -315,7 +315,7 @@ private fun CreateAndRenderPages(
|
|||
4 -> TabReceivedZaps(baseUser, zapFeedViewModel, accountViewModel, nav)
|
||||
5 -> TabBookmarks(baseUser, accountViewModel, nav)
|
||||
6 -> TabReports(baseUser, accountViewModel, nav)
|
||||
7 -> TabRelays(baseUser, accountViewModel)
|
||||
7 -> TabRelays(baseUser, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -782,7 +782,7 @@ private fun DrawAdditionalInfo(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun DisplayLNAddress(
|
||||
fun DisplayLNAddress(
|
||||
lud16: String?,
|
||||
userHex: String,
|
||||
account: Account
|
||||
|
@ -1287,7 +1287,7 @@ private fun WatchReportsAndUpdateFeed(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun TabRelays(user: User, accountViewModel: AccountViewModel) {
|
||||
fun TabRelays(user: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val feedViewModel: RelayFeedViewModel = viewModel()
|
||||
|
||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||
|
@ -1316,7 +1316,7 @@ fun TabRelays(user: User, accountViewModel: AccountViewModel) {
|
|||
Column(
|
||||
modifier = Modifier.padding(vertical = 0.dp)
|
||||
) {
|
||||
RelayFeedView(feedViewModel, accountViewModel, enablePullRefresh = false)
|
||||
RelayFeedView(feedViewModel, accountViewModel, enablePullRefresh = false, nav = nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue