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.
This commit is contained in:
Severiano Jaramillo 2024-03-28 21:09:47 -07:00
parent 8189b590d3
commit 26436eb687
4 changed files with 60 additions and 14 deletions

View file

@ -7,8 +7,13 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
import ui.theme.KeeTheme import ui.theme.KeeTheme
@Composable @Composable
fun NodeStatus() { fun NodeStatus(blockCount: Int? = null) {
Text("Connected to X.X.X.X", color = MaterialTheme.colorScheme.onBackground) val text = if (blockCount == null) {
"Not connected"
} else {
"Block #: $blockCount"
}
Text(text, color = MaterialTheme.colorScheme.onBackground)
} }
@Preview @Preview

View file

@ -27,13 +27,15 @@ class ImportAccountScreen : Screen {
override fun Content() = KeeTheme { override fun Content() = KeeTheme {
val screenModel = rememberScreenModel { ImportAccountScreenModel() } val screenModel = rememberScreenModel { ImportAccountScreenModel() }
ImportAccountScreenContent() val state by screenModel.state.collectAsState()
ImportAccountScreenContent(state)
} }
} }
@OptIn(ExperimentalResourceApi::class) @OptIn(ExperimentalResourceApi::class)
@Composable @Composable
private fun ImportAccountScreenContent() { private fun ImportAccountScreenContent(state: ImportAccountScreenModel.State) {
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
Scaffold( Scaffold(
@ -79,7 +81,7 @@ private fun ImportAccountScreenContent() {
Text("Import Account") Text("Import Account")
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
NodeStatus() NodeStatus(state.blockCount)
} }
} }
} }
@ -87,11 +89,11 @@ private fun ImportAccountScreenContent() {
@Preview @Preview
@Composable @Composable
private fun ImportAccountScreenContentLightPreview() = KeeTheme(useDarkTheme = false) { private fun ImportAccountScreenContentLightPreview() = KeeTheme(useDarkTheme = false) {
ImportAccountScreenContent() ImportAccountScreenContent(ImportAccountScreenModel.State())
} }
@Preview @Preview
@Composable @Composable
private fun ImportAccountScreenContentDarkPreview() = KeeTheme(useDarkTheme = true) { private fun ImportAccountScreenContentDarkPreview() = KeeTheme(useDarkTheme = true) {
ImportAccountScreenContent() ImportAccountScreenContent(ImportAccountScreenModel.State())
} }

View file

@ -1,19 +1,35 @@
package net.agorise.kee.ui.screen.importaccount 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 cafe.adriel.voyager.core.model.screenModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.agorise.shared.stargate.StargateBridge import net.agorise.shared.stargate.StargateBridge
class ImportAccountScreenModel : ScreenModel { class ImportAccountScreenModel : StateScreenModel<ImportAccountScreenModel.State>(State()) {
data class State(
val blockCount: Int? = null,
)
private val stargateBridge = StargateBridge() private val stargateBridge = StargateBridge()
init { init {
screenModelScope.launch(Dispatchers.IO) { screenModelScope.launch(Dispatchers.IO) {
stargateBridge.start() stargateBridge.start()
} }
listenForBlockCount()
}
private fun listenForBlockCount() {
stargateBridge.blockCountChannel.receiveAsFlow().onEach { blockCount ->
mutableState.value = state.value.copy(blockCount = blockCount)
}.launchIn(screenModelScope)
} }
override fun onDispose() { override fun onDispose() {

View file

@ -8,6 +8,7 @@ import io.ktor.client.plugins.websocket.webSocket
import io.ktor.websocket.Frame import io.ktor.websocket.Frame
import io.ktor.websocket.readText import io.ktor.websocket.readText
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
@ -18,11 +19,22 @@ import kotlinx.coroutines.flow.receiveAsFlow
class StargateBridge { class StargateBridge {
private val foundationNode = DeroNode(host = "node.derofoundation.org", port = 11012, path = "/ws") 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 client: HttpClient = HttpClient(CIO) { install(WebSockets) }
private val rendezvousChannel = Channel<Boolean>() /**
* Internal channel that listens for block broadcasts and is used to request the block count.
*/
private val blockBroadcastChannel = Channel<Boolean>(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<Int>(CONFLATED)
suspend fun start() { suspend fun start() {
val node = foundationNode val node = foundationNode
@ -42,16 +54,27 @@ class StargateBridge {
val text = textFrame.readText() val text = textFrame.readText()
println(text) println(text)
if (text.contains("\"method\":\"Block\"")) { 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() { private suspend fun DefaultClientWebSocketSession.writeMessages() {
rendezvousChannel.receiveAsFlow().onEach { blockBroadcastChannel.receiveAsFlow().onEach {
println("Prepared to send...") val blockCountRequest = getBlockCountRequest()
println("Requesting: $blockCountRequest")
outgoing.send(Frame.Text(blockCountRequest)) outgoing.send(Frame.Text(blockCountRequest))
}.launchIn(this) }.launchIn(this)
} }
// Temporary request to get block count
private fun getBlockCountRequest() = "{ \"jsonrpc\": \"2.0\", \"id\": \"${getNextRequestId()}\", \"method\": \"DERO.GetBlockCount\" }"
private fun getNextRequestId() = ++currentRequestId
} }