package com.ustadmobile.ustadapiconsumer import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.os.* import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.lifecycleScope import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import com.ustadmobile.httpoveripc.client.HttpOverIpcClient import com.ustadmobile.httpoveripc.client.HttpOverIpcProxy import com.ustadmobile.ustadapiconsumer.ui.screens.AccountDetailScreen import com.ustadmobile.ustadapiconsumer.ui.screens.oneroster.GetClassesForUserScreen import com.ustadmobile.ustadapiconsumer.ui.screens.StartScreen import com.ustadmobile.ustadapiconsumer.ui.screens.oneroster.GetLineItemScreen import com.ustadmobile.ustadapiconsumer.ui.screens.oneroster.GetResultsForStudentForClassScreen import com.ustadmobile.ustadapiconsumer.ui.screens.oneroster.PutLineItemScreen import com.ustadmobile.ustadapiconsumer.ui.screens.oneroster.PutResultScreen import com.ustadmobile.ustadapiconsumer.ui.theme.UstadApiConsumerTheme import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import kotlinx.serialization.json.Json import rawhttp.core.RawHttp class MainActivity : ComponentActivity(), IpcClientBindActivity { private var bound: Boolean = false private var mIpcClient: HttpOverIpcClient? = null override val ipcClient: HttpOverIpcClient? get() = mIpcClient private var mHttpOverIpcProxy: HttpOverIpcProxy? = null private val proxyPortFlow = MutableStateFlow(0) override val httpPort: Flow = proxyPortFlow.asStateFlow() override val rawHttp = RawHttp() override val json = Json { encodeDefaults = true ignoreUnknownKeys = true } private fun bindIpcService() { if(bound) return Intent("oneroster").also { intent -> intent.`package` = "com.toughra.ustadmobile" bindService(intent, mConnection, Context.BIND_AUTO_CREATE) } } private val mConnection = object: ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { mIpcClient = HttpOverIpcClient(service).also { mHttpOverIpcProxy = HttpOverIpcProxy(it, RawHttp()) mHttpOverIpcProxy?.start() proxyPortFlow.value = mHttpOverIpcProxy?.listeningPort ?: 0 } bound = true } override fun onServiceDisconnected(className: ComponentName) { mHttpOverIpcProxy?.stop() proxyPortFlow.value = 0 mIpcClient?.close() mIpcClient = null bound = false /* * As per * https://developer.chrome.com/docs/android/custom-tabs/guide-warmup-prefetch * Service connections can be disconnected */ lifecycleScope.launch { Log.d("UstadApiConsumer", "IPC Service disconnected, reconnecting after 1sec") delay(1_000) bindIpcService() } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { UstadApiConsumerTheme { AppNavHost() } } bindIpcService() } override fun onDestroy() { if(bound){ unbindService(mConnection) } super.onDestroy() } } @OptIn(ExperimentalComposeUiApi::class) @Composable fun AppNavHost( modifier: Modifier = Modifier, navController: NavHostController = rememberNavController(), startDestination: String = "start" ) { var tokenResult: GetTokenResult? by rememberSaveable { mutableStateOf(null) } Surface( modifier = Modifier.semantics { testTagsAsResourceId = true }, color = MaterialTheme.colorScheme.background, ) { NavHost( modifier = modifier, navController = navController, startDestination = startDestination ) { composable("start") { StartScreen( tokenResult = tokenResult, onTokenReceived = { tokenResult = it }, onClickAccountList = { navController.navigate("accountlist") }, onClickGetClassesForUser = { navController.navigate("getClassesForUser") }, onClickPutLineItem = { navController.navigate("putLineItem") }, onClickGetLineItem = { navController.navigate("getLineItem") }, onClickPutResult = { navController.navigate("putResult") }, onClickGetResultsForStudentForClass = { navController.navigate("getResultsForStudentForClass") } ) } composable("getClassesForUser") { GetClassesForUserScreen(tokenResult = tokenResult) } composable("putLineItem") { PutLineItemScreen(tokenResult = tokenResult) } composable("getLineItem") { GetLineItemScreen(tokenResult = tokenResult) } composable("putLineItem") { PutLineItemScreen(tokenResult = tokenResult) } composable("putResult") { PutResultScreen(tokenResult = tokenResult) } composable("getResultsForStudentForClass") { GetResultsForStudentForClassScreen(tokenResult = tokenResult) } composable( "accountdetail/{accountName}", arguments = listOf(navArgument("accountName") { type = NavType.StringType }) ){ navEntry -> AccountDetailScreen(accountName = navEntry.arguments?.getString("accountName") ?: "") } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") } @Preview(showBackground = true) @Composable fun DefaultPreview() { UstadApiConsumerTheme { Greeting("Android") } }