Skip to content

Commit e0222dd

Browse files
committed
Add support for Add to in FrontendScreen
1 parent 85dc687 commit e0222dd

9 files changed

Lines changed: 513 additions & 3 deletions

File tree

app/src/main/kotlin/io/homeassistant/companion/android/frontend/FrontendViewModel.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,8 +474,13 @@ internal class FrontendViewModel @VisibleForTesting constructor(
474474
_events.tryEmit(FrontendEvent.NavigateToNfcWrite(messageId = result.messageId, tagId = result.tagId))
475475
}
476476

477+
is FrontendHandlerEvent.EntityAddToExecuted -> {
478+
result.event?.let { _events.tryEmit(it) }
479+
}
480+
477481
is FrontendHandlerEvent.ConfigSent,
478482
is FrontendHandlerEvent.UnknownMessage,
483+
is FrontendHandlerEvent.EntityAddToActionsSent,
479484
-> {
480485
// No-op
481486
}

app/src/main/kotlin/io/homeassistant/companion/android/frontend/handler/FrontendHandlerEvent.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.homeassistant.companion.android.frontend.handler
33
import io.homeassistant.companion.android.frontend.download.DownloadResult
44
import io.homeassistant.companion.android.frontend.error.FrontendConnectionError
55
import io.homeassistant.companion.android.frontend.externalbus.incoming.HapticType
6+
import io.homeassistant.companion.android.frontend.navigation.FrontendEvent
67

78
/**
89
* Events emitted by [FrontendMessageHandler].
@@ -79,4 +80,17 @@ sealed interface FrontendHandlerEvent {
7980
* @param tagId Optional pre-filled tag identifier. When null, the user is prompted to enter one.
8081
*/
8182
data class WriteNfcTag(val messageId: Int, val tagId: String?) : FrontendHandlerEvent
83+
84+
/**
85+
* Frontend requested available EntityAddTo actions and the response was sent.
86+
*/
87+
data object EntityAddToActionsSent : FrontendHandlerEvent
88+
89+
/**
90+
* Frontend requested execution of an EntityAddTo action.
91+
*
92+
* The ViewModel should forward the [event] to the navigation layer.
93+
* When null, the action is unimplemented and should be ignored.
94+
*/
95+
data class EntityAddToExecuted(val event: FrontendEvent?) : FrontendHandlerEvent
8296
}

app/src/main/kotlin/io/homeassistant/companion/android/frontend/handler/FrontendMessageHandler.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import io.homeassistant.companion.android.common.util.AppVersionProvider
66
import io.homeassistant.companion.android.di.qualifiers.IsAutomotive
77
import io.homeassistant.companion.android.frontend.EvaluateJavascriptUsage
88
import io.homeassistant.companion.android.frontend.WebViewAction
9+
import io.homeassistant.companion.android.frontend.addto.FrontendEntityAddToHandler
910
import io.homeassistant.companion.android.frontend.download.FrontendDownloadManager
1011
import io.homeassistant.companion.android.frontend.externalbus.FrontendExternalBusRepository
1112
import io.homeassistant.companion.android.frontend.externalbus.incoming.ConfigGetMessage
1213
import io.homeassistant.companion.android.frontend.externalbus.incoming.ConnectionStatusMessage
14+
import io.homeassistant.companion.android.frontend.externalbus.incoming.EntityAddToGetActionsMessage
15+
import io.homeassistant.companion.android.frontend.externalbus.incoming.EntityAddToMessage
1316
import io.homeassistant.companion.android.frontend.externalbus.incoming.HandleBlobMessage
1417
import io.homeassistant.companion.android.frontend.externalbus.incoming.HapticMessage
1518
import io.homeassistant.companion.android.frontend.externalbus.incoming.IncomingExternalBusMessage
@@ -20,6 +23,7 @@ import io.homeassistant.companion.android.frontend.externalbus.incoming.TagWrite
2023
import io.homeassistant.companion.android.frontend.externalbus.incoming.ThemeUpdateMessage
2124
import io.homeassistant.companion.android.frontend.externalbus.incoming.UnknownIncomingMessage
2225
import io.homeassistant.companion.android.frontend.externalbus.outgoing.ConfigResultMessage
26+
import io.homeassistant.companion.android.frontend.externalbus.outgoing.EntityAddToActionsResultMessage
2327
import io.homeassistant.companion.android.frontend.js.FrontendJsHandler
2428
import io.homeassistant.companion.android.frontend.session.AuthPayload
2529
import io.homeassistant.companion.android.frontend.session.ExternalAuthResult
@@ -28,6 +32,7 @@ import io.homeassistant.companion.android.frontend.session.ServerSessionManager
2832
import io.homeassistant.companion.android.matter.MatterManager
2933
import io.homeassistant.companion.android.thread.ThreadManager
3034
import io.homeassistant.companion.android.util.sensitive
35+
import io.homeassistant.companion.android.webview.externalbus.ExternalEntityAddToAction
3136
import javax.inject.Inject
3237
import kotlinx.coroutines.flow.Flow
3338
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -59,6 +64,7 @@ class FrontendMessageHandler @Inject constructor(
5964
private val appVersionProvider: AppVersionProvider,
6065
private val sessionManager: ServerSessionManager,
6166
private val downloadManager: FrontendDownloadManager,
67+
private val entityAddToHandler: FrontendEntityAddToHandler,
6268
@param:IsAutomotive private val isAutomotive: Boolean,
6369
) : FrontendJsHandler,
6470
FrontendBusObserver {
@@ -188,6 +194,20 @@ class FrontendMessageHandler @Inject constructor(
188194
FrontendHandlerEvent.DownloadCompleted(result)
189195
}
190196

197+
is EntityAddToGetActionsMessage -> {
198+
Timber.d("Entity add_to get_actions request received for: ${message.payload.entityId}")
199+
val actions = entityAddToHandler.getActionsForEntity(message.payload.entityId)
200+
externalBusRepository.send(EntityAddToActionsResultMessage(id = message.id, actions = actions))
201+
FrontendHandlerEvent.EntityAddToActionsSent
202+
}
203+
204+
is EntityAddToMessage -> {
205+
Timber.d("Entity add_to request received for: ${message.payload.entityId}")
206+
val action = ExternalEntityAddToAction.appPayloadToAction(message.payload.appPayload)
207+
val event = entityAddToHandler.execute(message.payload.entityId, action)
208+
FrontendHandlerEvent.EntityAddToExecuted(event)
209+
}
210+
191211
is UnknownIncomingMessage -> {
192212
Timber.d("Unknown message type received: ${message.content}")
193213
FrontendHandlerEvent.UnknownMessage

app/src/main/kotlin/io/homeassistant/companion/android/webview/externalbus/ExternalBusMessage.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import io.homeassistant.companion.android.common.util.kotlinJsonMapper
88
import io.homeassistant.companion.android.common.util.toJsonObject
99
import io.homeassistant.companion.android.webview.addto.EntityAddToAction
1010
import kotlin.io.encoding.Base64
11+
import kotlinx.serialization.SerialName
1112
import kotlinx.serialization.Serializable
1213
import timber.log.Timber
1314

@@ -85,11 +86,11 @@ class ExternalConfigResponse(
8586

8687
@Serializable
8788
data class ExternalEntityAddToAction(
88-
val appPayload: String,
89+
@SerialName("app_payload") val appPayload: String,
8990
val enabled: Boolean,
9091
val name: String,
9192
val details: String?,
92-
val mdiIcon: String,
93+
@SerialName("mdi_icon") val mdiIcon: String,
9394
) {
9495
companion object {
9596
fun fromAction(context: Context, action: EntityAddToAction): ExternalEntityAddToAction {

app/src/test/kotlin/io/homeassistant/companion/android/frontend/FrontendViewModelTest.kt

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,77 @@ class FrontendViewModelTest {
666666
}
667667
}
668668

669+
@Test
670+
fun `Given EntityAddToExecuted with event when collected then event is forwarded`() = runTest {
671+
val messageFlow = MutableSharedFlow<FrontendHandlerEvent>()
672+
every { frontendBusObserver.messageResults() } returns messageFlow
673+
every { urlManager.serverUrlFlow(any(), any()) } returns flowOf(
674+
UrlLoadResult.Success(url = testUrlWithAuth, serverId = serverId),
675+
)
676+
677+
val viewModel = createViewModel()
678+
679+
viewModel.events.test {
680+
advanceTimeBy(CONNECTION_TIMEOUT - 1.seconds)
681+
682+
messageFlow.emit(
683+
FrontendHandlerEvent.EntityAddToExecuted(
684+
FrontendEvent.LaunchWidgetConfig(
685+
entityId = "light.test",
686+
widgetType = io.homeassistant.companion.android.frontend.navigation.WidgetType.Entity,
687+
),
688+
),
689+
)
690+
691+
val event = awaitItem()
692+
assertTrue(event is FrontendEvent.LaunchWidgetConfig)
693+
assertEquals("light.test", (event as FrontendEvent.LaunchWidgetConfig).entityId)
694+
cancelAndIgnoreRemainingEvents()
695+
}
696+
}
697+
698+
@Test
699+
fun `Given EntityAddToExecuted with null event when collected then no event is emitted`() = runTest {
700+
val messageFlow = MutableSharedFlow<FrontendHandlerEvent>()
701+
every { frontendBusObserver.messageResults() } returns messageFlow
702+
every { urlManager.serverUrlFlow(any(), any()) } returns flowOf(
703+
UrlLoadResult.Success(url = testUrlWithAuth, serverId = serverId),
704+
)
705+
706+
val viewModel = createViewModel()
707+
708+
viewModel.events.test {
709+
advanceTimeBy(CONNECTION_TIMEOUT - 1.seconds)
710+
711+
messageFlow.emit(FrontendHandlerEvent.EntityAddToExecuted(event = null))
712+
advanceUntilIdle()
713+
714+
expectNoEvents()
715+
cancelAndIgnoreRemainingEvents()
716+
}
717+
}
718+
719+
@Test
720+
fun `Given EntityAddToActionsSent when collected then no event is emitted`() = runTest {
721+
val messageFlow = MutableSharedFlow<FrontendHandlerEvent>()
722+
every { frontendBusObserver.messageResults() } returns messageFlow
723+
every { urlManager.serverUrlFlow(any(), any()) } returns flowOf(
724+
UrlLoadResult.Success(url = testUrlWithAuth, serverId = serverId),
725+
)
726+
727+
val viewModel = createViewModel()
728+
729+
viewModel.events.test {
730+
advanceTimeBy(CONNECTION_TIMEOUT - 1.seconds)
731+
732+
messageFlow.emit(FrontendHandlerEvent.EntityAddToActionsSent)
733+
advanceUntilIdle()
734+
735+
expectNoEvents()
736+
cancelAndIgnoreRemainingEvents()
737+
}
738+
}
739+
669740
@Test
670741
fun `Given onNfcWriteCompleted when called then sends empty-result ResultMessage back to frontend`() = runTest {
671742
every { urlManager.serverUrlFlow(any(), any()) } returns flowOf(

0 commit comments

Comments
 (0)