consola: tap en la notificación abre la sesión exacta
El evento trae shuma_session (el ULID que el daemon siembra en el PTY). La notificación lo lleva como extra; MainActivity (singleTask + onNewIntent) lo enruta a ConsolaViewModel.openFromNotification, que se adjunta a ese terminal exacto. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
3 archivos · +59 −6
diff --git a/consola/src/main/java/com/rizoma/consola/ConsolaViewModel.kt b/consola/src/main/java/com/rizoma/consola/ConsolaViewModel.kt
index 5b88c2e..a30ea06 100644
--- a/consola/src/main/java/com/rizoma/consola/ConsolaViewModel.kt
+++ b/consola/src/main/java/com/rizoma/consola/ConsolaViewModel.kt
@@ -155,6 +155,30 @@ class ConsolaViewModel(app: Application) : AndroidViewModel(app) {
}
}
+ /**
+ * Abre la sesión exacta referida por una notificación (tap). Si ya tenemos
+ * su metadata en la lista, se adjunta con ella; si no, se adjunta por id
+ * (el daemon solo necesita el ULID) y refresca la lista en paralelo.
+ */
+ fun openFromNotification(sessionId: String) {
+ val existing = sessions.find { it.id == sessionId }
+ if (existing != null) {
+ attach(existing)
+ return
+ }
+ startTerminal { svc ->
+ svc.openTerminal(
+ session = sessionId,
+ program = "",
+ args = emptyList(),
+ cwd = ".",
+ label = "claude",
+ title = "claude",
+ )
+ }
+ refresh()
+ }
+
/** Se adjunta a una sesión existente. */
fun attach(session: PtySession) {
startTerminal { svc ->
diff --git a/consola/src/main/java/com/rizoma/consola/MainActivity.kt b/consola/src/main/java/com/rizoma/consola/MainActivity.kt
index c9b762a..3404b04 100644
--- a/consola/src/main/java/com/rizoma/consola/MainActivity.kt
+++ b/consola/src/main/java/com/rizoma/consola/MainActivity.kt
@@ -1,12 +1,14 @@
package com.rizoma.consola
import android.Manifest
+import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
import com.rizoma.consola.ui.ConsolaApp
@@ -14,6 +16,10 @@ import com.rizoma.consola.ui.theme.ConsolaTheme
class MainActivity : ComponentActivity() {
+ // Mismo VM que crea ConsolaApp (ambos resuelven al store de la Activity),
+ // para poder enrutar el deep-link de la notificación a la sesión exacta.
+ private val vm: ConsolaViewModel by viewModels()
+
// Permiso para mostrar la notificación persistente del servicio (Android 13+).
private val pedirNotificaciones =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { /* opcional: la sesión sigue viva sin ella */ }
@@ -22,17 +28,34 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
pedirPermisoNotificaciones()
+ abrirSesionDeNotificacion(intent)
setContent {
ConsolaTheme {
- ConsolaApp()
+ ConsolaApp(vm)
}
}
}
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ setIntent(intent)
+ abrirSesionDeNotificacion(intent)
+ }
+
+ /** Si la notificación trae una sesión, se adjunta a ese terminal. */
+ private fun abrirSesionDeNotificacion(intent: Intent?) {
+ val id = intent?.getStringExtra(EXTRA_SESSION)?.takeIf { it.isNotBlank() } ?: return
+ vm.openFromNotification(id)
+ }
+
private fun pedirPermisoNotificaciones() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
val concedido = ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==
PackageManager.PERMISSION_GRANTED
if (!concedido) pedirNotificaciones.launch(Manifest.permission.POST_NOTIFICATIONS)
}
+
+ companion object {
+ const val EXTRA_SESSION = "session_id"
+ }
}
diff --git a/consola/src/main/java/com/rizoma/consola/service/ConsolaService.kt b/consola/src/main/java/com/rizoma/consola/service/ConsolaService.kt
index c0f9bec..245a503 100644
--- a/consola/src/main/java/com/rizoma/consola/service/ConsolaService.kt
+++ b/consola/src/main/java/com/rizoma/consola/service/ConsolaService.kt
@@ -169,6 +169,7 @@ class ConsolaService : Service() {
val kind = o.optString("kind")
val cwd = o.optString("cwd")
val msg = o.optString("message")
+ val session = o.optString("shuma_session").ifBlank { null }
val dir = cwd.substringAfterLast('/').ifBlank { cwd }
val (title, text) = when (kind) {
"Notification" ->
@@ -178,15 +179,20 @@ class ConsolaService : Service() {
else ->
kind.ifBlank { "Claude" } to msg.ifBlank { dir }
}
- notifyAlert(title, text)
+ notifyAlert(title, text, session)
}
- private fun notifyAlert(title: String, text: String) {
+ private fun notifyAlert(title: String, text: String, sessionId: String?) {
val mgr = getSystemService(NotificationManager::class.java)
+ // Al tocar: abre la app y, si el evento trae la sesión exacta, se
+ // adjunta a ESE terminal (SHUMA_SESSION sembrado por el daemon).
+ val intent = Intent(this, MainActivity::class.java)
+ .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ if (sessionId != null) intent.putExtra(MainActivity.EXTRA_SESSION, sessionId)
val open = PendingIntent.getActivity(
- this, 2,
- Intent(this, MainActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
- PendingIntent.FLAG_IMMUTABLE,
+ this, sessionId?.hashCode() ?: 2,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
val n = NotificationCompat.Builder(this, ALERT_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_consola)