mirror of
https://github.com/home-assistant/android
synced 2024-09-06 17:23:32 +00:00
Minor Assist UI feedback/changes (#3607)
- Remove attribution - Scroll when messages are updated in case messages are longer than the "…" while running - Animation for active voice input
This commit is contained in:
parent
3c706bb7b1
commit
02793338e2
|
@ -166,14 +166,11 @@ class AssistViewModel @Inject constructor(
|
|||
selectedPipeline =
|
||||
allPipelines[selectedServerId]?.firstOrNull { it.id == id } ?: serverManager.webSocketRepository(selectedServerId).getAssistPipeline(id)
|
||||
selectedPipeline?.let {
|
||||
val attribution = serverManager.webSocketRepository(selectedServerId).getConversationAgentInfo(it.conversationEngine)?.attribution
|
||||
currentPipeline = AssistUiPipeline(
|
||||
serverId = selectedServerId,
|
||||
serverName = serverManager.getServer(selectedServerId)?.friendlyName ?: "",
|
||||
id = it.id,
|
||||
name = it.name,
|
||||
attributionName = attribution?.name,
|
||||
attributionUrl = attribution?.url
|
||||
name = it.name
|
||||
)
|
||||
|
||||
_conversation.clear()
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
package io.homeassistant.companion.android.assist.ui
|
||||
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
|
@ -22,9 +29,11 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
|
@ -35,6 +44,7 @@ import androidx.compose.material.MaterialTheme
|
|||
import androidx.compose.material.ModalBottomSheetLayout
|
||||
import androidx.compose.material.ModalBottomSheetValue
|
||||
import androidx.compose.material.OutlinedButton
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
@ -51,19 +61,18 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.mikepenz.iconics.compose.Image
|
||||
|
@ -116,7 +125,7 @@ fun AssistSheetView(
|
|||
) {
|
||||
Column {
|
||||
val lazyListState = rememberLazyListState()
|
||||
LaunchedEffect(conversation.size) {
|
||||
LaunchedEffect("${conversation.size}.${conversation.lastOrNull()?.message?.length}") {
|
||||
lazyListState.animateScrollToItem(conversation.size)
|
||||
}
|
||||
|
||||
|
@ -169,13 +178,9 @@ fun AssistSheetHeader(
|
|||
)
|
||||
if (currentPipeline != null) {
|
||||
val color = colorResource(commonR.color.colorOnSurfaceVariant)
|
||||
val weight = if (currentPipeline.attributionName != null) 0.5f else 1f
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Box(Modifier.weight(weight, fill = false)) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Box {
|
||||
var pipelineShowList by remember { mutableStateOf(false) }
|
||||
val pipelineShowServer by rememberSaveable(pipelines.size) {
|
||||
mutableStateOf(pipelines.distinctBy { it.serverId }.size > 1)
|
||||
|
@ -216,22 +221,6 @@ fun AssistSheetHeader(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (currentPipeline.attributionName != null) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val baseModifier = Modifier.weight(weight, fill = false).padding(start = 8.dp)
|
||||
val modifier = currentPipeline.attributionUrl?.let {
|
||||
Modifier
|
||||
.clickable { uriHandler.openUri(it) }
|
||||
.then(baseModifier)
|
||||
} ?: baseModifier
|
||||
Text(
|
||||
text = currentPipeline.attributionName,
|
||||
textDecoration = if (currentPipeline.attributionUrl != null) TextDecoration.Underline else null,
|
||||
color = color,
|
||||
style = MaterialTheme.typography.caption,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -303,22 +292,49 @@ fun AssistSheetControls(
|
|||
} else {
|
||||
Spacer(Modifier.size(48.dp))
|
||||
Spacer(Modifier.weight(0.5f))
|
||||
OutlinedButton({ onMicrophoneInput() }) {
|
||||
Box(
|
||||
modifier = Modifier.size(64.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val inputIsActive = inputMode == AssistViewModel.AssistInputMode.VOICE_ACTIVE
|
||||
Image(
|
||||
asset = CommunityMaterial.Icon3.cmd_microphone,
|
||||
contentDescription = stringResource(
|
||||
if (inputIsActive) commonR.string.assist_stop_listening else commonR.string.assist_start_listening
|
||||
),
|
||||
colorFilter = ColorFilter.tint(
|
||||
if (inputIsActive) {
|
||||
LocalContentColor.current
|
||||
} else {
|
||||
MaterialTheme.colors.onSurface
|
||||
}
|
||||
),
|
||||
modifier = Modifier.size(32.dp)
|
||||
)
|
||||
if (inputIsActive) {
|
||||
val transition = rememberInfiniteTransition()
|
||||
val scale by transition.animateFloat(
|
||||
initialValue = 1f,
|
||||
targetValue = 1.2f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(600, easing = LinearEasing),
|
||||
repeatMode = RepeatMode.Reverse
|
||||
)
|
||||
)
|
||||
Surface(
|
||||
color = colorResource(commonR.color.colorSpeechText),
|
||||
modifier = Modifier.size(48.dp).scale(scale),
|
||||
shape = CircleShape,
|
||||
content = {}
|
||||
)
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = { onMicrophoneInput() },
|
||||
modifier = Modifier.size(48.dp),
|
||||
shape = CircleShape,
|
||||
border = if (inputIsActive) null else ButtonDefaults.outlinedBorder,
|
||||
colors = if (inputIsActive) {
|
||||
ButtonDefaults.outlinedButtonColors(backgroundColor = Color.Transparent, contentColor = Color.Black)
|
||||
} else {
|
||||
ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colors.onSurface)
|
||||
},
|
||||
contentPadding = PaddingValues(all = 0.dp)
|
||||
) {
|
||||
Image(
|
||||
asset = CommunityMaterial.Icon3.cmd_microphone,
|
||||
contentDescription = stringResource(
|
||||
if (inputIsActive) commonR.string.assist_stop_listening else commonR.string.assist_start_listening
|
||||
),
|
||||
colorFilter = ColorFilter.tint(LocalContentColor.current),
|
||||
modifier = Modifier.size(28.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.weight(0.5f))
|
||||
IconButton({ onChangeInput() }) {
|
||||
|
|
|
@ -4,7 +4,5 @@ data class AssistUiPipeline(
|
|||
val serverId: Int,
|
||||
val serverName: String,
|
||||
val id: String,
|
||||
val name: String,
|
||||
val attributionName: String? = null,
|
||||
val attributionUrl: String? = null
|
||||
val name: String
|
||||
)
|
||||
|
|
|
@ -9,7 +9,6 @@ import io.homeassistant.companion.android.common.data.websocket.impl.entities.As
|
|||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AssistPipelineListResponse
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AssistPipelineResponse
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.CompressedStateChangedEvent
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.ConversationAgentInfoResponse
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.ConversationResponse
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.CurrentUserResponse
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryResponse
|
||||
|
@ -87,12 +86,6 @@ interface WebSocketRepository {
|
|||
*/
|
||||
suspend fun getConversation(speech: String): ConversationResponse?
|
||||
|
||||
/**
|
||||
* Get information about the conversation agent.
|
||||
* @param agentId Should be an [AssistPipelineResponse.conversationEngine]
|
||||
*/
|
||||
suspend fun getConversationAgentInfo(agentId: String): ConversationAgentInfoResponse?
|
||||
|
||||
/**
|
||||
* Get information about an Assist pipeline.
|
||||
* @param pipelineId the ID of the pipeline to get details for, if not specified the preferred
|
||||
|
|
|
@ -34,7 +34,6 @@ import io.homeassistant.companion.android.common.data.websocket.impl.entities.As
|
|||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AssistPipelineSttEnd
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AssistPipelineTtsEnd
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.CompressedStateChangedEvent
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.ConversationAgentInfoResponse
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.ConversationResponse
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.CurrentUserResponse
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryResponse
|
||||
|
@ -222,17 +221,6 @@ class WebSocketRepositoryImpl @AssistedInject constructor(
|
|||
return mapResponse(socketResponse)
|
||||
}
|
||||
|
||||
override suspend fun getConversationAgentInfo(agentId: String): ConversationAgentInfoResponse? {
|
||||
val socketResponse = sendMessage(
|
||||
mapOf(
|
||||
"type" to "conversation/agent/info",
|
||||
"agent_id" to agentId
|
||||
)
|
||||
)
|
||||
|
||||
return mapResponse(socketResponse)
|
||||
}
|
||||
|
||||
override suspend fun getAssistPipeline(pipelineId: String?): AssistPipelineResponse? {
|
||||
val data = mapOf(
|
||||
"type" to "assist_pipeline/pipeline/get"
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
package io.homeassistant.companion.android.common.data.websocket.impl.entities
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class ConversationAgentInfoResponse(
|
||||
val attribution: ConversationAgentAttribution?
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class ConversationAgentAttribution(
|
||||
val name: String,
|
||||
val url: String?
|
||||
)
|
Loading…
Reference in a new issue