feat(ui): add CreateGroupChatDialog for multi-bot room creation (#982)
This commit is contained in:
7 files changed
+273
No files matched your search
@@ -21,6 +21,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.GroupAdd
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
@@ -71,6 +72,7 @@ fun BotsScreen(
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
var isSearchActive by remember { mutableStateOf(false) }
|
||||
var showCreateDialog by remember { mutableStateOf(false) }
|
||||
var showCreateGroupDialog by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
if (showCreateDialog) {
|
||||
@@ -84,6 +86,18 @@ fun BotsScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (showCreateGroupDialog) {
|
||||
CreateGroupChatDialog(
|
||||
availableBots = state.profiles,
|
||||
onDismiss = { showCreateGroupDialog = false },
|
||||
onCreateGroup = { groupName, botNames ->
|
||||
viewModel.createGroupChat(groupName, botNames) {
|
||||
showCreateGroupDialog = false
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
ToastEffect(
|
||||
toastMessage = state.toastMessage,
|
||||
onClearToast = { viewModel.clearToast() },
|
||||
@@ -130,6 +144,15 @@ fun BotsScreen(
|
||||
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
|
||||
actions = {
|
||||
if (!isSearchActive) {
|
||||
IconButton(
|
||||
onClick = { showCreateGroupDialog = true },
|
||||
modifier = Modifier.testTag("bots_action_create_group"),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.GroupAdd,
|
||||
contentDescription = stringResource(R.string.bots_action_create_group),
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { showCreateDialog = true },
|
||||
modifier = Modifier.testTag("bots_action_create"),
|
||||
|
||||
@@ -34,6 +34,13 @@ data class BotsUiState(
|
||||
val hasHiddenBots: Boolean
|
||||
get() = profiles.any { it.isHidden }
|
||||
|
||||
val allGroups: List<String>
|
||||
get() =
|
||||
profiles
|
||||
.flatMap { it.botMeta()?.groups.orEmpty() }
|
||||
.distinct()
|
||||
.sorted()
|
||||
|
||||
val activeNowBots: List<ProfileInfo>
|
||||
get() {
|
||||
val nowSeconds = System.currentTimeMillis() / 1000
|
||||
@@ -181,6 +188,29 @@ class BotsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun createGroupChat(
|
||||
groupName: String,
|
||||
botNames: List<String>,
|
||||
onSuccess: () -> Unit,
|
||||
) {
|
||||
viewModelScope.launch(ioDispatcher) {
|
||||
val currentProfiles = _uiState.value.profiles
|
||||
for (name in botNames) {
|
||||
val bot = currentProfiles.find { it.name == name } ?: continue
|
||||
val existingGroups = bot.botMeta()?.groups.orEmpty()
|
||||
if (!existingGroups.contains(groupName)) {
|
||||
val updatedMeta =
|
||||
(bot.botMeta() ?: BotRosterMeta()).copy(
|
||||
groups = existingGroups + groupName,
|
||||
)
|
||||
wsClientConfigureBot(name, updatedMeta)
|
||||
}
|
||||
}
|
||||
loadBots()
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
private fun wsClientConfigureBot(
|
||||
name: String,
|
||||
meta: BotRosterMeta,
|
||||
@@ -199,6 +229,9 @@ class BotsViewModel(
|
||||
},
|
||||
)
|
||||
}
|
||||
if (!meta.groups.isNullOrEmpty()) {
|
||||
put("groups", meta.groups)
|
||||
}
|
||||
}
|
||||
|
||||
HermesWsClient.send(
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package com.m57.hermescontrol.ui.bots
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
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.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
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
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.model.ProfileInfo
|
||||
import com.m57.hermescontrol.ui.common.BotAvatar
|
||||
|
||||
@Composable
|
||||
fun CreateGroupChatDialog(
|
||||
availableBots: List<ProfileInfo>,
|
||||
onDismiss: () -> Unit,
|
||||
onCreateGroup: (groupName: String, selectedBotNames: List<String>) -> Unit,
|
||||
) {
|
||||
var groupName by remember { mutableStateOf("") }
|
||||
var selectedBots by remember { mutableStateOf(setOf<String>()) }
|
||||
|
||||
val defaultPlaceholder =
|
||||
if (selectedBots.isNotEmpty()) {
|
||||
selectedBots.joinToString(", ")
|
||||
} else {
|
||||
stringResource(R.string.bots_create_group_name_placeholder)
|
||||
}
|
||||
|
||||
val effectiveName = groupName.ifBlank { selectedBots.joinToString(", ") }
|
||||
val isValid = selectedBots.size >= 2 && effectiveName.isNotBlank()
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.bots_create_group_title),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
OutlinedTextField(
|
||||
value = groupName,
|
||||
onValueChange = { groupName = it },
|
||||
label = { Text(stringResource(R.string.bots_create_group_name_label)) },
|
||||
placeholder = { Text(defaultPlaceholder) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.bots_create_group_select_bots),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
LazyColumn(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 240.dp),
|
||||
) {
|
||||
items(availableBots, key = { it.name }) { bot ->
|
||||
val isChecked = selectedBots.contains(bot.name)
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
selectedBots =
|
||||
if (isChecked) {
|
||||
selectedBots - bot.name
|
||||
} else {
|
||||
selectedBots + bot.name
|
||||
}
|
||||
}
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Checkbox(
|
||||
checked = isChecked,
|
||||
onCheckedChange = { checked ->
|
||||
selectedBots =
|
||||
if (checked) {
|
||||
selectedBots + bot.name
|
||||
} else {
|
||||
selectedBots - bot.name
|
||||
}
|
||||
},
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
BotAvatar(
|
||||
name = bot.name,
|
||||
avatar = bot.botMeta()?.avatar,
|
||||
size = 28.dp,
|
||||
showPresence = false,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = bot.effectiveTitle,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Text(
|
||||
text = "@${bot.name}",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
if (isValid) {
|
||||
onCreateGroup(effectiveName.trim(), selectedBots.toList())
|
||||
}
|
||||
},
|
||||
enabled = isValid,
|
||||
) {
|
||||
Text(stringResource(R.string.bots_create_group_button))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user