mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 19:08:23 +00:00
Merge pull request #500 from greenart7c3/claude/relay-trusted-score-toggle-vt8j12
Add user-configurable trust score toggle in settings
This commit is contained in:
@@ -77,6 +77,7 @@ private enum class SettingsKeys(val key: String) {
|
||||
RATE_LIMIT_MAX_PER_WINDOW("rate_limit_max_per_window"),
|
||||
RATE_LIMIT_WINDOW_SECONDS("rate_limit_window_seconds"),
|
||||
PROFILE_FETCH_INTERVAL("profile_fetch_interval"),
|
||||
TRUST_SCORE_ENABLED("trust_score_enabled"),
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@@ -156,6 +157,7 @@ object LocalPreferences {
|
||||
putBoolean(SettingsKeys.RATE_LIMIT_ENABLED.key, settings.rateLimitEnabled)
|
||||
putInt(SettingsKeys.RATE_LIMIT_MAX_PER_WINDOW.key, settings.rateLimitMaxPerWindow)
|
||||
putInt(SettingsKeys.RATE_LIMIT_WINDOW_SECONDS.key, settings.rateLimitWindowSeconds)
|
||||
putBoolean(SettingsKeys.TRUST_SCORE_ENABLED.key, settings.trustScoreEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,6 +321,7 @@ object LocalPreferences {
|
||||
} catch (_: IllegalArgumentException) {
|
||||
ProfileFetchInterval.FIFTEEN_MINUTES
|
||||
},
|
||||
trustScoreEnabled = getBoolean(SettingsKeys.TRUST_SCORE_ENABLED.key, true),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ data class AmberSettings(
|
||||
val rateLimitMaxPerWindow: Int = 5,
|
||||
val rateLimitWindowSeconds: Int = 30,
|
||||
val profileFetchInterval: ProfileFetchInterval = ProfileFetchInterval.FIFTEEN_MINUTES,
|
||||
val trustScoreEnabled: Boolean = true,
|
||||
) {
|
||||
val useProxy: Boolean get() = torMode != TorMode.DISABLED
|
||||
}
|
||||
|
||||
@@ -23,12 +23,18 @@ object TrustScoreService {
|
||||
private val cache = ConcurrentHashMap<String, CachedScore>()
|
||||
private val mapper = jacksonObjectMapper()
|
||||
|
||||
/**
|
||||
* Whether trust scores should be fetched and displayed.
|
||||
* False on the offline flavor or when the user disabled trust scores in the settings.
|
||||
*/
|
||||
fun isEnabled(): Boolean = !BuildFlavorChecker.isOfflineFlavor() && Amber.instance.settings.trustScoreEnabled
|
||||
|
||||
/**
|
||||
* Gets the trust score for a relay URL.
|
||||
* Returns cached value if available and not expired, otherwise fetches from API.
|
||||
*/
|
||||
suspend fun getScore(relayUrl: String): Int? {
|
||||
if (BuildFlavorChecker.isOfflineFlavor()) {
|
||||
if (!isEnabled()) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -50,6 +56,10 @@ object TrustScoreService {
|
||||
* Returns null if not cached or expired.
|
||||
*/
|
||||
fun getCachedScore(relayUrl: String): Int? {
|
||||
if (!isEnabled()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val normalizedUrl = normalizeUrl(relayUrl)
|
||||
val cached = cache[normalizedUrl] ?: return null
|
||||
|
||||
@@ -64,7 +74,7 @@ object TrustScoreService {
|
||||
* Prefetches scores for multiple relay URLs in parallel.
|
||||
*/
|
||||
suspend fun prefetchScores(relayUrls: List<String>) {
|
||||
if (BuildFlavorChecker.isOfflineFlavor()) {
|
||||
if (!isEnabled()) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -83,7 +93,7 @@ object TrustScoreService {
|
||||
* Fetches the trust score from the API.
|
||||
*/
|
||||
private suspend fun fetchScore(relayUrl: String): Int? {
|
||||
if (BuildFlavorChecker.isOfflineFlavor()) {
|
||||
if (!isEnabled()) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.BuildFlavorChecker
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.service.RelayUrlUtils
|
||||
@@ -56,7 +55,7 @@ fun AuthWhitelistScreen(
|
||||
val loadingScores = remember { mutableStateMapOf<String, Boolean>() }
|
||||
|
||||
LaunchedEffect(whitelist.toList()) {
|
||||
if (!BuildFlavorChecker.isOfflineFlavor()) {
|
||||
if (TrustScoreService.isEnabled()) {
|
||||
whitelist.forEach { hostname ->
|
||||
if (!trustScores.containsKey(hostname)) {
|
||||
loadingScores[hostname] = true
|
||||
|
||||
@@ -74,7 +74,7 @@ fun DefaultProfileRelaysScreen(
|
||||
|
||||
// Fetch trust scores for all relays
|
||||
LaunchedEffect(relays2.toList()) {
|
||||
if (!BuildFlavorChecker.isOfflineFlavor()) {
|
||||
if (TrustScoreService.isEnabled()) {
|
||||
relays2.forEach { relay ->
|
||||
val url = relay.url
|
||||
if (!trustScores.containsKey(url)) {
|
||||
|
||||
@@ -47,7 +47,6 @@ import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.BuildFlavorChecker
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.database.ApplicationWithPermissions
|
||||
import com.greenart7c3.nostrsigner.database.generateBunkerPrivKey
|
||||
@@ -99,7 +98,7 @@ fun EditConfigurationScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (!BuildFlavorChecker.isOfflineFlavor()) {
|
||||
if (TrustScoreService.isEnabled()) {
|
||||
relays.forEach { relay ->
|
||||
val url = relay.url
|
||||
if (!trustScores.containsKey(url)) {
|
||||
|
||||
@@ -1,21 +1,55 @@
|
||||
package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.BuildFlavorChecker
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.service.TrustScoreService
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun RelaysScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
navController: NavController,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
var trustScoreEnabled by remember { mutableStateOf(Amber.instance.settings.trustScoreEnabled) }
|
||||
|
||||
fun setTrustScoreEnabled(enabled: Boolean) {
|
||||
trustScoreEnabled = enabled
|
||||
Amber.instance.settings = Amber.instance.settings.copy(trustScoreEnabled = enabled)
|
||||
if (!enabled) {
|
||||
TrustScoreService.clearCache()
|
||||
}
|
||||
scope.launch(Dispatchers.IO) {
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
@@ -33,5 +67,33 @@ fun RelaysScreen(
|
||||
navController.navigate(Route.DefaultProfileRelaysScreen.route)
|
||||
},
|
||||
)
|
||||
|
||||
if (!BuildFlavorChecker.isOfflineFlavor()) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
setTrustScoreEnabled(!trustScoreEnabled)
|
||||
}
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(text = stringResource(R.string.relay_trust_score))
|
||||
Text(
|
||||
text = stringResource(R.string.relay_trust_score_description),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Gray,
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = trustScoreEnabled,
|
||||
onCheckedChange = { enabled ->
|
||||
setTrustScoreEnabled(enabled)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ fun DefaultRelaysScreen(
|
||||
|
||||
// Fetch trust scores for all relays
|
||||
LaunchedEffect(relays2.toList()) {
|
||||
if (!BuildFlavorChecker.isOfflineFlavor()) {
|
||||
if (TrustScoreService.isEnabled()) {
|
||||
relays2.forEach { relay ->
|
||||
val url = relay.url
|
||||
if (!trustScores.containsKey(url)) {
|
||||
@@ -613,7 +613,7 @@ fun ActiveRelaysScreen(
|
||||
|
||||
// Fetch trust scores for every relay shown
|
||||
LaunchedEffect(defaultRelays.toList(), connectionRelays.toList()) {
|
||||
if (!BuildFlavorChecker.isOfflineFlavor()) {
|
||||
if (TrustScoreService.isEnabled()) {
|
||||
(defaultRelays + connectionRelays).forEach { relay ->
|
||||
val url = relay.url
|
||||
if (!trustScores.containsKey(url)) {
|
||||
|
||||
+1
-2
@@ -53,7 +53,6 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.BuildFlavorChecker
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
@@ -130,7 +129,7 @@ fun BunkerConnectRequestScreen(
|
||||
|
||||
// Fetch trust scores for connection relays
|
||||
LaunchedEffect(connectionRelays) {
|
||||
if (!BuildFlavorChecker.isOfflineFlavor() && connectionRelays.isNotEmpty()) {
|
||||
if (TrustScoreService.isEnabled() && connectionRelays.isNotEmpty()) {
|
||||
connectionRelays.forEach { relay ->
|
||||
val url = relay.url
|
||||
if (!trustScores.containsKey(url)) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.service.TrustScoreService
|
||||
|
||||
enum class TrustLevel(val color: Color) {
|
||||
EXCELLENT(Color(0xFF4CAF50)), // Green - 80-100
|
||||
@@ -47,6 +48,7 @@ fun getTrustLevelLabel(level: TrustLevel): String = when (level) {
|
||||
/**
|
||||
* A badge displaying the trust score with color-coding.
|
||||
* Shows the numeric score and optionally a label.
|
||||
* Renders nothing when trust scores are disabled in the settings or on the offline flavor.
|
||||
*/
|
||||
@Composable
|
||||
fun TrustScoreBadge(
|
||||
@@ -55,6 +57,10 @@ fun TrustScoreBadge(
|
||||
isLoading: Boolean = false,
|
||||
showLabel: Boolean = false,
|
||||
) {
|
||||
if (!TrustScoreService.isEnabled()) {
|
||||
return
|
||||
}
|
||||
|
||||
val trustLevel = getTrustLevel(score)
|
||||
|
||||
if (isLoading) {
|
||||
|
||||
@@ -667,6 +667,8 @@
|
||||
<string name="trust_fair">Fair</string>
|
||||
<string name="trust_poor">Poor</string>
|
||||
<string name="trust_unknown">Unknown</string>
|
||||
<string name="relay_trust_score">Relay trust scores</string>
|
||||
<string name="relay_trust_score_description">Fetch and display relay trust scores from trustedrelays.xyz</string>
|
||||
<string name="relays_used">Relays used</string>
|
||||
<string name="do_not_leave_the_app_until_the_key_is_generated">Do not leave the app until the key is generated</string>
|
||||
<string name="status_detail">Status Detail</string>
|
||||
|
||||
Reference in New Issue
Block a user