From 56baf4a7503095d10a4f2866b2dcc94174897610 Mon Sep 17 00:00:00 2001 From: gmulhearn Date: Mon, 18 Sep 2023 16:51:04 +1000 Subject: [PATCH] add controller for managing app state Signed-off-by: Swapnil Tripathi --- aries_vcx/tests/test_credential_retrieval.rs | 15 +-- aries_vcx/tests/test_pool.rs | 2 +- aries_vcx/tests/test_proof_presentation.rs | 2 +- .../core/src/core/unpack_message.rs | 6 +- uniffi_aries_vcx/core/src/lib.rs | 13 +- .../hyperledger/ariesvcx/AppDemoController.kt | 113 ++++++++++++++++++ .../org/hyperledger/ariesvcx/Constants.kt | 2 +- .../org/hyperledger/ariesvcx/HomeScreen.kt | 69 ++++------- .../org/hyperledger/ariesvcx/MainActivity.kt | 54 +-------- .../org/hyperledger/ariesvcx/ScanScreen.kt | 25 +--- 10 files changed, 160 insertions(+), 141 deletions(-) create mode 100644 uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/AppDemoController.kt diff --git a/aries_vcx/tests/test_credential_retrieval.rs b/aries_vcx/tests/test_credential_retrieval.rs index 1696806090..35e72607eb 100644 --- a/aries_vcx/tests/test_credential_retrieval.rs +++ b/aries_vcx/tests/test_credential_retrieval.rs @@ -36,7 +36,7 @@ use crate::utils::migration::Migratable; #[ignore] // TODO: This should be a unit test async fn test_agency_pool_retrieve_credentials_empty() { - SetupProfile::run(|mut setup| async move { + SetupProfile::run(|setup| async move { // create skeleton proof request attachment data let mut req = json!({ "nonce":"123432421212", @@ -130,7 +130,7 @@ async fn test_agency_pool_retrieve_credentials_empty() { #[ignore] // TODO: This should be a unit test async fn test_agency_pool_case_for_proof_req_doesnt_matter_for_retrieve_creds() { - SetupProfile::run(|mut setup| async move { + SetupProfile::run(|setup| async move { let schema = create_and_write_test_schema( &setup.profile.inject_anoncreds(), &setup.profile.inject_anoncreds_ledger_write(), @@ -312,20 +312,15 @@ async fn test_agency_pool_it_should_fail_to_select_credentials_for_predicate() { let presentation_request_data = create_proof_request_data(&mut institution, "[]", &requested_preds_string, "{}", None) .await; - let mut verifier = create_verifier_from_request_data(presentation_request_data).await; + let verifier = create_verifier_from_request_data(presentation_request_data).await; #[cfg(feature = "migration")] consumer.migrate().await; let presentation_request = verifier.get_presentation_request_msg().unwrap(); let mut prover = create_prover_from_request(presentation_request.clone()).await; - let selected_credentials = prover_select_credentials( - &mut prover, - &mut consumer, - presentation_request.into(), - None, - ) - .await; + let selected_credentials = + prover_select_credentials(&mut prover, &mut consumer, presentation_request, None).await; assert!(selected_credentials.credential_for_referent.is_empty()); }) diff --git a/aries_vcx/tests/test_pool.rs b/aries_vcx/tests/test_pool.rs index f24041a753..957b41cc6e 100644 --- a/aries_vcx/tests/test_pool.rs +++ b/aries_vcx/tests/test_pool.rs @@ -424,7 +424,7 @@ async fn test_pool_add_get_attr() { #[tokio::test] #[ignore] async fn test_agency_pool_get_credential_def() { - SetupProfile::run(|mut setup| async move { + SetupProfile::run(|setup| async move { let (_, _, cred_def_id, cred_def_json, _) = create_and_store_nonrevocable_credential_def( &setup.profile.inject_anoncreds(), &setup.profile.inject_anoncreds_ledger_read(), diff --git a/aries_vcx/tests/test_proof_presentation.rs b/aries_vcx/tests/test_proof_presentation.rs index aaaa03ae55..e7f7d2bf0e 100644 --- a/aries_vcx/tests/test_proof_presentation.rs +++ b/aries_vcx/tests/test_proof_presentation.rs @@ -38,7 +38,7 @@ use crate::utils::{ #[tokio::test] #[ignore] async fn test_agency_pool_generate_proof_with_predicates() { - SetupProfile::run(|mut setup| async move { + SetupProfile::run(|setup| async move { let schema = create_and_write_test_schema( &setup.profile.inject_anoncreds(), &setup.profile.inject_anoncreds_ledger_write(), diff --git a/uniffi_aries_vcx/core/src/core/unpack_message.rs b/uniffi_aries_vcx/core/src/core/unpack_message.rs index a68d9a4675..ec105354d8 100644 --- a/uniffi_aries_vcx/core/src/core/unpack_message.rs +++ b/uniffi_aries_vcx/core/src/core/unpack_message.rs @@ -1,9 +1,9 @@ use std::sync::Arc; +use serde::{Deserialize, Serialize}; + use super::profile::ProfileHolder; use crate::{errors::error::VcxUniFFIResult, runtime::block_on}; -use aries_vcx::errors::error::AriesVcxError; -use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] pub struct UnpackMessage { @@ -20,7 +20,7 @@ pub fn unpack_message( block_on(async { let packed_bytes = packed_msg.as_bytes(); let wallet = profile_holder.inner.inject_wallet(); - let unpacked_bytes = wallet.unpack_message(&packed_bytes).await?; + let unpacked_bytes = wallet.unpack_message(packed_bytes).await?; let unpacked_string = String::from_utf8(unpacked_bytes)?; let unpacked_message = serde_json::from_str::(&unpacked_string)?; Ok(unpacked_message) diff --git a/uniffi_aries_vcx/core/src/lib.rs b/uniffi_aries_vcx/core/src/lib.rs index e6f4b04cea..c647641b7c 100644 --- a/uniffi_aries_vcx/core/src/lib.rs +++ b/uniffi_aries_vcx/core/src/lib.rs @@ -5,9 +5,12 @@ pub mod errors; pub mod handlers; pub mod runtime; -use crate::core::profile::*; -use crate::core::unpack_message::*; -use crate::errors::error::*; -use aries_vcx::aries_vcx_core::wallet::indy::WalletConfig; -use aries_vcx::protocols::connection::pairwise_info::PairwiseInfo; +use aries_vcx::{ + aries_vcx_core::wallet::indy::WalletConfig, protocols::connection::pairwise_info::PairwiseInfo, +}; use handlers::connection::*; + +use crate::{ + core::{profile::*, unpack_message::*}, + errors::error::*, +}; diff --git a/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/AppDemoController.kt b/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/AppDemoController.kt new file mode 100644 index 0000000000..86c7cf4633 --- /dev/null +++ b/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/AppDemoController.kt @@ -0,0 +1,113 @@ +package org.hyperledger.ariesvcx + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import org.hyperledger.ariesvcx.utils.await + +data class AppUiState( + val profileReady: Boolean = false, + val connectionInvitationReceived: Boolean = false, + val connectionCompleted: Boolean = false +) + +class AppDemoController : ViewModel() { + private val httpClient = OkHttpClient() + + private var profile: ProfileHolder? = null + private var connection: Connection? = null + + private var onConnectionComplete: (connection: Connection) -> Unit = {} + + // Expose screen UI state + private val _state = MutableStateFlow(AppUiState()) + val states: StateFlow = _state.asStateFlow() + + private val walletConfig = WalletConfig( + walletName = "test_create_wallet_add_uuid_here", + walletKey = "8dvfYSt5d1taSd6yJdpjq4emkwsPDDLYxkNFysFD2cZY", + walletKeyDerivation = "RAW", + walletType = null, + storageConfig = null, + storageCredentials = null, + rekey = null, + rekeyDerivationMethod = null + ) + + suspend fun setupProfile() { + withContext(Dispatchers.IO) { + val newProfile = newIndyProfile(walletConfig) + profile = newProfile + connection = createInvitee(newProfile) + } + _state.update { current -> + current.copy(profileReady = true) + } + } + + suspend fun acceptConnectionInvitation(invitation: String) { + if (connection == null || profile == null) { + throw Exception("Connection or Profile is null") + } + withContext(Dispatchers.IO) { + connection!!.acceptInvitation( + profile = profile!!, + invitation = invitation + ) + _state.update { it.copy(connectionInvitationReceived = true) } + + connection!!.sendRequest( + profile!!, + "$BASE_RELAY_ENDPOINT/send_user_message/$RELAY_USER_ID", + emptyList() + ) + + // use viewmodel scope to finish off this work + viewModelScope.launch(Dispatchers.IO) { + awaitConnectionCompletion() + } + } + } + + private suspend fun awaitConnectionCompletion() { + val pollRelayRequest = Request.Builder() + .url("$BASE_RELAY_ENDPOINT/pop_user_message/$RELAY_USER_ID") + .build() + while (true) { + delay(500) + val relayResponse = httpClient.newCall(pollRelayRequest).await() + if (relayResponse.code == 200) { + val message = relayResponse.body!!.string() + + val unpackedMessage = unpackMessage( + profile!!, + message + ) + + Log.d("AppDemoController", unpackedMessage.message) + connection!!.handleResponse(profile!!, unpackedMessage.message) + connection!!.sendAck(profile!!) + + Log.d("AppDemoController", "connection state: ${connection!!.getState()}") + + _state.update { it.copy(connectionCompleted = true) } + onConnectionComplete(connection!!) + break + } + } + } + + fun subscribeToConnectionComplete(onComplete: (connection: Connection) -> Unit) { + onConnectionComplete = onComplete + } +} diff --git a/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/Constants.kt b/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/Constants.kt index aca596f3d6..90fbb96fb3 100644 --- a/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/Constants.kt +++ b/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/Constants.kt @@ -2,4 +2,4 @@ package org.hyperledger.ariesvcx // Set your public IP address here, this endpoint will be used while communicating with the peer(agent). const val BASE_RELAY_ENDPOINT = "https://b199-27-57-116-96.ngrok-free.app"; -const val RELAY_USER_ID = "demo-user-1"; \ No newline at end of file +const val RELAY_USER_ID = "demo-user-1"; diff --git a/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/HomeScreen.kt b/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/HomeScreen.kt index efe3316640..54583ba273 100644 --- a/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/HomeScreen.kt +++ b/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/HomeScreen.kt @@ -8,67 +8,36 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.navigation.NavHostController import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import okhttp3.OkHttpClient -import okhttp3.Request -import org.hyperledger.ariesvcx.utils.await @Composable fun HomeScreen( + demoController: AppDemoController, navController: NavHostController, - setProfileHolder: (ProfileHolder) -> Unit, - profileHolder: ProfileHolder?, - connection: Connection?, - walletConfig: WalletConfig, - connectionRequestState: Boolean, - httpClient: OkHttpClient ) { + val demoState by demoController.states.collectAsState() + val scope = rememberCoroutineScope() val context = LocalContext.current - var flagKeepFetching by remember { - mutableStateOf(true) - } - - val request = Request.Builder() - .url("$BASE_RELAY_ENDPOINT/pop_user_message/$RELAY_USER_ID") - .build() - - - LaunchedEffect(true) { - scope.launch(Dispatchers.IO) { - while (flagKeepFetching && connectionRequestState) { - delay(500) - val response = httpClient.newCall(request).await() - if (response.code == 200) { - val message = response.body!!.string() - - val unpackedMessage = unpackMessage( - profileHolder!!, - message - ) - - Log.d("HOMESCREEN", "HomeScreen: ${unpackedMessage.message}") - connection?.handleResponse(profileHolder, unpackedMessage.message) - flagKeepFetching = false - connection?.sendAck(profileHolder) - } - } + demoController.subscribeToConnectionComplete { newConn -> + scope.launch(Dispatchers.Main) { + Toast.makeText( + context, + "New Connection Created", + Toast.LENGTH_SHORT + ).show() } } @@ -78,15 +47,14 @@ fun HomeScreen( verticalArrangement = Arrangement.Center ) { Button( - enabled = (connection == null), + enabled = (!demoState.profileReady), onClick = { - scope.launch(Dispatchers.IO) { - val profile = newIndyProfile(walletConfig) - setProfileHolder(profile) + scope.launch { + demoController.setupProfile() withContext(Dispatchers.Main) { Toast.makeText( context, - "New Profile Created: $profile", + "New Profile Created", Toast.LENGTH_SHORT ).show() } @@ -94,11 +62,16 @@ fun HomeScreen( }) { Text(text = "New Indy Profile") } - Button(enabled = (profileHolder != null && connection != null), + Button(enabled = (demoState.profileReady && !demoState.connectionInvitationReceived), onClick = { navController.navigate(Destination.QRScan.route) }) { Text(text = "Scan QR Code") } + Button(enabled = (demoState.connectionCompleted), + onClick = { + }) { + Text(text = "Receive a credential") + } } } diff --git a/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/MainActivity.kt b/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/MainActivity.kt index a096394d52..383816e78b 100644 --- a/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/MainActivity.kt +++ b/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/MainActivity.kt @@ -26,32 +26,6 @@ sealed class Destination(val route: String) { } class MainActivity : ComponentActivity() { - private var profile by mutableStateOf(null) - private var connection by mutableStateOf(null) - private var connectionRequestState by mutableStateOf(false) - private var httpClient = OkHttpClient() - - private val walletConfig = WalletConfig( - walletName = "test_create_wallet_add_uuid_here", - walletKey = "8dvfYSt5d1taSd6yJdpjq4emkwsPDDLYxkNFysFD2cZY", - walletKeyDerivation = "RAW", - walletType = null, - storageConfig = null, - storageCredentials = null, - rekey = null, - rekeyDerivationMethod = null - ) - - private fun setProfileHolder(profileHolder: ProfileHolder) { - profile = profileHolder - connection = createInvitee(profileHolder) - } - - private fun setConnectionRequestState() { - connectionRequestState = true - } - - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Os.setenv("EXTERNAL_STORAGE", this.filesDir.absolutePath, true) @@ -63,14 +37,8 @@ class MainActivity : ComponentActivity() { ) { val navController = rememberNavController() NavigationAppHost( + demoController = AppDemoController(), navController = navController, - setProfileHolder = { setProfileHolder(it) }, - connection = connection, - profileHolder = profile, - walletConfig = walletConfig, - connectionRequestState = connectionRequestState, - setConnectionRequestState = { setConnectionRequestState() }, - httpClient = httpClient ) } } @@ -80,35 +48,21 @@ class MainActivity : ComponentActivity() { @Composable fun NavigationAppHost( + demoController: AppDemoController, navController: NavHostController, - setProfileHolder: (ProfileHolder) -> Unit, - connection: Connection?, - profileHolder: ProfileHolder?, - walletConfig: WalletConfig, - connectionRequestState: Boolean, - setConnectionRequestState: () -> Unit, - httpClient: OkHttpClient, ) { NavHost(navController = navController, startDestination = "home") { composable(Destination.Home.route) { HomeScreen( + demoController = demoController, navController = navController, - setProfileHolder = setProfileHolder, - profileHolder = profileHolder, - connection = connection, - walletConfig = walletConfig, - connectionRequestState = connectionRequestState, - httpClient = httpClient ) } composable(Destination.QRScan.route) { ScanScreen( - connection = connection!!, - profileHolder = profileHolder!!, + demoController = demoController, navController = navController, - walletConfig = walletConfig, - setConnectionRequestState = setConnectionRequestState ) } } diff --git a/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/ScanScreen.kt b/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/ScanScreen.kt index 2c7cec32c7..8d96037473 100644 --- a/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/ScanScreen.kt +++ b/uniffi_aries_vcx/demo/app/src/main/java/org/hyperledger/ariesvcx/ScanScreen.kt @@ -3,7 +3,6 @@ package org.hyperledger.ariesvcx import android.Manifest import android.content.pm.PackageManager import android.net.Uri -import android.util.Size import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.camera.core.CameraSelector @@ -40,11 +39,8 @@ import kotlinx.coroutines.withContext @Composable fun ScanScreen( - connection: Connection, - profileHolder: ProfileHolder, + demoController: AppDemoController, navController: NavHostController, - walletConfig: WalletConfig, - setConnectionRequestState: () -> Unit ) { var scannedQRCodeText by remember { mutableStateOf(null) @@ -67,18 +63,9 @@ fun ScanScreen( text = { Text(decoded) }, confirmButton = { TextButton(onClick = { - scope.launch(Dispatchers.IO) { - connection.acceptInvitation( - profile = profileHolder, - invitation = decoded - ) - connection.sendRequest( - profileHolder, - "$BASE_RELAY_ENDPOINT/send_user_message/$RELAY_USER_ID", - emptyList() - ) + scope.launch { + demoController.acceptConnectionInvitation(decoded) withContext(Dispatchers.Main) { - setConnectionRequestState() navController.navigate("home") } } @@ -130,12 +117,6 @@ fun ScanScreen( .build() preview.setSurfaceProvider(previewView.surfaceProvider) val imageAnalysis = ImageAnalysis.Builder() - .setTargetResolution( - Size( - 320, - 480 - ) - ) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() imageAnalysis.setAnalyzer(