update android api to call haskell off main thread (#320)

This commit is contained in:
Evgeny Poberezkin
2022-02-16 21:31:22 +00:00
committed by GitHub
parent f8951b44fc
commit d743804b1d
3 changed files with 81 additions and 45 deletions
@@ -4,6 +4,10 @@ import android.app.Application
import android.net.LocalServerSocket
import android.util.Log
import chat.simplex.app.model.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.InputStreamReader
import java.lang.ref.WeakReference
@@ -28,17 +32,21 @@ class SimplexApp: Application() {
override fun onCreate() {
super.onCreate()
controller = ChatController(chatInit(applicationContext.filesDir.toString()))
var user = controller.apiGetActiveUser()
if (user == null) {
GlobalScope.launch {
withContext(Dispatchers.Main) {
var user = controller.apiGetActiveUser()
if (user == null) {
// user = controller.apiCreateActiveUser(Profile("android", "Android test"))
}
Log.d("SIMPLEX (user)", user.toString())
try {
controller.apiStartChat()
Log.d("SIMPLEX", "started chat")
} catch(e: Error) {
Log.d("SIMPLEX", "failed starting chat $e")
}
Log.d("SIMPLEX (user)", user.toString())
try {
controller.apiStartChat()
Log.d("SIMPLEX", "started chat")
} catch(e: Error) {
Log.d("SIMPLEX", "failed starting chat $e")
// throw e
}
}
}
}
@@ -3,18 +3,20 @@ package chat.simplex.app.model
import android.util.Log
import chat.simplex.app.chatRecvMsg
import chat.simplex.app.chatSendCmd
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import java.lang.Exception
import java.util.*
import kotlin.concurrent.thread
typealias Controller = Long
typealias ChatCtrl = Long
open class ChatController(val ctrl: Controller) {
open class ChatController(val ctrl: ChatCtrl) {
private var chatModel: ChatModel? = null
private val callbacks = mutableMapOf<String, (Error?, CR?) -> Unit>()
fun setModel(m: ChatModel) {
chatModel = m
@@ -26,34 +28,46 @@ open class ChatController(val ctrl: Controller) {
while(true) {
val json = chatRecvMsg(ctrl)
Log.d("SIMPLEX chatRecvMsg: ", json)
chatModel?.terminalItems?.add(TerminalItem.Resp(APIResponse.decodeStr(json)))
val r = APIResponse.decodeStr(json)
if (r.corr != null) {
val cb = callbacks[r.corr]
if (cb != null) cb(null, r.resp)
}
chatModel?.terminalItems?.add(TerminalItem.Resp(r.resp))
}
}
}
fun sendCmd(cmd: CC): CR {
val c = cmd.cmdString
val json = chatSendCmd(ctrl, c)
Log.d("SIMPLEX", "sendCmd: $c")
Log.d("SIMPLEX", "sendCmd response $json")
chatModel?.terminalItems?.add(TerminalItem.Resp(APIResponse.decodeStr(json)))
return APIResponse.decodeStr(json)
suspend fun sendCmd(cmd: CC): CR {
return withContext(Dispatchers.IO) {
val c = cmd.cmdString
chatModel?.terminalItems?.add(TerminalItem.Cmd(cmd))
val json = chatSendCmd(ctrl, c)
Log.d("SIMPLEX", "sendCmd: $c")
Log.d("SIMPLEX", "sendCmd response $json")
val r = APIResponse.decodeStr(json)
chatModel?.terminalItems?.add(TerminalItem.Resp(r.resp))
r.resp
}
}
fun apiGetActiveUser(): User? {
suspend fun apiGetActiveUser(): User? {
val r = sendCmd(CC.ShowActiveUser())
return if (r is CR.ActiveUser) r.user else null
if (r is CR.ActiveUser) return r.user
Log.d("SIMPLEX", "apiGetActiveUser: ${r.toString()}")
return null
}
fun apiCreateActiveUser(p: Profile): User {
suspend fun apiCreateActiveUser(p: Profile): User {
val r = sendCmd(CC.CreateActiveUser(p))
if (r is CR.ActiveUser) return r.user
throw Error("failed creating user: ${r.toString()}")
Log.d("SIMPLEX", "apiCreateActiveUser: ${r.toString()}")
throw Error("user not created ${r.toString()}")
}
fun apiStartChat() {
suspend fun apiStartChat() {
val r = sendCmd(CC.StartChat())
if (r is CR.ChatStarted) return
if (r is CR.ChatStarted ) return
throw Error("failed starting chat: ${r.toString()}")
}
@@ -101,17 +115,20 @@ val json = Json {
}
@Serializable
class APIResponse(val resp: CR) {
class APIResponse(val resp: CR, val corr: String? = null) {
companion object {
fun decodeStr(str: String): CR {
fun decodeStr(str: String): APIResponse {
try {
return json.decodeFromString<APIResponse>(str).resp
return json.decodeFromString(str)
} catch(e: Exception) {
try {
val data = json.parseToJsonElement(str)
return CR.Response(data.jsonObject["resp"]!!.jsonObject["type"]?.toString() ?: "invalid", json.encodeToString(data))
val data = json.parseToJsonElement(str).jsonObject
return APIResponse(
resp = CR.Response(data["resp"]!!.jsonObject["type"]?.toString() ?: "invalid", json.encodeToString(data)),
corr = data["corr"]?.toString()
)
} catch(e: Exception) {
return CR.Invalid(str)
return APIResponse(CR.Invalid(str))
}
}
}
@@ -145,6 +162,13 @@ sealed class CR {
override val details get() = contact.toString()
}
@Serializable
@SerialName("cmdAccepted")
class CmdAccepted(val corr: String): CR() {
override val responseType get() = "cmdAccepted"
override val details get() = "corrId: $corr"
}
@Serializable
class Response(val type: String, val json: String): CR() {
override val responseType get() = "* $type"
@@ -168,7 +192,7 @@ abstract class TerminalItem {
abstract val details: String
class Cmd(val cmd: CC): TerminalItem() {
override val label get() = "> ${cmd.cmdString.substring(0, 30)}"
override val label get() = "> ${cmd.cmdString}"
override val details get() = cmd.cmdString
}
@@ -8,15 +8,16 @@ import androidx.compose.material.Text
import androidx.compose.material.Button
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.*
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chat.SendMsgView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun TerminalPage(chatModel: ChatModel) {
@@ -30,7 +31,7 @@ fun TerminalPage(chatModel: ChatModel) {
type = NavType.StringType
}
)
) { entry -> DetailView( entry.arguments?.getString("_details"), navController)}
) { entry -> DetailView( entry.arguments?.getString("_details"), navController) }
}
}
@@ -39,7 +40,13 @@ fun TerminalView(chatModel: ChatModel, navController: NavController) {
Column {
TerminalLog(chatModel.terminalItems, navController)
SendMsgView(sendMessage = { cmd ->
chatModel.controller.sendCmd(CC.Console(cmd))
GlobalScope.launch {
withContext(Dispatchers.Main) {
// show "in progress"
chatModel.controller.sendCmd(CC.Console(cmd))
// hide "in progress"
}
}
})
}
}
@@ -50,7 +57,7 @@ fun TerminalLog(terminalItems: List<TerminalItem>, navController: NavController)
items(terminalItems) { item ->
ClickableText(
AnnotatedString(item.label),
onClick={navController.navigate("details/${item.details}")}
onClick = { navController.navigate("details/${item.details}") }
)
}
}
@@ -60,11 +67,8 @@ fun TerminalLog(terminalItems: List<TerminalItem>, navController: NavController)
fun DetailView(details: String?, navController: NavController){
Column {
Text("$details")
Button(
onClick={navController.popBackStack()}
)
{Text("Back")}
Button(onClick = { navController.popBackStack() }) {
Text("Back")
}
}
}