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
@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

View file

@ -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())
}

View file

@ -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<ImportAccountScreenModel.State>(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() {

View file

@ -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<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() {
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
}