From 7b15968ffb028b6b92f68f62a84effed25b3c056 Mon Sep 17 00:00:00 2001 From: Stilianos Tzouvaras Date: Mon, 27 May 2024 15:53:12 +0300 Subject: [PATCH] Improved behavior with issuance resume from authorization, fixed issue with new credential offer deeplink while viewing a previous one, Android back key action correction for presentation and credential offer --- .../commonfeature/ui/request/RequestScreen.kt | 2 +- .../eu/europa/ec/corelogic/util/Constants.kt | 21 +++++++ .../ui/document/add/AddDocumentScreen.kt | 6 ++ .../ui/document/add/AddDocumentViewModel.kt | 5 ++ .../ui/document/offer/DocumentOfferScreen.kt | 8 ++- .../document/offer/DocumentOfferViewModel.kt | 5 ++ .../ec/uilogic/component/BroadcastReceiver.kt | 56 +++++++++++++++++++ .../container/EudiComponentActivity.kt | 11 ++++ .../ec/uilogic/navigation/RouterHost.kt | 14 +++++ .../navigation/helper/DeepLinkHelper.kt | 16 +++++- 10 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 core-logic/src/main/java/eu/europa/ec/corelogic/util/Constants.kt create mode 100644 ui-logic/src/main/java/eu/europa/ec/uilogic/component/BroadcastReceiver.kt diff --git a/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/request/RequestScreen.kt b/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/request/RequestScreen.kt index 052b2a7c..3467113a 100644 --- a/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/request/RequestScreen.kt +++ b/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/request/RequestScreen.kt @@ -77,7 +77,7 @@ fun RequestScreen( ContentScreen( navigatableAction = ScreenNavigateAction.NONE, isLoading = state.isLoading, - onBack = { viewModel.setEvent(Event.GoBack) }, + onBack = { viewModel.setEvent(Event.SecondaryButtonPressed) }, contentErrorConfig = state.error ) { paddingValues -> Content( diff --git a/core-logic/src/main/java/eu/europa/ec/corelogic/util/Constants.kt b/core-logic/src/main/java/eu/europa/ec/corelogic/util/Constants.kt new file mode 100644 index 00000000..959a2711 --- /dev/null +++ b/core-logic/src/main/java/eu/europa/ec/corelogic/util/Constants.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 European Commission + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European + * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work + * except in compliance with the Licence. + * + * You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the Licence for the specific language + * governing permissions and limitations under the Licence. + */ + +package eu.europa.ec.corelogic.util + +object CoreActions { + const val VCI_RESUME_ACTION = "vci.resume.eudi.action" +} \ No newline at end of file diff --git a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/add/AddDocumentScreen.kt b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/add/AddDocumentScreen.kt index 32aed29f..de6ecf70 100644 --- a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/add/AddDocumentScreen.kt +++ b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/add/AddDocumentScreen.kt @@ -43,6 +43,7 @@ import androidx.navigation.NavController import eu.europa.ec.commonfeature.model.DocumentOptionItemUi import eu.europa.ec.corelogic.controller.IssuanceMethod import eu.europa.ec.corelogic.model.DocumentType +import eu.europa.ec.corelogic.util.CoreActions import eu.europa.ec.resourceslogic.R import eu.europa.ec.resourceslogic.theme.values.allCorneredShapeSmall import eu.europa.ec.resourceslogic.theme.values.backgroundDefault @@ -52,6 +53,7 @@ import eu.europa.ec.uilogic.component.AppIcons import eu.europa.ec.uilogic.component.IconData import eu.europa.ec.uilogic.component.IssuanceButton import eu.europa.ec.uilogic.component.IssuanceButtonData +import eu.europa.ec.uilogic.component.SystemBroadcastReceiver import eu.europa.ec.uilogic.component.content.ContentScreen import eu.europa.ec.uilogic.component.content.ContentTitle import eu.europa.ec.uilogic.component.content.ScreenNavigateAction @@ -129,6 +131,10 @@ fun AddDocumentScreen( ) { viewModel.setEvent(Event.Init(context.getPendingDeepLink())) } + + SystemBroadcastReceiver(action = CoreActions.VCI_RESUME_ACTION) { + viewModel.setEvent(Event.OnResumeIssuance) + } } @Composable diff --git a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/add/AddDocumentViewModel.kt b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/add/AddDocumentViewModel.kt index 92c260a9..8ebd9054 100644 --- a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/add/AddDocumentViewModel.kt +++ b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/add/AddDocumentViewModel.kt @@ -70,6 +70,7 @@ sealed class Event : ViewEvent { data class Init(val deepLink: Uri?) : Event() data object Pop : Event() data object OnPause : Event() + data object OnResumeIssuance : Event() data object Finish : Event() data object DismissError : Event() data class IssueDocument( @@ -141,6 +142,10 @@ class AddDocumentViewModel( setState { copy(isLoading = false) } } } + + is Event.OnResumeIssuance -> setState { + copy(isLoading = true) + } } } diff --git a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/offer/DocumentOfferScreen.kt b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/offer/DocumentOfferScreen.kt index 351cf860..39095174 100644 --- a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/offer/DocumentOfferScreen.kt +++ b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/offer/DocumentOfferScreen.kt @@ -41,8 +41,10 @@ import androidx.lifecycle.Lifecycle import androidx.navigation.NavController import eu.europa.ec.commonfeature.ui.request.DocumentCard import eu.europa.ec.commonfeature.ui.request.model.DocumentItemUi +import eu.europa.ec.corelogic.util.CoreActions import eu.europa.ec.resourceslogic.R import eu.europa.ec.uilogic.component.ErrorInfo +import eu.europa.ec.uilogic.component.SystemBroadcastReceiver import eu.europa.ec.uilogic.component.content.ContentGradient import eu.europa.ec.uilogic.component.content.ContentScreen import eu.europa.ec.uilogic.component.content.ContentTitle @@ -86,7 +88,7 @@ fun DocumentOfferScreen( isLoading = state.isLoading, contentErrorConfig = state.error, navigatableAction = ScreenNavigateAction.NONE, - onBack = { viewModel.setEvent(Event.Pop) }, + onBack = { viewModel.setEvent(Event.SecondaryButtonPressed) }, ) { paddingValues -> Content( state = state, @@ -131,6 +133,10 @@ fun DocumentOfferScreen( OneTimeLaunchedEffect { viewModel.setEvent(Event.Init) } + + SystemBroadcastReceiver(action = CoreActions.VCI_RESUME_ACTION) { + viewModel.setEvent(Event.OnResumeIssuance) + } } private fun handleNavigationEffect( diff --git a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/offer/DocumentOfferViewModel.kt b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/offer/DocumentOfferViewModel.kt index 441d4367..68f128f9 100644 --- a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/offer/DocumentOfferViewModel.kt +++ b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/offer/DocumentOfferViewModel.kt @@ -62,6 +62,7 @@ sealed class Event : ViewEvent { data object Init : Event() data object Pop : Event() data object OnPause : Event() + data object OnResumeIssuance : Event() data object DismissError : Event() data class PrimaryButtonPressed(val context: Context) : Event() @@ -173,6 +174,10 @@ class DocumentOfferViewModel( setState { copy(isLoading = false) } } } + + is Event.OnResumeIssuance -> setState { + copy(isLoading = true) + } } } diff --git a/ui-logic/src/main/java/eu/europa/ec/uilogic/component/BroadcastReceiver.kt b/ui-logic/src/main/java/eu/europa/ec/uilogic/component/BroadcastReceiver.kt new file mode 100644 index 00000000..e4d7652d --- /dev/null +++ b/ui-logic/src/main/java/eu/europa/ec/uilogic/component/BroadcastReceiver.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 European Commission + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European + * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work + * except in compliance with the Licence. + * + * You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the Licence for the specific language + * governing permissions and limitations under the Licence. + */ + +package eu.europa.ec.uilogic.component + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat + +@Composable +fun SystemBroadcastReceiver( + action: String, + onEvent: (intent: Intent?) -> Unit +) { + val context = LocalContext.current + + // If either context or Action changes, unregister and register again + DisposableEffect(context, action) { + val intentFilter = IntentFilter(action) + val broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + onEvent(intent) + } + } + + ContextCompat.registerReceiver( + context, + broadcastReceiver, + intentFilter, + ContextCompat.RECEIVER_EXPORTED + ) + + // When the effect leaves the Composition, remove the callback + onDispose { + context.unregisterReceiver(broadcastReceiver) + } + } +} \ No newline at end of file diff --git a/ui-logic/src/main/java/eu/europa/ec/uilogic/container/EudiComponentActivity.kt b/ui-logic/src/main/java/eu/europa/ec/uilogic/container/EudiComponentActivity.kt index 4b934b30..4c7f432f 100644 --- a/ui-logic/src/main/java/eu/europa/ec/uilogic/container/EudiComponentActivity.kt +++ b/ui-logic/src/main/java/eu/europa/ec/uilogic/container/EudiComponentActivity.kt @@ -36,6 +36,7 @@ import androidx.navigation.NavGraphBuilder import com.chuckerteam.chucker.api.Chucker import eu.europa.ec.businesslogic.controller.security.SecurityController import eu.europa.ec.resourceslogic.theme.ThemeManager +import eu.europa.ec.uilogic.navigation.IssuanceScreens import eu.europa.ec.uilogic.navigation.RouterHost import eu.europa.ec.uilogic.navigation.helper.DeepLinkType import eu.europa.ec.uilogic.navigation.helper.handleDeepLinkAction @@ -111,6 +112,16 @@ open class EudiComponentActivity : FragmentActivity() { routerHost.getNavController(), it.link ) + } else if ( + it.type == DeepLinkType.OPENID4VCI + && !routerHost.currentFlowIsAfterOnBoarding() + && routerHost.isScreenOnBackStackOrForeground(IssuanceScreens.AddDocument) + ) { + cacheDeepLink(intent) + routerHost.getNavController().popBackStack( + route = IssuanceScreens.AddDocument.screenRoute, + inclusive = false + ) } else if (it.type != DeepLinkType.ISSUANCE) { cacheDeepLink(intent) if (routerHost.currentFlowIsAfterOnBoarding()) { diff --git a/ui-logic/src/main/java/eu/europa/ec/uilogic/navigation/RouterHost.kt b/ui-logic/src/main/java/eu/europa/ec/uilogic/navigation/RouterHost.kt index e66953b8..00272252 100644 --- a/ui-logic/src/main/java/eu/europa/ec/uilogic/navigation/RouterHost.kt +++ b/ui-logic/src/main/java/eu/europa/ec/uilogic/navigation/RouterHost.kt @@ -35,6 +35,7 @@ interface RouterHost { fun currentFlowIsAfterOnBoarding(): Boolean fun popToLandingScreen() fun getLandingScreen(): String + fun isScreenOnBackStackOrForeground(screen: Screen): Boolean @Composable fun StartFlow(builder: NavGraphBuilder.(NavController) -> Unit) @@ -81,6 +82,19 @@ class RouterHostImpl( } } + override fun isScreenOnBackStackOrForeground(screen: Screen): Boolean { + val screenRoute = screen.screenRoute + try { + if (navController.currentDestination?.route == screenRoute) { + return true + } + navController.getBackStackEntry(screenRoute) + return true + } catch (e: Exception) { + return false + } + } + override fun popToLandingScreen() { navController.popBackStack( route = getLandingScreen(), diff --git a/ui-logic/src/main/java/eu/europa/ec/uilogic/navigation/helper/DeepLinkHelper.kt b/ui-logic/src/main/java/eu/europa/ec/uilogic/navigation/helper/DeepLinkHelper.kt index 531cb5c6..14da4b5d 100644 --- a/ui-logic/src/main/java/eu/europa/ec/uilogic/navigation/helper/DeepLinkHelper.kt +++ b/ui-logic/src/main/java/eu/europa/ec/uilogic/navigation/helper/DeepLinkHelper.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.net.Uri import androidx.core.net.toUri import androidx.navigation.NavController +import eu.europa.ec.corelogic.util.CoreActions import eu.europa.ec.eudi.wallet.EudiWallet import eu.europa.ec.uilogic.BuildConfig import eu.europa.ec.uilogic.container.EudiComponentActivity @@ -83,7 +84,11 @@ fun hasDeepLink(deepLinkUri: Uri?): DeepLinkAction? { } } -fun handleDeepLinkAction(navController: NavController, uri: Uri, arguments: String? = null) { +fun handleDeepLinkAction( + navController: NavController, + uri: Uri, + arguments: String? = null +) { hasDeepLink(uri)?.let { action -> val screen: Screen @@ -99,6 +104,7 @@ fun handleDeepLinkAction(navController: NavController, uri: Uri, arguments: Stri DeepLinkType.ISSUANCE -> { EudiWallet.resumeOpenId4VciWithAuthorization(action.link) + notifyOnResumeIssuance(navController.context) return@let } @@ -147,4 +153,12 @@ enum class DeepLinkType(val host: String? = null) { else -> EXTERNAL } } +} + +private fun notifyOnResumeIssuance(context: Context) { + Intent().also { intent -> + intent.setAction(CoreActions.VCI_RESUME_ACTION) + intent.putExtra("data", "Nothing to see here, move along.") + context.sendBroadcast(intent) + } } \ No newline at end of file