refactor(ui): remove LOCAL tab and feed relay-picker entry

RelayScreen drops the LOCAL tab and its LocalRelayTab composable.
FeedScreen's RelayPickerDialog no longer surfaces a "My Local
Relay" row, and its localRelayUrl parameter is removed.
This commit is contained in:
Barry Deen
2026-05-15 11:36:25 -04:00
parent 94445d3086
commit 77ec550571
2 changed files with 40 additions and 261 deletions
@@ -34,7 +34,6 @@ import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material3.AlertDialog
@@ -292,7 +291,6 @@ fun FeedScreen(
val newNotesButtonHidden by viewModel.newNotesButtonHidden.collectAsState()
val initLoadingState by viewModel.initLoadingState.collectAsState()
val relayFeedStatus by viewModel.relayFeedStatus.collectAsState()
val localRelayConfig by viewModel.keyRepo.localRelayFlow.collectAsState()
val pendingFirstFollow by viewModel.pendingFirstFollow.collectAsState()
val firstFollowCheckDone by viewModel.firstFollowCheckDone.collectAsState()
val zapInProgress by viewModel.zapInProgress.collectAsState()
@@ -466,7 +464,6 @@ fun FeedScreen(
favoriteRelays = favoriteRelays,
relaySets = ownRelaySets,
relayInfoRepo = viewModel.relayInfoRepo,
localRelayUrl = localRelayConfig?.let { if (it.enabled) it.url else null },
onSelect = { url ->
viewModel.setSelectedRelay(url)
viewModel.setFeedType(FeedType.RELAY)
@@ -1670,7 +1667,6 @@ private fun RelayPickerDialog(
favoriteRelays: List<String>,
relaySets: List<RelaySet>,
relayInfoRepo: RelayInfoRepository,
localRelayUrl: String? = null,
onSelect: (String) -> Unit,
onSelectRelaySet: (RelaySet) -> Unit,
onCreateRelaySet: (String) -> Unit,
@@ -1751,44 +1747,6 @@ private fun RelayPickerDialog(
Spacer(Modifier.size(12.dp))
LazyColumn(modifier = Modifier.heightIn(max = 400.dp)) {
// Local relay — always first when configured
if (localRelayUrl != null) {
item(key = "local-relay") {
Surface(
onClick = { onSelect(localRelayUrl) },
shape = RoundedCornerShape(8.dp),
color = MaterialTheme.colorScheme.surfaceVariant,
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)
) {
Row(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Filled.Home,
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(Modifier.width(8.dp))
Column {
Text(
stringResource(R.string.local_relay_my_relay),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurface
)
Text(
localRelayUrl.removePrefix("ws://").removePrefix("wss://").removeSuffix("/"),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
}
// Favorites section
if (favoriteRelays.isNotEmpty()) {
item {
@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
@@ -23,35 +22,25 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Switch
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import com.wisp.app.relay.LocalRelayConfig
import com.wisp.app.relay.LocalRelayWritePolicy
import com.wisp.app.relay.RelayConfig
import com.wisp.app.relay.RelayPool
import com.wisp.app.relay.RelaySetType
import com.wisp.app.R
import com.wisp.app.ui.theme.wispSwitchColors
import com.wisp.app.viewmodel.RelayViewModel
@OptIn(ExperimentalMaterial3Api::class)
@@ -69,7 +58,6 @@ fun RelayScreen(
val searchRelays by viewModel.searchRelays.collectAsState()
val blockedRelays by viewModel.blockedRelays.collectAsState()
val newRelayUrl by viewModel.newRelayUrl.collectAsState()
val localRelayConfig by viewModel.localRelay.collectAsState()
val tabs = RelaySetType.entries
@@ -108,81 +96,53 @@ fun RelayScreen(
}
Column(modifier = Modifier.padding(16.dp)) {
if (selectedTab == RelaySetType.LOCAL) {
// Local tab: show URL input only when no relay is configured
if (localRelayConfig == null) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
value = newRelayUrl,
onValueChange = { viewModel.updateNewRelayUrl(it) },
label = { Text(stringResource(R.string.local_relay_url_hint)) },
singleLine = true,
modifier = Modifier.weight(1f)
)
Spacer(Modifier.width(8.dp))
IconButton(onClick = { viewModel.addRelay() }) {
Icon(Icons.Default.Add, stringResource(R.string.cd_add_relay))
}
}
Spacer(Modifier.height(8.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
value = newRelayUrl,
onValueChange = { viewModel.updateNewRelayUrl(it) },
label = { Text(stringResource(R.string.placeholder_relay_url)) },
singleLine = true,
modifier = Modifier.weight(1f)
)
Spacer(Modifier.width(8.dp))
IconButton(onClick = { viewModel.addRelay() }) {
Icon(Icons.Default.Add, stringResource(R.string.cd_add_relay))
}
}
LocalRelayTab(localRelayConfig, viewModel)
} else {
// Network relay tabs: URL input + broadcast
Row(
verticalAlignment = Alignment.CenterVertically,
Spacer(Modifier.height(8.dp))
if (relayPool != null) {
val buttonLabel = when (selectedTab) {
RelaySetType.GENERAL -> stringResource(R.string.broadcast_nip65)
RelaySetType.DM -> stringResource(R.string.broadcast_dm_relays)
RelaySetType.SEARCH -> stringResource(R.string.broadcast_search_relays)
RelaySetType.BLOCKED -> stringResource(R.string.broadcast_blocked_relays)
}
val successMsg = stringResource(R.string.error_relay_broadcast)
val failureMsg = stringResource(R.string.error_broadcast_failed)
Button(
onClick = {
val ok = viewModel.publishRelayList(relayPool, signer = signer)
val msg = if (ok) successMsg else failureMsg
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
value = newRelayUrl,
onValueChange = { viewModel.updateNewRelayUrl(it) },
label = { Text(stringResource(R.string.placeholder_relay_url)) },
singleLine = true,
modifier = Modifier.weight(1f)
)
Spacer(Modifier.width(8.dp))
IconButton(onClick = { viewModel.addRelay() }) {
Icon(Icons.Default.Add, stringResource(R.string.cd_add_relay))
}
Text(buttonLabel)
}
}
Spacer(Modifier.height(8.dp))
Spacer(Modifier.height(16.dp))
if (relayPool != null) {
val buttonLabel = when (selectedTab) {
RelaySetType.GENERAL -> stringResource(R.string.broadcast_nip65)
RelaySetType.DM -> stringResource(R.string.broadcast_dm_relays)
RelaySetType.SEARCH -> stringResource(R.string.broadcast_search_relays)
RelaySetType.BLOCKED -> stringResource(R.string.broadcast_blocked_relays)
RelaySetType.LOCAL -> "" // unreachable
}
val successMsg = stringResource(R.string.error_relay_broadcast)
val failureMsg = stringResource(R.string.error_broadcast_failed)
Button(
onClick = {
val ok = viewModel.publishRelayList(relayPool, signer = signer)
val msg = if (ok) successMsg else failureMsg
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
) {
Text(buttonLabel)
}
}
Spacer(Modifier.height(16.dp))
when (selectedTab) {
RelaySetType.GENERAL -> GeneralRelayList(relays, viewModel)
RelaySetType.DM -> SimpleRelayList(dmRelays, viewModel)
RelaySetType.SEARCH -> SimpleRelayList(searchRelays, viewModel)
RelaySetType.BLOCKED -> SimpleRelayList(blockedRelays, viewModel)
RelaySetType.LOCAL -> {} // handled above
}
when (selectedTab) {
RelaySetType.GENERAL -> GeneralRelayList(relays, viewModel)
RelaySetType.DM -> SimpleRelayList(dmRelays, viewModel)
RelaySetType.SEARCH -> SimpleRelayList(searchRelays, viewModel)
RelaySetType.BLOCKED -> SimpleRelayList(blockedRelays, viewModel)
}
}
}
@@ -235,145 +195,6 @@ private fun GeneralRelayList(relays: List<RelayConfig>, viewModel: RelayViewMode
}
}
@Composable
private fun LocalRelayTab(config: LocalRelayConfig?, viewModel: RelayViewModel) {
if (config == null) return
Column {
// URL + enabled toggle
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = config.url,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface
)
}
Switch(
checked = config.enabled,
onCheckedChange = { viewModel.toggleLocalRelayEnabled() },
colors = wispSwitchColors()
)
}
Spacer(Modifier.height(12.dp))
// Write policy selector
Text(
text = stringResource(R.string.local_relay_write_policy),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
LocalRelayWritePolicy.entries.forEach { policy ->
Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton(
selected = config.writePolicy == policy,
onClick = { viewModel.updateLocalRelayPolicy(policy) }
)
Text(
text = when (policy) {
LocalRelayWritePolicy.OWN_NOTES -> stringResource(R.string.local_relay_own_notes)
LocalRelayWritePolicy.TAGGED -> stringResource(R.string.local_relay_tagged)
LocalRelayWritePolicy.ALL_NOTES -> stringResource(R.string.local_relay_all_notes)
},
style = MaterialTheme.typography.bodyMedium
)
}
}
Spacer(Modifier.height(12.dp))
// Kind chips
Text(
text = stringResource(R.string.local_relay_kinds),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(Modifier.height(4.dp))
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
FilterChip(
selected = 1 in config.kinds,
onClick = {
val updated = if (1 in config.kinds) config.kinds - 1 else config.kinds + 1
viewModel.updateLocalRelayKinds(updated)
},
label = { Text(stringResource(R.string.local_relay_kind_notes)) }
)
FilterChip(
selected = 1059 in config.kinds,
onClick = {
val updated = if (1059 in config.kinds) config.kinds - 1059 else config.kinds + 1059
viewModel.updateLocalRelayKinds(updated)
},
label = { Text(stringResource(R.string.local_relay_kind_dms)) }
)
FilterChip(
selected = 9735 in config.kinds,
onClick = {
val updated = if (9735 in config.kinds) config.kinds - 9735 else config.kinds + 9735
viewModel.updateLocalRelayKinds(updated)
},
label = { Text(stringResource(R.string.local_relay_kind_zaps)) }
)
}
// Custom kind input
var customKindInput by remember { mutableStateOf("") }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = 4.dp)
) {
OutlinedTextField(
value = customKindInput,
onValueChange = { customKindInput = it.filter { c -> c.isDigit() } },
label = { Text(stringResource(R.string.local_relay_add_kind)) },
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.weight(1f)
)
Spacer(Modifier.width(8.dp))
IconButton(onClick = {
val kind = customKindInput.toIntOrNull()
if (kind != null && kind !in config.kinds) {
viewModel.updateLocalRelayKinds(config.kinds + kind)
customKindInput = ""
}
}) {
Icon(Icons.Default.Add, stringResource(R.string.cd_add_relay))
}
}
// Show custom kinds as removable chips
val customKinds = config.kinds - setOf(1, 1059, 9735)
if (customKinds.isNotEmpty()) {
Spacer(Modifier.height(4.dp))
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
customKinds.forEach { kind ->
FilterChip(
selected = true,
onClick = { viewModel.updateLocalRelayKinds(config.kinds - kind) },
label = { Text("Kind $kind") }
)
}
}
}
Spacer(Modifier.height(16.dp))
TextButton(
onClick = { viewModel.removeRelay(config.url) },
modifier = Modifier.fillMaxWidth()
) {
Text(
stringResource(R.string.local_relay_remove),
color = MaterialTheme.colorScheme.error
)
}
}
}
@Composable
private fun SimpleRelayList(urls: List<String>, viewModel: RelayViewModel) {
LazyColumn {