Conversation tile design changes (#3233)

* Update conversation UI with speech bubble

* Clean up design, vibrate when displaying response, close activity if screen turns off

* Review comments

* Switch response side to better match frontend

* Design tweaks

* Use row padding instead of spacer
This commit is contained in:
Daniel Shokouhi 2023-01-20 11:03:30 -08:00 committed by GitHub
parent 33fc6c3779
commit cce8b60c66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 111 additions and 14 deletions

View file

@ -32,4 +32,5 @@
<color name="colorDeviceControlsDefaultOn">#8AB4F8</color>
<color name="colorDeviceControlsThermostatHeat">#FF8B66</color>
<color name="colorDeviceControlsCamera">#F1F3F4</color>
<color name="colorSpeechText">#B3E5FC</color>
</resources>

View file

@ -3,11 +3,13 @@ package io.homeassistant.companion.android.conversation
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.PowerManager
import android.speech.RecognizerIntent
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.core.content.getSystemService
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.conversation.views.ConversationResultView
@ -56,4 +58,12 @@ class ConversationActivity : ComponentActivity() {
ConversationResultView(conversationViewModel)
}
}
override fun onPause() {
super.onPause()
val pm = applicationContext.getSystemService<PowerManager>()
if (pm?.isInteractive == false && conversationViewModel.conversationResult.isNotEmpty()) {
finish()
}
}
}

View file

@ -28,6 +28,9 @@ class ConversationViewModel @Inject constructor(
var supportsConversation by mutableStateOf(false)
private set
var isHapticEnabled = mutableStateOf(false)
private set
fun getConversation() {
viewModelScope.launch {
conversationResult = integrationUseCase.getConversation(speechResult) ?: ""
@ -38,6 +41,7 @@ class ConversationViewModel @Inject constructor(
supportsConversation =
integrationUseCase.isHomeAssistantVersionAtLeast(2023, 1, 0) &&
webSocketRepository.getConfig()?.components?.contains("conversation") == true
isHapticEnabled.value = integrationUseCase.getWearHapticFeedback()
}
fun updateSpeechResult(result: String) {

View file

@ -1,19 +1,35 @@
package io.homeassistant.companion.android.conversation.views
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.PositionIndicator
import androidx.wear.compose.material.Scaffold
import androidx.wear.compose.material.ScalingLazyColumn
import androidx.wear.compose.material.Text
import androidx.wear.compose.material.rememberScalingLazyListState
import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.conversation.ConversationViewModel
import io.homeassistant.companion.android.home.views.TimeText
import io.homeassistant.companion.android.theme.WearAppTheme
import io.homeassistant.companion.android.views.ThemeLazyColumn
@Composable
fun ConversationResultView(
@ -30,28 +46,94 @@ fun ConversationResultView(
},
timeText = { TimeText(visible = !scrollState.isScrollInProgress) }
) {
ThemeLazyColumn(
state = scrollState
ScalingLazyColumn(
state = scrollState,
horizontalAlignment = Alignment.Start
) {
item {
Text(
text = conversationViewModel.speechResult.ifEmpty {
if (conversationViewModel.supportsConversation)
stringResource(R.string.no_results)
else
stringResource(R.string.no_conversation_support)
},
modifier = Modifier.padding(40.dp)
)
Column {
Spacer(Modifier.padding(24.dp))
SpeechBubble(
text = conversationViewModel.speechResult.ifEmpty {
if (conversationViewModel.supportsConversation)
stringResource(R.string.no_results)
else
stringResource(R.string.no_conversation_support)
},
false
)
Spacer(Modifier.padding(8.dp))
}
}
if (conversationViewModel.conversationResult.isNotEmpty())
item {
Text(
if (conversationViewModel.isHapticEnabled.value) {
val haptic = LocalHapticFeedback.current
LaunchedEffect(key1 = "haptic") {
haptic.performHapticFeedback(
HapticFeedbackType.LongPress
)
}
}
SpeechBubble(
text = conversationViewModel.conversationResult,
modifier = Modifier.padding(top = 8.dp, start = 32.dp)
true
)
}
}
}
}
}
@Composable
fun SpeechBubble(text: String, isResponse: Boolean) {
Row(
horizontalArrangement = if (isResponse) Arrangement.Start else Arrangement.End,
modifier = Modifier
.fillMaxWidth()
.padding(
start = if (isResponse) 0.dp else 24.dp,
end = if (isResponse) 24.dp else 0.dp
)
) {
Box(
modifier = Modifier
.background(
if (isResponse)
colorResource(R.color.colorAccent)
else
colorResource(R.color.colorSpeechText),
AbsoluteRoundedCornerShape(
topLeftPercent = 40,
topRightPercent = 40,
bottomLeftPercent = if (isResponse) 0 else 40,
bottomRightPercent = if (isResponse) 40 else 0
)
)
.padding(4.dp)
) {
Text(
text = text,
color = if (isResponse)
Color.White
else
Color.Black,
modifier = Modifier
.padding(4.dp)
)
}
}
}
@Preview(device = Devices.WEAR_OS_SMALL_ROUND)
@Composable
fun PreviewSpeechBubble() {
ScalingLazyColumn(horizontalAlignment = Alignment.Start) {
item {
SpeechBubble(text = "Speech", isResponse = false)
}
item {
SpeechBubble(text = "Response", isResponse = true)
}
}
}