select a server on tap and refuse to delete the active one

This commit is contained in:
austinried
2026-10-04 00:30:03 +00:00
parent a77ce6984e
commit 46dc6bda0e
6 changed files with 23 additions and 29 deletions
@@ -20,9 +20,6 @@ interface SourcesDao {
@Query("SELECT * FROM sources WHERE id = :id")
suspend fun sourceOnce(id: Long): Source?
@Query("SELECT COUNT(*) FROM sources")
suspend fun sourceCount(): Int
@Query("SELECT id FROM sources WHERE isActive = 1 LIMIT 1")
fun activeSourceId(): Flow<Long?>
@@ -32,9 +29,6 @@ interface SourcesDao {
@Query("SELECT id FROM sources WHERE isActive = 1 LIMIT 1")
suspend fun activeSourceIdOnce(): Long?
@Query("SELECT id FROM sources ORDER BY createdAt LIMIT 1")
suspend fun firstSourceId(): Long?
@Insert
suspend fun insertSource(source: Source): Long
@@ -175,13 +175,8 @@ class SourceRepository(
}
suspend fun deleteSource(id: Long): Boolean {
val dao = db.sourcesDao()
if (dao.sourceCount() <= 1) return false
val wasActive = dao.activeSourceIdOnce() == id
dao.deleteSource(id)
if (wasActive) {
dao.firstSourceId()?.let { dao.setActiveSource(it) }
}
if (db.sourcesDao().activeSourceIdOnce() == id) return false
db.sourcesDao().deleteSource(id)
return true
}
@@ -173,7 +173,7 @@ fun AddSourceScreen(
)
if (state.isEditing && !state.canDelete) {
Text(
"This is the only server. Add another before you can remove this one.",
"This is the active server. Switch to another server before deleting it.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -59,7 +59,8 @@ class AddSourceViewModel(
}
viewModelScope.launch {
sourceRepository.sources().collect { sources ->
_state.update { it.copy(canDelete = sources.size > 1) }
val active = sources.any { it.id == sourceId && it.isActive }
_state.update { it.copy(canDelete = !active) }
}
}
}
@@ -145,7 +146,7 @@ class AddSourceViewModel(
viewModelScope.launch {
if (!sourceRepository.deleteSource(id)) {
_state.update {
it.copy(busy = false, message = "At least one server is required", isError = true)
it.copy(busy = false, message = "The active server can't be deleted; switch to another server first", isError = true)
}
return@launch
}
@@ -19,8 +19,8 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -140,9 +140,11 @@ fun SettingsScreen(
)
},
trailingContent = {
Icon(Icons.AutoMirrored.Rounded.KeyboardArrowRight, contentDescription = null)
IconButton(onClick = { onEditServer(source.id) }) {
Icon(Icons.Rounded.Edit, contentDescription = "Edit ${source.name}")
}
},
modifier = Modifier.clickable(onClickLabel = "Edit ${source.name}") { onEditServer(source.id) },
modifier = Modifier.clickable(onClickLabel = "Use ${source.name}") { onSelectSource(source.id) },
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
)
}
@@ -105,19 +105,21 @@ class SourceRepositoryTest {
}
@Test
fun theLastSourceCannotBeDeleted() =
fun theActiveSourceCannotBeDeleted() =
runTest {
repository.addSource("only", "http://a.example", "u", "p", true)
val id = repository.activeSourceId().first()!!
assertFalse("the last source must survive deletion", repository.deleteSource(id))
assertEquals(1, repository.sources().first().size)
repository.addSource("first", "http://a.example", "u", "p", true)
val firstId = repository.activeSourceId().first()!!
repository.addSource("second", "http://b.example", "u", "p", true)
val secondId = repository.activeSourceId().first()!!
assertTrue(repository.deleteSource(id))
assertEquals(listOf("second"), repository.sources().first().map { it.name })
assertEquals("second", repository.activeConfig().first()?.name)
assertFalse("the active source must survive deletion", repository.deleteSource(secondId))
assertEquals(2, repository.sources().first().size)
repository.selectSource(firstId)
assertTrue(repository.deleteSource(secondId))
assertEquals(listOf("first"), repository.sources().first().map { it.name })
assertEquals("first", repository.activeConfig().first()?.name)
}
@Test