diff --git a/app/src/main/java/com/m57/hermescontrol/Navigation.kt b/app/src/main/java/com/m57/hermescontrol/Navigation.kt index feb8bb8c..9641c574 100644 --- a/app/src/main/java/com/m57/hermescontrol/Navigation.kt +++ b/app/src/main/java/com/m57/hermescontrol/Navigation.kt @@ -26,7 +26,6 @@ import androidx.compose.material.icons.filled.Code import androidx.compose.material.icons.filled.Dashboard import androidx.compose.material.icons.filled.Devices import androidx.compose.material.icons.filled.Extension -import androidx.compose.material.icons.filled.Forum import androidx.compose.material.icons.filled.HistoryEdu import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Key @@ -83,7 +82,6 @@ import com.m57.hermescontrol.ui.model.ModelScreen as ModelScreenContent import com.m57.hermescontrol.ui.pairing.PairingScreen as PairingScreenContent import com.m57.hermescontrol.ui.plugins.PluginsScreen as PluginsScreenContent import com.m57.hermescontrol.ui.profiles.ProfilesScreen as ProfilesScreenContent -import com.m57.hermescontrol.ui.sessions.SessionsScreen as SessionsScreenContent import com.m57.hermescontrol.ui.settings.SettingsScreen as SettingsScreenContent import com.m57.hermescontrol.ui.skills.SkillsScreen as SkillsScreenContent import com.m57.hermescontrol.ui.system.SystemScreen as SystemScreenContent @@ -105,7 +103,6 @@ private val ALL_NAV_ITEMS: List = BottomNavItem(SkillsScreen, "Skills", Icons.Filled.Extension), BottomNavItem(CronJobsScreen, "Cron", Icons.Filled.Schedule), BottomNavItem(SystemScreen, "System", Icons.Filled.Info), - BottomNavItem(SessionsScreen, "Sessions", Icons.Filled.Forum), BottomNavItem(SettingsScreen, "Settings", Icons.Filled.Settings), BottomNavItem(ProfilesScreen, "Profiles", Icons.Filled.AccountCircle), BottomNavItem(WebhooksScreen, "Webhooks", Icons.Filled.Webhook), @@ -166,7 +163,6 @@ private val DRAWER_ENTRIES = DrawerEntry(KeysScreen, "Keys", Icons.Filled.Key, DrawerSection.CONFIGURE), DrawerEntry(ChannelsScreen, "Channels", Icons.AutoMirrored.Filled.ListAlt, DrawerSection.CONFIGURE), // Inspect - DrawerEntry(SessionsScreen, "Sessions", Icons.Filled.Forum, DrawerSection.INSPECT), DrawerEntry(LogsScreen, "Logs", Icons.Filled.HistoryEdu, DrawerSection.INSPECT), DrawerEntry(KanbanScreen, "Kanban", Icons.Filled.Dashboard, DrawerSection.INSPECT), DrawerEntry(AchievementsScreen, "Achievements", Icons.Filled.Info, DrawerSection.INSPECT), @@ -446,16 +442,6 @@ fun MainNavigation() { ) } - entry { - SessionsScreenContent( - onOpenDrawer = { openDrawer() }, - onSwitchToSession = { sessionId, sessionTitle -> - backStack.removeAll { it == SessionsScreen } - NavigationController.navigateTo(ChatScreen) - }, - ) - } - entry { KanbanScreenContent( onOpenDrawer = { openDrawer() }, diff --git a/app/src/main/java/com/m57/hermescontrol/NavigationKeys.kt b/app/src/main/java/com/m57/hermescontrol/NavigationKeys.kt index ad41e4e5..00ef45e7 100644 --- a/app/src/main/java/com/m57/hermescontrol/NavigationKeys.kt +++ b/app/src/main/java/com/m57/hermescontrol/NavigationKeys.kt @@ -41,6 +41,4 @@ import kotlinx.serialization.Serializable @Serializable data object SystemScreen : NavKey -@Serializable data object SessionsScreen : NavKey - @Serializable data object KanbanScreen : NavKey diff --git a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt deleted file mode 100644 index dd5bdef9..00000000 --- a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt +++ /dev/null @@ -1,189 +0,0 @@ -package com.m57.hermescontrol.ui.sessions - -import android.widget.Toast -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel -import com.m57.hermescontrol.data.model.SessionInfo -import com.m57.hermescontrol.theme.LocalSpacing -import com.m57.hermescontrol.ui.common.EmptyState -import com.m57.hermescontrol.ui.common.ErrorState -import com.m57.hermescontrol.ui.common.HermesScaffold -import com.m57.hermescontrol.ui.common.LoadingState -import com.m57.hermescontrol.ui.common.StatusBadge -import com.m57.hermescontrol.ui.common.StatusBadgeType -import com.m57.hermescontrol.ui.common.listContentPadding -import com.m57.hermescontrol.ui.common.listItemSpacing - -@Composable -fun SessionsScreen( - modifier: Modifier = Modifier, - onOpenDrawer: (() -> Unit)? = null, - onSwitchToSession: ((sessionId: String, sessionTitle: String?) -> Unit)? = null, - viewModel: SessionsViewModel = viewModel { SessionsViewModel() }, -) { - val state by viewModel.uiState.collectAsStateWithLifecycle() - val context = LocalContext.current - val spacing = LocalSpacing.current - - LaunchedEffect(Unit) { - viewModel.loadSessions() - } - - LaunchedEffect(state.toastMessage) { - state.toastMessage?.let { msg -> - Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() - viewModel.clearToast() - } - } - - HermesScaffold( - title = { Text("Sessions") }, - onOpenDrawer = onOpenDrawer, - isRefreshing = state.isLoading, - onRefresh = { viewModel.loadSessions() }, - ) { paddingValues -> - when { - state.isLoading && state.sessions.isEmpty() -> { - LoadingState(modifier = Modifier.padding(paddingValues)) - } - state.errorMessage != null -> { - ErrorState( - message = state.errorMessage ?: "", - onRetry = { viewModel.loadSessions() }, - modifier = Modifier.padding(paddingValues), - ) - } - state.sessions.isEmpty() -> { - EmptyState( - title = "No sessions yet", - subtitle = "Start a conversation to create a session.", - modifier = Modifier.padding(paddingValues), - ) - } - else -> { - LazyColumn( - modifier = - modifier - .fillMaxSize() - .padding(paddingValues), - contentPadding = listContentPadding, - verticalArrangement = listItemSpacing, - ) { - items(state.sessions, key = { it.id }) { session -> - SessionCard( - session = session, - onClick = { - onSwitchToSession?.invoke(session.id, session.title) - }, - ) - } - } - } - } - } -} - -@Composable -private fun SessionCard( - session: SessionInfo, - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - Card( - onClick = onClick, - modifier = modifier.fillMaxWidth(), - colors = - CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceContainer, - ), - ) { - Row( - modifier = - Modifier - .fillMaxWidth() - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - Column(modifier = Modifier.weight(1f)) { - // Title - Text( - text = session.title ?: "Untitled Session", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Spacer(modifier = Modifier.height(4.dp)) - // ID + status - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = session.id.take(12) + "...", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f), - ) - session.status?.let { status -> - StatusBadge( - text = status, - status = - when (status.lowercase()) { - "active" -> StatusBadgeType.SUCCESS - "paused" -> StatusBadgeType.WARNING - else -> StatusBadgeType.NEUTRAL - }, - ) - } - } - Spacer(modifier = Modifier.height(4.dp)) - // Metadata row - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - session.message_count?.let { count -> - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = "$count messages", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } - session.created_at?.let { date -> - Text( - text = date.take(10), // YYYY-MM-DD - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f), - ) - } - } - } - } - } -} diff --git a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsViewModel.kt deleted file mode 100644 index a74d5de3..00000000 --- a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsViewModel.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.m57.hermescontrol.ui.sessions - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.m57.hermescontrol.data.model.SessionInfo -import com.m57.hermescontrol.data.remote.ApiClient -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext - -data class SessionsUiState( - val isLoading: Boolean = false, - val sessions: List = emptyList(), - val errorMessage: String? = null, - val toastMessage: String? = null, -) - -class SessionsViewModel : ViewModel() { - private val _uiState = MutableStateFlow(SessionsUiState()) - val uiState: StateFlow = _uiState.asStateFlow() - - fun loadSessions() { - _uiState.update { it.copy(isLoading = true, errorMessage = null) } - viewModelScope.launch { - try { - val response = - withContext(Dispatchers.IO) { - ApiClient.hermesApi.getSessions() - } - if (response.isSuccessful) { - _uiState.update { - it.copy( - isLoading = false, - sessions = response.body()?.sessions.orEmpty(), - ) - } - } else { - _uiState.update { - it.copy( - isLoading = false, - errorMessage = "Failed to load sessions: HTTP ${response.code()}", - ) - } - } - } catch (e: Exception) { - _uiState.update { - it.copy( - isLoading = false, - errorMessage = "Failed to load sessions: ${e.message}", - ) - } - } - } - } - - fun deleteSession(id: String) { - val originalSessions = _uiState.value.sessions - // Optimistic removal - _uiState.update { state -> - state.copy( - sessions = state.sessions.filter { it.id != id }, - ) - } - viewModelScope.launch { - try { - // The Hermes API uses PATCH to switch session, not delete. - // Session deletion is not directly available in the current - // API surface, so we optimistically remove from the list - // and reload to reconcile. - loadSessions() - } catch (e: Exception) { - _uiState.update { - it.copy( - sessions = originalSessions, - toastMessage = "Failed to remove session: ${e.message}", - ) - } - } - } - } - - fun clearToast() { - _uiState.update { it.copy(toastMessage = null) } - } -} diff --git a/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt index d8f7360d..9c07d5a5 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt @@ -78,7 +78,6 @@ class SettingsViewModel : ViewModel() { "SkillsScreen" to "Skills", "CronJobsScreen" to "Cron Jobs", "SystemScreen" to "System", - "SessionsScreen" to "Sessions", "SettingsScreen" to "Settings", "ProfilesScreen" to "Profiles", "WebhooksScreen" to "Webhooks",