refactor: convert Marmot dialogs to full route screens
Replace AlertDialog-based CreateGroupDialog and AddMemberDialog with proper full-screen routes that slide up from the bottom, following the existing app patterns (ChannelMetadataEdit, NewGroupDM, etc.). New screens: - CreateGroupScreen: uses CreatingTopBar with close/create buttons, replaces CreateGroupDialog - AddMemberScreen: uses ActionTopBar with close/add buttons, replaces AddMemberDialog New routes: - Route.CreateMarmotGroup → composableFromBottom - Route.MarmotGroupAddMember(nostrGroupId) → composableFromBottomArgs Updated call sites: - MarmotGroupListScreen: FAB navigates to Route.CreateMarmotGroup - MarmotGroupChatScreen: add-member icon navigates to Route.MarmotGroupAddMember - MarmotGroupInfoScreen: add-member icon navigates to Route.MarmotGroupAddMember Deleted old files: - CreateGroupDialog.kt - AddMemberDialog.kt https://claude.ai/code/session_0194SxKfAU61PY92eqP4cCXM
This commit is contained in:
@@ -71,6 +71,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.list.metadat
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.membershipManagement.ArticleBookmarkListManagementScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.membershipManagement.PostBookmarkListManagementScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.old.OldBookmarkListScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.AddMemberScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.CreateGroupScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.MarmotGroupChatScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.MarmotGroupInfoScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.MarmotGroupListScreen
|
||||
@@ -289,6 +291,9 @@ fun BuildNavigation(
|
||||
composableFromEndArgs<Route.MarmotGroupChat> { MarmotGroupChatScreen(it.nostrGroupId, accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.MarmotGroupInfo> { MarmotGroupInfoScreen(it.nostrGroupId, accountViewModel, nav) }
|
||||
|
||||
composableFromBottom<Route.CreateMarmotGroup> { CreateGroupScreen(accountViewModel, nav) }
|
||||
composableFromBottomArgs<Route.MarmotGroupAddMember> { AddMemberScreen(it.nostrGroupId, accountViewModel, nav) }
|
||||
|
||||
composableFromEndArgs<Route.PublicChatChannel> {
|
||||
PublicChatChannelScreen(it.id, it.draftId, it.replyTo, accountViewModel, nav)
|
||||
}
|
||||
|
||||
@@ -273,6 +273,12 @@ sealed class Route {
|
||||
val nostrGroupId: String,
|
||||
) : Route()
|
||||
|
||||
@Serializable object CreateMarmotGroup : Route()
|
||||
|
||||
@Serializable data class MarmotGroupAddMember(
|
||||
val nostrGroupId: String,
|
||||
) : Route()
|
||||
|
||||
@Serializable data class NewGroupDM(
|
||||
val message: String? = null,
|
||||
val attachment: String? = null,
|
||||
|
||||
+65
-56
@@ -21,13 +21,14 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -36,6 +37,8 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.ActionTopBar
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
@@ -45,55 +48,22 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun AddMemberDialog(
|
||||
fun AddMemberScreen(
|
||||
nostrGroupId: HexKey,
|
||||
accountViewModel: AccountViewModel,
|
||||
onDismiss: () -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var memberInput by remember { mutableStateOf("") }
|
||||
var statusMessage by remember { mutableStateOf<String?>(null) }
|
||||
var isAdding by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Add Member") },
|
||||
text = {
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
value = memberInput,
|
||||
onValueChange = {
|
||||
memberInput = it
|
||||
statusMessage = null
|
||||
},
|
||||
label = { Text("npub or hex pubkey") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
)
|
||||
Text(
|
||||
"Enter the member's npub or hex public key. " +
|
||||
"They must have published a KeyPackage (kind:30443) to be added.",
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
if (statusMessage != null) {
|
||||
Text(
|
||||
text = statusMessage!!,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color =
|
||||
if (statusMessage!!.startsWith("Error")) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
MaterialTheme.colorScheme.primary
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
ActionTopBar(
|
||||
postRes = com.vitorpamplona.amethyst.R.string.add,
|
||||
onCancel = { nav.popBack() },
|
||||
onPost = {
|
||||
isAdding = true
|
||||
statusMessage = "Looking up KeyPackage..."
|
||||
scope.launch(Dispatchers.IO) {
|
||||
@@ -106,8 +76,8 @@ fun AddMemberDialog(
|
||||
}
|
||||
statusMessage = "Fetching KeyPackage for ${pubkey.take(8)}..."
|
||||
// TODO: Fetch KeyPackage from relays and call addMarmotGroupMember
|
||||
// For now, show that the flow would proceed here
|
||||
statusMessage = "Error: KeyPackage fetch not yet implemented. " +
|
||||
statusMessage =
|
||||
"Error: KeyPackage fetch not yet implemented. " +
|
||||
"The member's KeyPackage (kind:30443) must be fetched from relays."
|
||||
isAdding = false
|
||||
} catch (e: Exception) {
|
||||
@@ -116,28 +86,67 @@ fun AddMemberDialog(
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = !isAdding && memberInput.isNotBlank(),
|
||||
) {
|
||||
Text(if (isAdding) "Adding..." else "Add")
|
||||
}
|
||||
isActive = { !isAdding && memberInput.isNotBlank() },
|
||||
)
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("Cancel")
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.imePadding()
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "Add Member",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp),
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = memberInput,
|
||||
onValueChange = {
|
||||
memberInput = it
|
||||
statusMessage = null
|
||||
},
|
||||
label = { Text("npub or hex pubkey") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
Text(
|
||||
"Enter the member's npub or hex public key. " +
|
||||
"They must have published a KeyPackage (kind:30443) to be added.",
|
||||
modifier = Modifier.padding(top = 12.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
||||
if (statusMessage != null) {
|
||||
Text(
|
||||
text = statusMessage!!,
|
||||
modifier = Modifier.padding(top = 12.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color =
|
||||
if (statusMessage!!.startsWith("Error")) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
MaterialTheme.colorScheme.primary
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolvePubkey(input: String): HexKey? {
|
||||
val trimmed = input.trim()
|
||||
|
||||
// Try hex pubkey
|
||||
if (trimmed.length == 64 && trimmed.all { it in '0'..'9' || it in 'a'..'f' }) {
|
||||
return trimmed
|
||||
}
|
||||
|
||||
// Try bech32 (npub / nprofile)
|
||||
val entity =
|
||||
Nip19Parser.uriToRoute("nostr:$trimmed")?.entity
|
||||
?: Nip19Parser.uriToRoute(trimmed)?.entity
|
||||
+48
-40
@@ -21,12 +21,14 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -35,66 +37,72 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.CreatingTopBar
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.random.Random
|
||||
|
||||
@Composable
|
||||
fun CreateGroupDialog(
|
||||
fun CreateGroupScreen(
|
||||
accountViewModel: AccountViewModel,
|
||||
onDismiss: () -> Unit,
|
||||
onGroupCreated: (HexKey) -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var groupName by remember { mutableStateOf("") }
|
||||
var isCreating by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Create Marmot Group") },
|
||||
text = {
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
value = groupName,
|
||||
onValueChange = { groupName = it },
|
||||
label = { Text("Group Name") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
)
|
||||
Text(
|
||||
"A new MLS group will be created. You can add members after.",
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
style = androidx.compose.material3.MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
CreatingTopBar(
|
||||
onCancel = { nav.popBack() },
|
||||
onPost = {
|
||||
isCreating = true
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val nostrGroupId = Random.nextBytes(32).joinToString("") { "%02x".format(it) }
|
||||
accountViewModel.createMarmotGroup(nostrGroupId)
|
||||
// Set display name locally
|
||||
if (groupName.isNotBlank()) {
|
||||
accountViewModel.account.marmotGroupList
|
||||
.getOrCreateGroup(nostrGroupId)
|
||||
.displayName.value = groupName
|
||||
}
|
||||
onGroupCreated(nostrGroupId)
|
||||
nav.nav(Route.MarmotGroupChat(nostrGroupId))
|
||||
}
|
||||
},
|
||||
enabled = !isCreating,
|
||||
) {
|
||||
Text(if (isCreating) "Creating..." else "Create")
|
||||
}
|
||||
isActive = { !isCreating },
|
||||
)
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("Cancel")
|
||||
}
|
||||
},
|
||||
)
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.imePadding()
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "Create Marmot Group",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp),
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = groupName,
|
||||
onValueChange = { groupName = it },
|
||||
label = { Text("Group Name") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
Text(
|
||||
"A new MLS group will be created. You can add members after.",
|
||||
modifier = Modifier.padding(top = 12.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-13
@@ -31,13 +31,12 @@ import androidx.compose.material.icons.filled.GroupAdd
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||
@@ -59,7 +58,6 @@ fun MarmotGroupChatScreen(
|
||||
}
|
||||
val displayName by chatroom.displayName.collectAsStateWithLifecycle()
|
||||
val memberCount by chatroom.memberCount.collectAsStateWithLifecycle()
|
||||
var showAddMemberDialog by remember { mutableStateOf(false) }
|
||||
|
||||
DisappearingScaffold(
|
||||
isInvertedLayout = true,
|
||||
@@ -84,13 +82,13 @@ fun MarmotGroupChatScreen(
|
||||
if (memberCount > 0) {
|
||||
Text(
|
||||
text = "$memberCount members",
|
||||
style = androidx.compose.material3.MaterialTheme.typography.bodySmall,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { showAddMemberDialog = true }) {
|
||||
IconButton(onClick = { nav.nav(Route.MarmotGroupAddMember(nostrGroupId)) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.GroupAdd,
|
||||
contentDescription = "Add Member",
|
||||
@@ -109,12 +107,4 @@ fun MarmotGroupChatScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showAddMemberDialog) {
|
||||
AddMemberDialog(
|
||||
nostrGroupId = nostrGroupId,
|
||||
accountViewModel = accountViewModel,
|
||||
onDismiss = { showAddMemberDialog = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-10
@@ -80,7 +80,6 @@ fun MarmotGroupInfoScreen(
|
||||
val displayName by chatroom.displayName.collectAsStateWithLifecycle()
|
||||
var members by remember { mutableStateOf(emptyList<GroupMemberInfo>()) }
|
||||
var showLeaveDialog by remember { mutableStateOf(false) }
|
||||
var showAddMemberDialog by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val myPubkey = accountViewModel.account.signer.pubKey
|
||||
|
||||
@@ -102,7 +101,7 @@ fun MarmotGroupInfoScreen(
|
||||
},
|
||||
title = { Text("Group Info") },
|
||||
actions = {
|
||||
IconButton(onClick = { showAddMemberDialog = true }) {
|
||||
IconButton(onClick = { nav.nav(Route.MarmotGroupAddMember(nostrGroupId)) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.GroupAdd,
|
||||
contentDescription = "Add Member",
|
||||
@@ -218,14 +217,6 @@ fun MarmotGroupInfoScreen(
|
||||
onDismiss = { showLeaveDialog = false },
|
||||
)
|
||||
}
|
||||
|
||||
if (showAddMemberDialog) {
|
||||
AddMemberDialog(
|
||||
nostrGroupId = nostrGroupId,
|
||||
accountViewModel = accountViewModel,
|
||||
onDismiss = { showAddMemberDialog = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
+1
-13
@@ -70,7 +70,6 @@ fun MarmotGroupListScreen(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
var showCreateGroupDialog by remember { mutableStateOf(false) }
|
||||
var groupList by remember { mutableStateOf(listOf<Pair<HexKey, MarmotGroupChatroom>>()) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
@@ -115,7 +114,7 @@ fun MarmotGroupListScreen(
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(onClick = { showCreateGroupDialog = true }) {
|
||||
FloatingActionButton(onClick = { nav.nav(Route.CreateMarmotGroup) }) {
|
||||
Icon(Icons.Default.Add, contentDescription = "Create Group")
|
||||
}
|
||||
},
|
||||
@@ -155,17 +154,6 @@ fun MarmotGroupListScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showCreateGroupDialog) {
|
||||
CreateGroupDialog(
|
||||
accountViewModel = accountViewModel,
|
||||
onDismiss = { showCreateGroupDialog = false },
|
||||
onGroupCreated = { groupId ->
|
||||
showCreateGroupDialog = false
|
||||
nav.nav(Route.MarmotGroupChat(groupId))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadGroupList(
|
||||
|
||||
Reference in New Issue
Block a user