feat: auto-publish key package on MarmotGroupListScreen entry

Replace the manual KeyPackage publish button with a LaunchedEffect
that auto-publishes if no key package exists yet. The subscription
already correctly downloads our own kind:30443 via
ownKeyPackageFilter() on home relays, and rotation is handled
reactively when Welcome messages are processed.

https://claude.ai/code/session_01LhfCp8DHqNVx6mSiYfpQny
This commit is contained in:
Claude
2026-04-08 01:03:18 +00:00
parent f6538d4c59
commit 6fe2852011
@@ -36,8 +36,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.VpnKey
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
@@ -45,8 +43,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -54,12 +50,10 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -71,8 +65,6 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.HexKey
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -81,10 +73,6 @@ fun MarmotGroupListScreen(
nav: INav, nav: INav,
) { ) {
var groupList by remember { mutableStateOf(listOf<Pair<HexKey, MarmotGroupChatroom>>()) } var groupList by remember { mutableStateOf(listOf<Pair<HexKey, MarmotGroupChatroom>>()) }
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
var isPublishing by remember { mutableStateOf(false) }
var hasPublishedKeyPackage by remember { mutableStateOf(accountViewModel.hasPublishedKeyPackage()) }
// Load group list // Load group list
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -98,6 +86,17 @@ fun MarmotGroupListScreen(
} }
} }
// Auto-publish KeyPackage if none exists yet
LaunchedEffect(Unit) {
if (!accountViewModel.hasPublishedKeyPackage()) {
try {
accountViewModel.publishMarmotKeyPackage()
} catch (_: Exception) {
// Silently retry on next screen visit
}
}
}
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -110,47 +109,8 @@ fun MarmotGroupListScreen(
} }
}, },
title = { Text("Marmot Groups") }, title = { Text("Marmot Groups") },
actions = {
if (isPublishing) {
CircularProgressIndicator(
modifier = Modifier.padding(12.dp).size(24.dp),
strokeWidth = 2.dp,
strokeCap = StrokeCap.Round,
)
} else {
IconButton(
onClick = {
isPublishing = true
scope.launch(Dispatchers.IO) {
try {
accountViewModel.publishMarmotKeyPackage()
hasPublishedKeyPackage = true
snackbarHostState.showSnackbar("KeyPackage published successfully")
} catch (e: Exception) {
snackbarHostState.showSnackbar("Failed to publish KeyPackage: ${e.message}")
} finally {
isPublishing = false
}
}
},
) {
Icon(
imageVector = Icons.Default.VpnKey,
contentDescription =
if (hasPublishedKeyPackage) "Republish KeyPackage" else "Publish KeyPackage",
tint =
if (hasPublishedKeyPackage) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.onSurfaceVariant
},
)
}
}
},
) )
}, },
snackbarHost = { SnackbarHost(snackbarHostState) },
floatingActionButton = { floatingActionButton = {
FloatingActionButton(onClick = { nav.nav(Route.CreateMarmotGroup) }, shape = CircleShape) { FloatingActionButton(onClick = { nav.nav(Route.CreateMarmotGroup) }, shape = CircleShape) {
Icon(Icons.Default.Add, contentDescription = "Create Group") Icon(Icons.Default.Add, contentDescription = "Create Group")
@@ -168,12 +128,7 @@ fun MarmotGroupListScreen(
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
) )
Text( Text(
text = text = "Create a group or wait for an invitation.",
if (hasPublishedKeyPackage) {
"Your KeyPackage is published. Waiting for group invitations."
} else {
"Tap the key icon above to publish your KeyPackage and receive invitations."
},
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 4.dp), modifier = Modifier.padding(top = 4.dp),