diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt b/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt index e55e512a16b0..3e729551adf3 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt @@ -1,5 +1,6 @@ package ai.openclaw.app +import ai.openclaw.app.i18n.nativeLocaleChanges import ai.openclaw.app.i18n.nativeString import android.Manifest import android.app.Notification @@ -21,6 +22,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -88,36 +90,41 @@ class NodeForegroundService : Service() { // can update without restarting runtime-owned connection work. notificationJob = scope.launch { - combine( + val notificationStates = combine( - runtime.gatewayConnectionDisplay, - runtime.serverName, - runtime.voiceCaptureMode, - runtime.locationMode, - ) { connection, server, mode, _ -> - VoiceNotificationBase( - status = connection.statusText, - server = server, - connected = connection.isConnected, - mode = mode, - ) - }, - combine( - runtime.micEnabled, - runtime.micIsListening, - runtime.talkModeListening, - runtime.talkModeSpeaking, - ) { micEnabled, micListening, talkListening, talkSpeaking -> - VoiceNotificationCapture( - micEnabled = micEnabled, - micListening = micListening, - talkListening = talkListening, - talkSpeaking = talkSpeaking, - ) - }, - ) { base, capture -> - VoiceNotificationState(base = base, capture = capture) - }.collect { state -> + combine( + runtime.gatewayConnectionDisplay, + runtime.serverName, + runtime.voiceCaptureMode, + runtime.locationMode, + ) { connection, server, mode, _ -> + VoiceNotificationBase( + status = connection.statusText, + server = server, + connected = connection.isConnected, + mode = mode, + ) + }, + combine( + runtime.micEnabled, + runtime.micIsListening, + runtime.talkModeListening, + runtime.talkModeSpeaking, + ) { micEnabled, micListening, talkListening, talkSpeaking -> + VoiceNotificationCapture( + micEnabled = micEnabled, + micListening = micListening, + talkListening = talkListening, + talkSpeaking = talkSpeaking, + ) + }, + ) { base, capture -> + VoiceNotificationState(base = base, capture = capture) + } + refreshNotificationOnLocaleChanges( + states = notificationStates, + localeChanges = nativeLocaleChanges, + ).collect { state -> voiceCaptureMode = state.mode val title = when { @@ -413,3 +420,9 @@ private data class VoiceNotificationState( val mode: VoiceCaptureMode get() = base.mode } + +/** Re-emits stable runtime state when app-owned notification copy changes locale. */ +internal fun refreshNotificationOnLocaleChanges( + states: Flow, + localeChanges: Flow, +): Flow = combine(states, localeChanges) { state, _ -> state } diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt index d851a4b7a0ab..9cc92e6318c5 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt @@ -5997,31 +5997,11 @@ class NodeRuntime private constructor( val body = raw.substringAfter("", raw).substringBefore("") return body .split(Regex("\\n---\\n")) - .mapNotNull(::parseDreamDiaryEntry) + .mapNotNull(::parseGatewayDreamDiaryEntry) .asReversed() .take(4) } - private fun parseDreamDiaryEntry(block: String): GatewayDreamDiaryEntry? { - val lines = block.trim().lines() - val date = - lines - .firstOrNull { line -> - val trimmed = line.trim() - trimmed.length > 2 && trimmed.startsWith("*") && trimmed.endsWith("*") - }?.trim() - ?.trim('*') - ?.takeIf { it.isNotEmpty() } - val text = - lines - .map { it.trim() } - .filter { line -> line.isNotEmpty() && !line.startsWith("#") && !line.startsWith("