* fix(#240): parse session_id from params root Allows correct session mapping when opening app via notification. * fix(#240): set MainActivity launchMode to singleTop Prevents activity destruction and WebSocket reconnection when tapping notifications. * fix(#240): save inline notification replies to Room database * fix(#240): refresh current session when app comes to foreground * fix(#240): use uppercase role string to match Mapper mapping expectations * style: format NotificationReplyReceiver.kt to satisfy ktlint
This commit is contained in:
6 files changed
+60
-1
No files matched your search
@@ -20,6 +20,7 @@
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:launchMode="singleTop"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -35,7 +35,8 @@ object EventParser {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val payload = params["payload"] as? Map<String, Any?>
|
||||
|
||||
val sessionId = payload?.get("session_id") as? String
|
||||
// B7 (Jun 21 2026, kanban t_240): extract session_id from params first, fallback to payload
|
||||
val sessionId = params["session_id"] as? String ?: payload?.get("session_id") as? String
|
||||
|
||||
return when (eventType) {
|
||||
"gateway.ready" -> {
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.RemoteInput
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.ws.HermesWsClient
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class NotificationReplyReceiver : BroadcastReceiver() {
|
||||
companion object {
|
||||
@@ -26,6 +27,32 @@ class NotificationReplyReceiver : BroadcastReceiver() {
|
||||
if (!replyText.isNullOrBlank() && !sessionId.isNullOrBlank()) {
|
||||
HermesWsClient.sendMessage(sessionId, replyText)
|
||||
|
||||
val pendingResult = goAsync()
|
||||
kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Dispatchers.IO).launch {
|
||||
try {
|
||||
val db =
|
||||
com.m57.hermescontrol.data.local.HermesDatabase
|
||||
.get(context)
|
||||
val dao = db.chatMessageDao()
|
||||
val entity =
|
||||
com.m57.hermescontrol.data.local.ChatMessageEntity(
|
||||
id =
|
||||
java.util.UUID
|
||||
.randomUUID()
|
||||
.toString(),
|
||||
sessionId = sessionId,
|
||||
role = "USER",
|
||||
content = replyText,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
)
|
||||
dao.upsert(entity)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("NotificationReply", "Failed to save replied message to Room DB", e)
|
||||
} finally {
|
||||
pendingResult.finish()
|
||||
}
|
||||
}
|
||||
|
||||
val repliedNotification =
|
||||
NotificationCompat
|
||||
.Builder(context, ChatNotificationService.CHAT_CHANNEL_ID)
|
||||
|
||||
@@ -169,6 +169,7 @@ fun ChatScreen(
|
||||
androidx.lifecycle.Lifecycle.Event.ON_START -> {
|
||||
NotificationHelper.setAppForeground(context, true)
|
||||
NotificationHelper.stop(context)
|
||||
viewModel.refreshCurrentSession()
|
||||
}
|
||||
|
||||
androidx.lifecycle.Lifecycle.Event.ON_STOP -> {
|
||||
|
||||
@@ -803,6 +803,12 @@ class ChatViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshCurrentSession() {
|
||||
val sessionId = _uiState.value.currentSessionId ?: return
|
||||
loadCachedMessages(sessionId)
|
||||
loadSessionMessages(sessionId)
|
||||
}
|
||||
|
||||
fun switchSession(sessionId: String) {
|
||||
if (sessionId == _uiState.value.currentSessionId) return
|
||||
|
||||
|
||||
@@ -86,6 +86,29 @@ class EventParserTest {
|
||||
assertEquals("session-1", tokenEvent.sessionId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseMessageToken_withSessionIdInParams_returnsMessageTokenEvent() {
|
||||
val response =
|
||||
JsonRpcResponse(
|
||||
jsonrpc = "2.0",
|
||||
id = null,
|
||||
result = null,
|
||||
error = null,
|
||||
method = "event",
|
||||
params =
|
||||
mapOf(
|
||||
"type" to "message.token",
|
||||
"session_id" to "session-params-1",
|
||||
"payload" to mapOf("text" to "hello"),
|
||||
),
|
||||
)
|
||||
val event = EventParser.parse(response)
|
||||
assertTrue(event is WsEvent.MessageToken)
|
||||
val tokenEvent = event as WsEvent.MessageToken
|
||||
assertEquals("hello", tokenEvent.token)
|
||||
assertEquals("session-params-1", tokenEvent.sessionId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseThinkingDelta_returnsThinkingDeltaEvent() {
|
||||
val response =
|
||||
|
||||
Reference in new issue
Block a user