Improvements to the relay settings page.
This commit is contained in:
@@ -14,4 +14,6 @@ data class RelaySetupInfo(
|
|||||||
val spamCount: Int = 0,
|
val spamCount: Int = 0,
|
||||||
val feedTypes: Set<FeedType>,
|
val feedTypes: Set<FeedType>,
|
||||||
val paidRelay: Boolean = false
|
val paidRelay: Boolean = false
|
||||||
)
|
) {
|
||||||
|
val briefInfo: RelayBriefInfo = RelayBriefInfo(url)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.LruCache
|
||||||
|
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
object Nip11CachedRetriever {
|
||||||
|
val relayInformationDocumentCache = LruCache<String, RelayInformation>(100)
|
||||||
|
val retriever = Nip11Retriever()
|
||||||
|
|
||||||
|
suspend fun loadRelayInfo(
|
||||||
|
dirtyUrl: String,
|
||||||
|
onInfo: (RelayInformation) -> Unit,
|
||||||
|
onError: (String, Nip11Retriever.ErrorCode, String?) -> Unit
|
||||||
|
) {
|
||||||
|
val url = retriever.cleanUrl(dirtyUrl)
|
||||||
|
val doc = relayInformationDocumentCache.get(url)
|
||||||
|
|
||||||
|
if (doc != null) {
|
||||||
|
onInfo(doc)
|
||||||
|
} else {
|
||||||
|
Nip11Retriever().loadRelayInfo(
|
||||||
|
url,
|
||||||
|
dirtyUrl,
|
||||||
|
onInfo = {
|
||||||
|
relayInformationDocumentCache.put(url, it)
|
||||||
|
onInfo(it)
|
||||||
|
},
|
||||||
|
onError
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Nip11Retriever {
|
||||||
|
enum class ErrorCode {
|
||||||
|
FAIL_TO_ASSEMBLE_URL,
|
||||||
|
FAIL_TO_REACH_SERVER,
|
||||||
|
FAIL_TO_PARSE_RESULT,
|
||||||
|
FAIL_WITH_HTTP_STATUS
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cleanUrl(dirtyUrl: String): String {
|
||||||
|
return if (dirtyUrl.contains("://")) {
|
||||||
|
dirtyUrl
|
||||||
|
.replace("wss://", "https://")
|
||||||
|
.replace("ws://", "http://")
|
||||||
|
} else {
|
||||||
|
"https://$dirtyUrl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadRelayInfo(
|
||||||
|
url: String,
|
||||||
|
dirtyUrl: String,
|
||||||
|
onInfo: (RelayInformation) -> Unit,
|
||||||
|
onError: (String, ErrorCode, String?) -> Unit
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
val request: Request = Request
|
||||||
|
.Builder()
|
||||||
|
.header("Accept", "application/nostr+json")
|
||||||
|
.url(url)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
HttpClient.getHttpClient()
|
||||||
|
.newCall(request)
|
||||||
|
.enqueue(
|
||||||
|
object : Callback {
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
checkNotInMainThread()
|
||||||
|
response.use {
|
||||||
|
val body = it.body.string()
|
||||||
|
try {
|
||||||
|
if (it.isSuccessful) {
|
||||||
|
onInfo(RelayInformation.fromJson(body))
|
||||||
|
} else {
|
||||||
|
onError(dirtyUrl, ErrorCode.FAIL_WITH_HTTP_STATUS, it.code.toString())
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("RelayInfoFail", "Resulting Message from Relay $dirtyUrl in not parseable: $body", e)
|
||||||
|
onError(dirtyUrl, ErrorCode.FAIL_TO_PARSE_RESULT, e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
Log.e("RelayInfoFail", "$dirtyUrl unavailable", e)
|
||||||
|
onError(dirtyUrl, ErrorCode.FAIL_TO_REACH_SERVER, e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("RelayInfoFail", "Invalid URL $dirtyUrl", e)
|
||||||
|
onError(dirtyUrl, ErrorCode.FAIL_TO_ASSEMBLE_URL, e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
||||||
|
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||||
import com.vitorpamplona.amethyst.service.relays.Constants
|
import com.vitorpamplona.amethyst.service.relays.Constants
|
||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||||
@@ -24,6 +25,7 @@ class NewRelayListViewModel : ViewModel() {
|
|||||||
fun load(account: Account) {
|
fun load(account: Account) {
|
||||||
this.account = account
|
this.account = account
|
||||||
clear()
|
clear()
|
||||||
|
loadRelayDocuments()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create() {
|
fun create() {
|
||||||
@@ -36,6 +38,21 @@ class NewRelayListViewModel : ViewModel() {
|
|||||||
clear()
|
clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadRelayDocuments() {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
_relays.value.forEach { item ->
|
||||||
|
Nip11CachedRetriever.loadRelayInfo(
|
||||||
|
dirtyUrl = item.url,
|
||||||
|
onInfo = {
|
||||||
|
togglePaidRelay(item, it.limitation?.payment_required ?: false)
|
||||||
|
},
|
||||||
|
onError = { url, errorCode, exceptionMessage ->
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
_relays.update {
|
_relays.update {
|
||||||
var relayFile = account.userProfile().latestContactList?.relays()
|
var relayFile = account.userProfile().latestContactList?.relays()
|
||||||
|
|||||||
+33
-109
@@ -1,8 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.actions
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -28,27 +25,24 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||||
import com.vitorpamplona.amethyst.service.HttpClient
|
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableEmail
|
import com.vitorpamplona.amethyst.ui.components.ClickableEmail
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadUser
|
import com.vitorpamplona.amethyst.ui.note.LoadUser
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||||
import kotlinx.coroutines.launch
|
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||||
import okhttp3.Call
|
|
||||||
import okhttp3.Callback
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun RelayInformationDialog(
|
fun RelayInformationDialog(
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
|
relayBriefInfo: RelayBriefInfo,
|
||||||
relayInfo: RelayInformation,
|
relayInfo: RelayInformation,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
@@ -79,18 +73,25 @@ fun RelayInformationDialog(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = StdPadding.fillMaxWidth()) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
Column() {
|
||||||
horizontalArrangement = Arrangement.Center
|
RenderRelayIcon(
|
||||||
) {
|
relayBriefInfo.favIcon,
|
||||||
Title(relayInfo.name ?: "")
|
Size55dp
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Row(
|
Spacer(modifier = DoubleHorzSpacer)
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.Center
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
) {
|
Row() {
|
||||||
SectionContent(relayInfo.description ?: "")
|
Title(relayInfo.name?.trim() ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
Row() {
|
||||||
|
SubtitleContent(relayInfo.description?.trim() ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(stringResource(R.string.owner))
|
Section(stringResource(R.string.owner))
|
||||||
@@ -212,7 +213,7 @@ private fun DisplaySupportedNips(relayInfo: RelayInformation) {
|
|||||||
val text = item.toString().padStart(2, '0')
|
val text = item.toString().padStart(2, '0')
|
||||||
Box(Modifier.padding(10.dp)) {
|
Box(Modifier.padding(10.dp)) {
|
||||||
ClickableUrl(
|
ClickableUrl(
|
||||||
urlText = "$text",
|
urlText = text,
|
||||||
url = "https://github.com/nostr-protocol/nips/blob/master/$text.md"
|
url = "https://github.com/nostr-protocol/nips/blob/master/$text.md"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -222,7 +223,7 @@ private fun DisplaySupportedNips(relayInfo: RelayInformation) {
|
|||||||
val text = item.padStart(2, '0')
|
val text = item.padStart(2, '0')
|
||||||
Box(Modifier.padding(10.dp)) {
|
Box(Modifier.padding(10.dp)) {
|
||||||
ClickableUrl(
|
ClickableUrl(
|
||||||
urlText = "$text",
|
urlText = text,
|
||||||
url = "https://github.com/nostr-protocol/nips/blob/master/$text.md"
|
url = "https://github.com/nostr-protocol/nips/blob/master/$text.md"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -258,13 +259,18 @@ private fun DisplayOwnerInformation(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Title(text: String) {
|
fun Title(text: String) {
|
||||||
Spacer(modifier = DoubleVertSpacer)
|
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 24.sp
|
fontSize = 24.sp
|
||||||
)
|
)
|
||||||
Spacer(modifier = DoubleVertSpacer)
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SubtitleContent(text: String) {
|
||||||
|
Text(
|
||||||
|
text = text
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -285,85 +291,3 @@ fun SectionContent(text: String) {
|
|||||||
text = text
|
text = text
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadRelayInfo(
|
|
||||||
dirtyUrl: String,
|
|
||||||
context: Context,
|
|
||||||
scope: CoroutineScope,
|
|
||||||
onInfo: (RelayInformation) -> Unit
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
val url = if (dirtyUrl.contains("://")) {
|
|
||||||
dirtyUrl
|
|
||||||
.replace("wss://", "https://")
|
|
||||||
.replace("ws://", "http://")
|
|
||||||
} else {
|
|
||||||
"https://$dirtyUrl"
|
|
||||||
}
|
|
||||||
|
|
||||||
val request: Request = Request
|
|
||||||
.Builder()
|
|
||||||
.header("Accept", "application/nostr+json")
|
|
||||||
.url(url)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
HttpClient.getHttpClient()
|
|
||||||
.newCall(request)
|
|
||||||
.enqueue(
|
|
||||||
object : Callback {
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
|
||||||
checkNotInMainThread()
|
|
||||||
response.use {
|
|
||||||
val body = it.body.string()
|
|
||||||
try {
|
|
||||||
if (it.isSuccessful) {
|
|
||||||
onInfo(RelayInformation.fromJson(body))
|
|
||||||
} else {
|
|
||||||
scope.launch {
|
|
||||||
Toast
|
|
||||||
.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("RelayInfoFail", "Resulting Message from Relay $dirtyUrl in not parseable: $body", e)
|
|
||||||
scope.launch {
|
|
||||||
Toast
|
|
||||||
.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
|
||||||
Log.e("RelayInfoFail", "$dirtyUrl unavailable", e)
|
|
||||||
scope.launch {
|
|
||||||
Toast
|
|
||||||
.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("RelayInfoFail", "Invalid URL $dirtyUrl", e)
|
|
||||||
scope.launch {
|
|
||||||
Toast
|
|
||||||
.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import androidx.compose.ui.window.DialogProperties
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||||
|
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -41,6 +42,11 @@ data class RelayList(
|
|||||||
val isSelected: Boolean
|
val isSelected: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class RelayInfoDialog(
|
||||||
|
val relayBriefInfo: RelayBriefInfo,
|
||||||
|
val relayInfo: RelayInformation
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RelaySelectionDialog(
|
fun RelaySelectionDialog(
|
||||||
preSelectedList: List<Relay>,
|
preSelectedList: List<Relay>,
|
||||||
@@ -63,16 +69,17 @@ fun RelaySelectionDialog(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
var relayInfo: RelayInformation? by remember { mutableStateOf(null) }
|
var relayInfo: RelayInfoDialog? by remember { mutableStateOf(null) }
|
||||||
|
|
||||||
if (relayInfo != null) {
|
relayInfo?.let {
|
||||||
RelayInformationDialog(
|
RelayInformationDialog(
|
||||||
onClose = {
|
onClose = {
|
||||||
relayInfo = null
|
relayInfo = null
|
||||||
},
|
},
|
||||||
relayInfo = relayInfo!!,
|
relayInfo = it.relayInfo,
|
||||||
accountViewModel,
|
relayBriefInfo = it.relayBriefInfo,
|
||||||
nav
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,9 +168,30 @@ fun RelaySelectionDialog(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongPress = {
|
onLongPress = {
|
||||||
loadRelayInfo(item.relay.url, context, scope) {
|
accountViewModel.retrieveRelayDocument(
|
||||||
relayInfo = it
|
item.relay.url,
|
||||||
}
|
onInfo = {
|
||||||
|
relayInfo = RelayInfoDialog(RelayBriefInfo(item.relay.url), it)
|
||||||
|
},
|
||||||
|
onError = { url, errorCode, exceptionMessage ->
|
||||||
|
val msg = when (errorCode) {
|
||||||
|
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||||
|
Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||||
|
Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||||
|
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
msg,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
@@ -28,14 +29,15 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||||
|
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||||
import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog
|
import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog
|
||||||
import com.vitorpamplona.amethyst.ui.actions.loadRelayInfo
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
|
import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
|
||||||
@@ -45,6 +47,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size15dp
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
@@ -112,8 +115,9 @@ fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav:
|
|||||||
relayInfo = null
|
relayInfo = null
|
||||||
},
|
},
|
||||||
relayInfo = relayInfo!!,
|
relayInfo = relayInfo!!,
|
||||||
accountViewModel,
|
relayBriefInfo = relay,
|
||||||
nav
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,9 +135,30 @@ fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav:
|
|||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
indication = ripple,
|
indication = ripple,
|
||||||
onClick = {
|
onClick = {
|
||||||
loadRelayInfo(relay.url, context, scope) {
|
accountViewModel.retrieveRelayDocument(
|
||||||
relayInfo = it
|
relay.url,
|
||||||
}
|
onInfo = {
|
||||||
|
relayInfo = it
|
||||||
|
},
|
||||||
|
onError = { url, errorCode, exceptionMessage ->
|
||||||
|
val msg = when (errorCode) {
|
||||||
|
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||||
|
Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||||
|
Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||||
|
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
msg,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -146,12 +171,12 @@ fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderRelayIcon(iconUrl: String) {
|
fun RenderRelayIcon(iconUrl: String, size: Dp = Size13dp) {
|
||||||
val backgroundColor = MaterialTheme.colors.background
|
val backgroundColor = MaterialTheme.colors.background
|
||||||
|
|
||||||
val iconModifier = remember {
|
val iconModifier = remember {
|
||||||
Modifier
|
Modifier
|
||||||
.size(Size13dp)
|
.size(size)
|
||||||
.clip(shape = CircleShape)
|
.clip(shape = CircleShape)
|
||||||
.background(backgroundColor)
|
.background(backgroundColor)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,13 @@ import com.vitorpamplona.amethyst.model.AddressableNote
|
|||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
import com.vitorpamplona.amethyst.model.ConnectivityType
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.UserState
|
import com.vitorpamplona.amethyst.model.UserState
|
||||||
import com.vitorpamplona.amethyst.service.Nip05Verifier
|
import com.vitorpamplona.amethyst.service.Nip05Verifier
|
||||||
|
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||||
|
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||||
import com.vitorpamplona.amethyst.service.OnlineChecker
|
import com.vitorpamplona.amethyst.service.OnlineChecker
|
||||||
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||||
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
||||||
@@ -551,6 +554,16 @@ class AccountViewModel(val account: Account) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun retrieveRelayDocument(
|
||||||
|
dirtyUrl: String,
|
||||||
|
onInfo: (RelayInformation) -> Unit,
|
||||||
|
onError: (String, Nip11Retriever.ErrorCode, String?) -> Unit
|
||||||
|
) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
Nip11CachedRetriever.loadRelayInfo(dirtyUrl, onInfo, onError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Factory(val account: Account) : ViewModelProvider.Factory {
|
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||||
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
|
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
|
||||||
return AccountViewModel(account) as AccountViewModel
|
return AccountViewModel(account) as AccountViewModel
|
||||||
|
|||||||
@@ -24,3 +24,9 @@ val DarkerGreen = Color.Green.copy(alpha = 0.32f)
|
|||||||
val WarningColor = Color(0xFFC62828)
|
val WarningColor = Color(0xFFC62828)
|
||||||
|
|
||||||
val RelayIconFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0.5f) })
|
val RelayIconFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0.5f) })
|
||||||
|
|
||||||
|
val LightWarningColor = Color(0xFFffcc00)
|
||||||
|
val DarkWarningColor = Color(0xFFF8DE22)
|
||||||
|
|
||||||
|
val LightAllGoodColor = Color(0xFF339900)
|
||||||
|
val DarkAllGoodColor = Color(0xFF99cc33)
|
||||||
|
|||||||
@@ -286,6 +286,12 @@ val Colors.overPictureBackground: Color
|
|||||||
val Colors.bitcoinColor: Color
|
val Colors.bitcoinColor: Color
|
||||||
get() = if (isLight) BitcoinLight else BitcoinDark
|
get() = if (isLight) BitcoinLight else BitcoinDark
|
||||||
|
|
||||||
|
val Colors.warningColor: Color
|
||||||
|
get() = if (isLight) LightWarningColor else DarkWarningColor
|
||||||
|
|
||||||
|
val Colors.allGoodColor: Color
|
||||||
|
get() = if (isLight) LightAllGoodColor else DarkAllGoodColor
|
||||||
|
|
||||||
val Colors.markdownStyle: RichTextStyle
|
val Colors.markdownStyle: RichTextStyle
|
||||||
get() = if (isLight) MarkDownStyleOnLight else MarkDownStyleOnDark
|
get() = if (isLight) MarkDownStyleOnLight else MarkDownStyleOnDark
|
||||||
|
|
||||||
|
|||||||
@@ -558,4 +558,16 @@
|
|||||||
<string name="error_dialog_zap_error">Unable to send zap</string>
|
<string name="error_dialog_zap_error">Unable to send zap</string>
|
||||||
<string name="error_dialog_talk_to_user">Message the User</string>
|
<string name="error_dialog_talk_to_user">Message the User</string>
|
||||||
<string name="error_dialog_button_ok">Ok</string>
|
<string name="error_dialog_button_ok">Ok</string>
|
||||||
|
|
||||||
|
<string name="relay_information_document_error_assemble_url">Failed to reach %1$s: %2$s</string>
|
||||||
|
<string name="relay_information_document_error_reach_server">Failed to reach %1$s: %2$s</string>
|
||||||
|
<string name="relay_information_document_error_parse_result">Failed to parse result from %1$s: %2$s</string>
|
||||||
|
<string name="relay_information_document_error_http_status">%1$s failed with code %2$s</string>
|
||||||
|
<string name="active_for">Active for: </string>
|
||||||
|
|
||||||
|
<string name="active_for_home">Home</string>
|
||||||
|
<string name="active_for_msg">DMs</string>
|
||||||
|
<string name="active_for_chats">Chats</string>
|
||||||
|
<string name="active_for_global">Global</string>
|
||||||
|
<string name="active_for_search">Search</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user