@@ -22,6 +22,7 @@ import ai.openclaw.app.ui.design.ClawTextBadge
2222import ai.openclaw.app.ui.design.ClawTextField
2323import ai.openclaw.app.ui.design.ClawTheme
2424import androidx.compose.foundation.BorderStroke
25+ import androidx.compose.foundation.background
2526import androidx.compose.foundation.layout.Arrangement
2627import androidx.compose.foundation.layout.Box
2728import androidx.compose.foundation.layout.Column
@@ -35,20 +36,24 @@ import androidx.compose.foundation.layout.padding
3536import androidx.compose.foundation.layout.size
3637import androidx.compose.foundation.lazy.LazyColumn
3738import androidx.compose.foundation.shape.CircleShape
39+ import androidx.compose.foundation.shape.RoundedCornerShape
3840import androidx.compose.material.icons.Icons
3941import androidx.compose.material.icons.automirrored.filled.ArrowBack
42+ import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
4043import androidx.compose.material.icons.automirrored.filled.ScreenShare
4144import androidx.compose.material.icons.automirrored.filled.VolumeUp
4245import androidx.compose.material.icons.filled.Bolt
4346import androidx.compose.material.icons.filled.CameraAlt
4447import androidx.compose.material.icons.filled.Cloud
48+ import androidx.compose.material.icons.filled.GraphicEq
4549import androidx.compose.material.icons.filled.Info
4650import androidx.compose.material.icons.filled.LocationOn
4751import androidx.compose.material.icons.filled.Lock
4852import androidx.compose.material.icons.filled.Mic
4953import androidx.compose.material.icons.filled.Notifications
5054import androidx.compose.material.icons.filled.Palette
5155import androidx.compose.material.icons.filled.Person
56+ import androidx.compose.material.icons.filled.PlayArrow
5257import androidx.compose.material.icons.filled.Storage
5358import androidx.compose.material3.HorizontalDivider
5459import androidx.compose.material3.Icon
@@ -389,21 +394,139 @@ private fun V2VoiceSettingsScreen(
389394 val speakerEnabled by viewModel.speakerEnabled.collectAsState()
390395 val micEnabled by viewModel.micEnabled.collectAsState()
391396 val talkModeEnabled by viewModel.talkModeEnabled.collectAsState()
392- val micStatusText by viewModel.micStatusText.collectAsState()
393- val talkModeStatusText by viewModel.talkModeStatusText.collectAsState()
394397
395- V2SettingsDetailFrame (title = " Voice" , subtitle = " Control talk, dictation, and playback." , icon = Icons .Default .Mic , onBack = onBack) {
396- V2SettingsTogglePanel (
397- rows =
398- listOf (
399- V2SettingsToggleRow (" Speaker" , if (speakerEnabled) " Assistant replies play aloud." else " Assistant speech is muted." , Icons .AutoMirrored .Filled .VolumeUp , speakerEnabled, viewModel::setSpeakerEnabled),
400- V2SettingsToggleRow (" Dictation" , micStatusText, Icons .Default .Mic , micEnabled, viewModel::setMicEnabled),
401- V2SettingsToggleRow (" Realtime Talk" , talkModeStatusText, Icons .Default .Bolt , talkModeEnabled, viewModel::setTalkModeEnabled),
402- ),
398+ V2SettingsDetailFrame (title = " Talk Provider Setup" , subtitle = " Configure voice, transport, and playback." , icon = Icons .Default .Mic , onBack = onBack) {
399+ Column (verticalArrangement = Arrangement .spacedBy(10 .dp)) {
400+ V2VoiceSetupPanel (
401+ voiceActive = micEnabled || talkModeEnabled,
402+ )
403+ Text (text = " Audio Test" , style = ClawTheme .type.section, color = ClawTheme .colors.text)
404+ Text (text = " Check that OpenClaw can speak clearly on this phone." , style = ClawTheme .type.body, color = ClawTheme .colors.textMuted)
405+ V2SettingsWaveformPanel (active = speakerEnabled)
406+ V2VoiceSetupActionRow (
407+ title = if (speakerEnabled) " Mute speaker" else " Enable speaker" ,
408+ subtitle = if (speakerEnabled) " Replies play aloud" else " Assistant speech muted" ,
409+ icon = Icons .AutoMirrored .Filled .VolumeUp ,
410+ statusText = if (speakerEnabled) " On" else " Muted" ,
411+ ready = speakerEnabled,
412+ onClick = { viewModel.setSpeakerEnabled(! speakerEnabled) },
413+ )
414+ ClawPrimaryButton (text = " Save Voice Setup" , onClick = onBack, modifier = Modifier .fillMaxWidth(), icon = Icons .Default .GraphicEq )
415+ }
416+ }
417+ }
418+
419+ @Composable
420+ private fun V2VoiceSetupPanel (
421+ voiceActive : Boolean ,
422+ ) {
423+ Column (verticalArrangement = Arrangement .spacedBy(9 .dp)) {
424+ V2VoiceSetupActionRow (
425+ title = " Realtime Provider" ,
426+ subtitle = " Gateway voice relay" ,
427+ icon = Icons .Default .GraphicEq ,
428+ statusText = if (voiceActive) " Live" else " Ready" ,
429+ ready = true ,
430+ )
431+ V2VoiceSetupActionRow (
432+ title = " Voice" ,
433+ subtitle = " Voice input" ,
434+ icon = Icons .Default .Mic ,
435+ statusText = " Configured" ,
436+ ready = true ,
437+ )
438+ V2VoiceSetupActionRow (
439+ title = " Transport" ,
440+ subtitle = " Socket relay" ,
441+ icon = Icons .Default .Bolt ,
442+ statusText = " Configured" ,
443+ ready = true ,
403444 )
404445 }
405446}
406447
448+ @Composable
449+ private fun V2VoiceSetupActionRow (
450+ title : String ,
451+ subtitle : String ,
452+ icon : ImageVector ,
453+ statusText : String ,
454+ ready : Boolean ,
455+ onClick : (() -> Unit )? = null,
456+ ) {
457+ val rowModifier = Modifier .fillMaxWidth().heightIn(min = 68 .dp)
458+ Surface (
459+ onClick = onClick ? : {},
460+ enabled = onClick != null ,
461+ modifier = rowModifier,
462+ shape = RoundedCornerShape (ClawTheme .radii.panel),
463+ color = ClawTheme .colors.surface,
464+ contentColor = ClawTheme .colors.text,
465+ border = BorderStroke (1 .dp, ClawTheme .colors.border),
466+ ) {
467+ Row (
468+ modifier = Modifier .fillMaxWidth().padding(horizontal = 12 .dp, vertical = 10 .dp),
469+ verticalAlignment = Alignment .CenterVertically ,
470+ horizontalArrangement = Arrangement .spacedBy(11 .dp),
471+ ) {
472+ Surface (
473+ modifier = Modifier .size(38 .dp),
474+ shape = CircleShape ,
475+ color = ClawTheme .colors.canvas,
476+ contentColor = ClawTheme .colors.text,
477+ border = BorderStroke (1 .dp, ClawTheme .colors.borderStrong),
478+ ) {
479+ Box (contentAlignment = Alignment .Center ) {
480+ Icon (imageVector = icon, contentDescription = null , modifier = Modifier .size(19 .dp))
481+ }
482+ }
483+ Column (modifier = Modifier .weight(1f ), verticalArrangement = Arrangement .spacedBy(2 .dp)) {
484+ Text (text = title, style = ClawTheme .type.section, color = ClawTheme .colors.text, maxLines = 1 , overflow = TextOverflow .Ellipsis )
485+ Text (text = subtitle, style = ClawTheme .type.body, color = ClawTheme .colors.textMuted, maxLines = 1 , overflow = TextOverflow .Ellipsis )
486+ }
487+ Row (verticalAlignment = Alignment .CenterVertically , horizontalArrangement = Arrangement .spacedBy(7 .dp)) {
488+ Box (
489+ modifier =
490+ Modifier
491+ .size(7 .dp)
492+ .background(if (ready) ClawTheme .colors.success else ClawTheme .colors.textSubtle, CircleShape ),
493+ )
494+ Text (text = statusText, style = ClawTheme .type.body, color = ClawTheme .colors.textMuted, maxLines = 1 )
495+ Icon (imageVector = Icons .AutoMirrored .Filled .KeyboardArrowRight , contentDescription = null , modifier = Modifier .size(20 .dp), tint = ClawTheme .colors.textMuted)
496+ }
497+ }
498+ }
499+ }
500+
501+ @Composable
502+ private fun V2SettingsWaveformPanel (active : Boolean ) {
503+ Surface (
504+ modifier = Modifier .fillMaxWidth().height(76 .dp),
505+ shape = RoundedCornerShape (ClawTheme .radii.panel),
506+ color = ClawTheme .colors.surface,
507+ contentColor = ClawTheme .colors.text,
508+ border = BorderStroke (1 .dp, ClawTheme .colors.border),
509+ ) {
510+ Row (
511+ modifier = Modifier .fillMaxWidth().padding(horizontal = 18 .dp),
512+ verticalAlignment = Alignment .CenterVertically ,
513+ horizontalArrangement = Arrangement .spacedBy(5 .dp),
514+ ) {
515+ Icon (imageVector = Icons .Default .PlayArrow , contentDescription = null , modifier = Modifier .size(24 .dp), tint = ClawTheme .colors.text)
516+ Row (modifier = Modifier .weight(1f ), horizontalArrangement = Arrangement .SpaceEvenly , verticalAlignment = Alignment .CenterVertically ) {
517+ listOf (6 , 12 , 18 , 11 , 28 , 34 , 18 , 10 , 8 , 24 , 38 , 31 , 12 , 8 , 18 , 30 , 40 , 22 , 12 , 8 , 20 , 29 , 16 , 8 ).forEachIndexed { index, height ->
518+ Box (
519+ modifier =
520+ Modifier
521+ .size(width = 2 .dp, height = (if (active) height else 7 + index % 4 * 4 ).dp)
522+ .background(if (active) ClawTheme .colors.text else ClawTheme .colors.textSubtle, RoundedCornerShape (999 .dp)),
523+ )
524+ }
525+ }
526+ }
527+ }
528+ }
529+
407530@Composable
408531private fun V2NotificationSettingsScreen (
409532 viewModel : MainViewModel ,
0 commit comments