From 26436eb687aadb2bb5d9e2f068915f5914648409 Mon Sep 17 00:00:00 2001 From: Severiano Jaramillo Date: Thu, 28 Mar 2024 21:09:47 -0700 Subject: [PATCH] Show current block number in Import Account screen. - Updated NodeStatus component to show the current block count if connected to the Dero node, or a 'Not connected' text otherwise. - Updated ImportAccountScreenModel to extend from StateScreenModel with a State data class that contains the blockCount for now. This State is observed by ImportAccountScreen which automatically adjusts to the latest State and shows the proper UI. --- .../kee/ui/component/nodestatus/NodeStatus.kt | 9 +++-- .../importaccount/ImportAccountScreen.kt | 12 ++++--- .../importaccount/ImportAccountScreenModel.kt | 20 +++++++++-- .../agorise/shared/stargate/StargateBridge.kt | 33 ++++++++++++++++--- 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/net/agorise/kee/ui/component/nodestatus/NodeStatus.kt b/composeApp/src/commonMain/kotlin/net/agorise/kee/ui/component/nodestatus/NodeStatus.kt index f9966a5..425ccde 100644 --- a/composeApp/src/commonMain/kotlin/net/agorise/kee/ui/component/nodestatus/NodeStatus.kt +++ b/composeApp/src/commonMain/kotlin/net/agorise/kee/ui/component/nodestatus/NodeStatus.kt @@ -7,8 +7,13 @@ import org.jetbrains.compose.ui.tooling.preview.Preview import ui.theme.KeeTheme @Composable -fun NodeStatus() { - Text("Connected to X.X.X.X", color = MaterialTheme.colorScheme.onBackground) +fun NodeStatus(blockCount: Int? = null) { + val text = if (blockCount == null) { + "Not connected" + } else { + "Block #: $blockCount" + } + Text(text, color = MaterialTheme.colorScheme.onBackground) } @Preview diff --git a/composeApp/src/commonMain/kotlin/net/agorise/kee/ui/screen/importaccount/ImportAccountScreen.kt b/composeApp/src/commonMain/kotlin/net/agorise/kee/ui/screen/importaccount/ImportAccountScreen.kt index 0d70d9b..8eeb496 100644 --- a/composeApp/src/commonMain/kotlin/net/agorise/kee/ui/screen/importaccount/ImportAccountScreen.kt +++ b/composeApp/src/commonMain/kotlin/net/agorise/kee/ui/screen/importaccount/ImportAccountScreen.kt @@ -27,13 +27,15 @@ class ImportAccountScreen : Screen { override fun Content() = KeeTheme { val screenModel = rememberScreenModel { ImportAccountScreenModel() } - ImportAccountScreenContent() + val state by screenModel.state.collectAsState() + + ImportAccountScreenContent(state) } } @OptIn(ExperimentalResourceApi::class) @Composable -private fun ImportAccountScreenContent() { +private fun ImportAccountScreenContent(state: ImportAccountScreenModel.State) { val navigator = LocalNavigator.current Scaffold( @@ -79,7 +81,7 @@ private fun ImportAccountScreenContent() { Text("Import Account") } Spacer(modifier = Modifier.weight(1f)) - NodeStatus() + NodeStatus(state.blockCount) } } } @@ -87,11 +89,11 @@ private fun ImportAccountScreenContent() { @Preview @Composable private fun ImportAccountScreenContentLightPreview() = KeeTheme(useDarkTheme = false) { - ImportAccountScreenContent() + ImportAccountScreenContent(ImportAccountScreenModel.State()) } @Preview @Composable private fun ImportAccountScreenContentDarkPreview() = KeeTheme(useDarkTheme = true) { - ImportAccountScreenContent() + ImportAccountScreenContent(ImportAccountScreenModel.State()) } diff --git a/composeApp/src/commonMain/kotlin/net/agorise/kee/ui/screen/importaccount/ImportAccountScreenModel.kt b/composeApp/src/commonMain/kotlin/net/agorise/kee/ui/screen/importaccount/ImportAccountScreenModel.kt index 6a94bf5..24955b2 100644 --- a/composeApp/src/commonMain/kotlin/net/agorise/kee/ui/screen/importaccount/ImportAccountScreenModel.kt +++ b/composeApp/src/commonMain/kotlin/net/agorise/kee/ui/screen/importaccount/ImportAccountScreenModel.kt @@ -1,19 +1,35 @@ package net.agorise.kee.ui.screen.importaccount -import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import net.agorise.shared.stargate.StargateBridge -class ImportAccountScreenModel : ScreenModel { +class ImportAccountScreenModel : StateScreenModel(State()) { + + data class State( + val blockCount: Int? = null, + ) + private val stargateBridge = StargateBridge() init { screenModelScope.launch(Dispatchers.IO) { stargateBridge.start() } + + listenForBlockCount() + } + + private fun listenForBlockCount() { + stargateBridge.blockCountChannel.receiveAsFlow().onEach { blockCount -> + mutableState.value = state.value.copy(blockCount = blockCount) + }.launchIn(screenModelScope) } override fun onDispose() { diff --git a/shared/stargate/src/commonMain/kotlin/net/agorise/shared/stargate/StargateBridge.kt b/shared/stargate/src/commonMain/kotlin/net/agorise/shared/stargate/StargateBridge.kt index 09d17de..211eb52 100644 --- a/shared/stargate/src/commonMain/kotlin/net/agorise/shared/stargate/StargateBridge.kt +++ b/shared/stargate/src/commonMain/kotlin/net/agorise/shared/stargate/StargateBridge.kt @@ -8,6 +8,7 @@ import io.ktor.client.plugins.websocket.webSocket import io.ktor.websocket.Frame import io.ktor.websocket.readText import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.Channel.Factory.CONFLATED import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow @@ -18,11 +19,22 @@ import kotlinx.coroutines.flow.receiveAsFlow class StargateBridge { private val foundationNode = DeroNode(host = "node.derofoundation.org", port = 11012, path = "/ws") + /** + * Configure http client to use WebSockets + */ private val client: HttpClient = HttpClient(CIO) { install(WebSockets) } - private val rendezvousChannel = Channel() + /** + * Internal channel that listens for block broadcasts and is used to request the block count. + */ + private val blockBroadcastChannel = Channel(CONFLATED) - private val blockCountRequest = "{ \"jsonrpc\": \"2.0\", \"id\": \"1\", \"method\": \"DERO.GetBlockCount\" }" + private var currentRequestId: Int = 0 + + /** + * Public channel that broadcasts the block count. + */ + val blockCountChannel = Channel(CONFLATED) suspend fun start() { val node = foundationNode @@ -42,16 +54,27 @@ class StargateBridge { val text = textFrame.readText() println(text) if (text.contains("\"method\":\"Block\"")) { - rendezvousChannel.send(true) + blockBroadcastChannel.send(true) + } else if (text.contains("\"count\":")) { + // Rudimentary parsing to obtain block count. Implement proper serialization + // using kotlinx.serialization + val blockCount = text.substringAfter("\"count\":").substringBefore(",").toInt() + blockCountChannel.send(blockCount) } } } } private suspend fun DefaultClientWebSocketSession.writeMessages() { - rendezvousChannel.receiveAsFlow().onEach { - println("Prepared to send...") + blockBroadcastChannel.receiveAsFlow().onEach { + val blockCountRequest = getBlockCountRequest() + println("Requesting: $blockCountRequest") outgoing.send(Frame.Text(blockCountRequest)) }.launchIn(this) } + + // Temporary request to get block count + private fun getBlockCountRequest() = "{ \"jsonrpc\": \"2.0\", \"id\": \"${getNextRequestId()}\", \"method\": \"DERO.GetBlockCount\" }" + + private fun getNextRequestId() = ++currentRequestId }