diff --git a/assembly-logic/build.gradle.kts b/assembly-logic/build.gradle.kts
index 22e62d09..8ac366d4 100644
--- a/assembly-logic/build.gradle.kts
+++ b/assembly-logic/build.gradle.kts
@@ -36,6 +36,7 @@ import project.convention.logic.kover.koverModules
plugins {
id("project.android.library")
id("project.android.library.compose")
+ id("project.rqes.sdk")
}
android {
diff --git a/assembly-logic/src/main/AndroidManifest.xml b/assembly-logic/src/main/AndroidManifest.xml
index 61b72626..c4f8d240 100644
--- a/assembly-logic/src/main/AndroidManifest.xml
+++ b/assembly-logic/src/main/AndroidManifest.xml
@@ -166,6 +166,18 @@
android:scheme="${openId4VciAuthorizationScheme}" />
+
+
+
+
+
+
+
+
+
diff --git a/assembly-logic/src/main/java/eu/europa/ec/assemblylogic/Application.kt b/assembly-logic/src/main/java/eu/europa/ec/assemblylogic/Application.kt
index 9422434a..a6b81fe0 100644
--- a/assembly-logic/src/main/java/eu/europa/ec/assemblylogic/Application.kt
+++ b/assembly-logic/src/main/java/eu/europa/ec/assemblylogic/Application.kt
@@ -19,22 +19,38 @@ package eu.europa.ec.assemblylogic
import android.app.Application
import eu.europa.ec.analyticslogic.controller.AnalyticsController
import eu.europa.ec.assemblylogic.di.setupKoin
+import eu.europa.ec.businesslogic.config.ConfigLogic
import eu.europa.ec.corelogic.config.WalletCoreConfig
+import eu.europa.ec.eudi.rqesui.infrastructure.EudiRQESUi
import eu.europa.ec.eudi.wallet.EudiWallet
import org.koin.android.ext.android.inject
+import org.koin.core.KoinApplication
class Application : Application() {
private val configWalletCore: WalletCoreConfig by inject()
private val analyticsController: AnalyticsController by inject()
+ private val configLogic: ConfigLogic by inject()
override fun onCreate() {
super.onCreate()
- setupKoin()
+ initializeKoin().initializeRqes()
initializeReporting()
initializeEudiWallet()
}
+ private fun KoinApplication.initializeRqes() {
+ EudiRQESUi.setup(
+ application = this@Application,
+ config = configLogic.rqesConfig,
+ koinApplication = this@initializeRqes
+ )
+ }
+
+ private fun initializeKoin(): KoinApplication {
+ return setupKoin()
+ }
+
private fun initializeReporting() {
analyticsController.initialize(this)
}
diff --git a/assembly-logic/src/main/java/eu/europa/ec/assemblylogic/di/AssemblyModule.kt b/assembly-logic/src/main/java/eu/europa/ec/assemblylogic/di/AssemblyModule.kt
index e1289259..108a9c7e 100644
--- a/assembly-logic/src/main/java/eu/europa/ec/assemblylogic/di/AssemblyModule.kt
+++ b/assembly-logic/src/main/java/eu/europa/ec/assemblylogic/di/AssemblyModule.kt
@@ -33,6 +33,7 @@ import eu.europa.ec.startupfeature.di.FeatureStartupModule
import eu.europa.ec.uilogic.di.LogicUiModule
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
+import org.koin.core.KoinApplication
import org.koin.core.context.GlobalContext.startKoin
import org.koin.ksp.generated.module
@@ -57,8 +58,8 @@ private val assembledModules = listOf(
FeatureIssuanceModule().module
)
-internal fun Application.setupKoin() {
- startKoin {
+internal fun Application.setupKoin(): KoinApplication {
+ return startKoin {
androidContext(this@setupKoin)
androidLogger()
modules(assembledModules)
diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts
index 1da129b7..0f29df25 100644
--- a/build-logic/convention/build.gradle.kts
+++ b/build-logic/convention/build.gradle.kts
@@ -112,5 +112,9 @@ gradlePlugin {
id = "project.android.base.profile"
implementationClass = "AndroidBaseLineProfilePlugin"
}
+ register("eudiRqes") {
+ id = "project.rqes.sdk"
+ implementationClass = "EudiRqesPlugin"
+ }
}
}
diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt
index a1ba855b..924da9fb 100644
--- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt
@@ -58,6 +58,10 @@ class AndroidLibraryConventionPlugin : Plugin {
val openId4VciAuthorizationScheme = "eu.europa.ec.euidi"
val openId4VciAuthorizationHost = "authorization"
+ val rqesScheme = "rqes"
+ val rqesHost = "oauth"
+ val rqesPath = "/callback"
+
with(pluginManager) {
apply("com.android.library")
apply("project.android.library.kover")
@@ -87,6 +91,9 @@ class AndroidLibraryConventionPlugin : Plugin {
"ISSUE_AUTHORIZATION_DEEPLINK",
"$openId4VciAuthorizationScheme://$openId4VciAuthorizationHost"
)
+ addConfigField("RQES_SCHEME", rqesScheme)
+ addConfigField("RQES_HOST", rqesHost)
+ addConfigField("RQES_DEEPLINK", "$rqesScheme://$rqesHost$rqesPath")
// Manifest placeholders for Wallet deepLink
manifestPlaceholders["deepLinkScheme"] = walletScheme
@@ -109,6 +116,11 @@ class AndroidLibraryConventionPlugin : Plugin {
openId4VciAuthorizationScheme
manifestPlaceholders["openId4VciAuthorizationHost"] =
openId4VciAuthorizationHost
+
+ // Manifest placeholders used for RQES
+ manifestPlaceholders["rqesHost"] = rqesHost
+ manifestPlaceholders["rqesScheme"] = rqesScheme
+ manifestPlaceholders["rqesPath"] = rqesPath
}
configureFlavors(this)
configureGradleManagedDevices(this)
diff --git a/build-logic/convention/src/main/kotlin/EudiRqesPlugin.kt b/build-logic/convention/src/main/kotlin/EudiRqesPlugin.kt
new file mode 100644
index 00000000..f56f8bc9
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/EudiRqesPlugin.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.dependencies
+import project.convention.logic.libs
+
+class EudiRqesPlugin : Plugin {
+ override fun apply(target: Project) {
+ with(target) {
+ dependencies {
+ add("implementation", libs.findLibrary("rqes-ui-sdk").get())
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/kotlin/project/convention/logic/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/project/convention/logic/KotlinAndroid.kt
index 0e0aab66..11f0b5ac 100644
--- a/build-logic/convention/src/main/kotlin/project/convention/logic/KotlinAndroid.kt
+++ b/build-logic/convention/src/main/kotlin/project/convention/logic/KotlinAndroid.kt
@@ -37,7 +37,7 @@ internal fun Project.configureKotlinAndroid(
compileSdk = 35
defaultConfig {
- minSdk = 26
+ minSdk = 28
}
buildFeatures {
diff --git a/business-logic/build.gradle.kts b/business-logic/build.gradle.kts
index c6755284..f6c4f42f 100644
--- a/business-logic/build.gradle.kts
+++ b/business-logic/build.gradle.kts
@@ -20,6 +20,7 @@ import project.convention.logic.kover.excludeFromKoverReport
plugins {
id("project.android.library")
+ id("project.rqes.sdk")
}
android {
diff --git a/business-logic/src/demo/java/eu/europa/ec/businesslogic/config/ConfigLogicImpl.kt b/business-logic/src/demo/java/eu/europa/ec/businesslogic/config/ConfigLogicImpl.kt
index c315cda1..ba978601 100644
--- a/business-logic/src/demo/java/eu/europa/ec/businesslogic/config/ConfigLogicImpl.kt
+++ b/business-logic/src/demo/java/eu/europa/ec/businesslogic/config/ConfigLogicImpl.kt
@@ -16,12 +16,24 @@
package eu.europa.ec.businesslogic.config
+import eu.europa.ec.businesslogic.BuildConfig
+import eu.europa.ec.eudi.rqes.HashAlgorithmOID
+import eu.europa.ec.eudi.rqes.SigningAlgorithmOID
+import eu.europa.ec.eudi.rqesui.domain.extension.toUri
+import eu.europa.ec.eudi.rqesui.infrastructure.config.EudiRQESUiConfig
+import eu.europa.ec.eudi.rqesui.infrastructure.config.RqesServiceConfig
+import eu.europa.ec.eudi.rqesui.infrastructure.config.data.QtspData
+import java.net.URI
+
class ConfigLogicImpl : ConfigLogic {
override val appFlavor: AppFlavor
get() = AppFlavor.DEMO
override val environmentConfig: EnvironmentConfig
get() = DemoEnvironmentConfig()
+
+ override val rqesConfig: EudiRQESUiConfig
+ get() = RqesConfig()
}
private class DemoEnvironmentConfig : EnvironmentConfig() {
@@ -29,4 +41,25 @@ private class DemoEnvironmentConfig : EnvironmentConfig() {
ServerConfig.Debug -> ""
ServerConfig.Release -> ""
}
+}
+
+private class RqesConfig : EudiRQESUiConfig {
+
+ override val rqesServiceConfig: RqesServiceConfig
+ get() = RqesServiceConfig(
+ clientId = "wallet-client",
+ clientSecret = "somesecret2",
+ authFlowRedirectionURI = URI.create(BuildConfig.RQES_DEEPLINK),
+ signingAlgorithm = SigningAlgorithmOID.RSA,
+ hashAlgorithm = HashAlgorithmOID.SHA_256,
+ )
+
+ override val qtsps: List
+ get() = listOf(
+ QtspData(
+ name = "Wallet-Centric",
+ endpoint = "https://walletcentric.signer.eudiw.dev/csc/v2".toUri(),
+ scaUrl = "https://walletcentric.signer.eudiw.dev",
+ )
+ )
}
\ No newline at end of file
diff --git a/business-logic/src/dev/java/eu/europa/ec/businesslogic/config/ConfigLogicImpl.kt b/business-logic/src/dev/java/eu/europa/ec/businesslogic/config/ConfigLogicImpl.kt
index 7a76f345..6dea9144 100644
--- a/business-logic/src/dev/java/eu/europa/ec/businesslogic/config/ConfigLogicImpl.kt
+++ b/business-logic/src/dev/java/eu/europa/ec/businesslogic/config/ConfigLogicImpl.kt
@@ -16,12 +16,24 @@
package eu.europa.ec.businesslogic.config
+import eu.europa.ec.businesslogic.BuildConfig
+import eu.europa.ec.eudi.rqes.HashAlgorithmOID
+import eu.europa.ec.eudi.rqes.SigningAlgorithmOID
+import eu.europa.ec.eudi.rqesui.domain.extension.toUri
+import eu.europa.ec.eudi.rqesui.infrastructure.config.EudiRQESUiConfig
+import eu.europa.ec.eudi.rqesui.infrastructure.config.RqesServiceConfig
+import eu.europa.ec.eudi.rqesui.infrastructure.config.data.QtspData
+import java.net.URI
+
class ConfigLogicImpl : ConfigLogic {
override val appFlavor: AppFlavor
get() = AppFlavor.DEV
override val environmentConfig: EnvironmentConfig
get() = DevEnvironmentConfig()
+
+ override val rqesConfig: EudiRQESUiConfig
+ get() = RqesConfig()
}
private class DevEnvironmentConfig : EnvironmentConfig() {
@@ -29,4 +41,25 @@ private class DevEnvironmentConfig : EnvironmentConfig() {
ServerConfig.Debug -> ""
ServerConfig.Release -> ""
}
+}
+
+private class RqesConfig : EudiRQESUiConfig {
+
+ override val rqesServiceConfig: RqesServiceConfig
+ get() = RqesServiceConfig(
+ clientId = "wallet-client",
+ clientSecret = "somesecret2",
+ authFlowRedirectionURI = URI.create(BuildConfig.RQES_DEEPLINK),
+ signingAlgorithm = SigningAlgorithmOID.RSA,
+ hashAlgorithm = HashAlgorithmOID.SHA_256,
+ )
+
+ override val qtsps: List
+ get() = listOf(
+ QtspData(
+ name = "Wallet-Centric",
+ endpoint = "https://walletcentric.signer.eudiw.dev/csc/v2".toUri(),
+ scaUrl = "https://walletcentric.signer.eudiw.dev",
+ )
+ )
}
\ No newline at end of file
diff --git a/business-logic/src/main/java/eu/europa/ec/businesslogic/config/ConfigLogic.kt b/business-logic/src/main/java/eu/europa/ec/businesslogic/config/ConfigLogic.kt
index 79ccc041..42eddfb9 100644
--- a/business-logic/src/main/java/eu/europa/ec/businesslogic/config/ConfigLogic.kt
+++ b/business-logic/src/main/java/eu/europa/ec/businesslogic/config/ConfigLogic.kt
@@ -17,6 +17,7 @@
package eu.europa.ec.businesslogic.config
import eu.europa.ec.businesslogic.BuildConfig
+import eu.europa.ec.eudi.rqesui.infrastructure.config.EudiRQESUiConfig
interface ConfigLogic {
@@ -39,6 +40,11 @@ interface ConfigLogic {
* Application version.
*/
val appVersion: String get() = BuildConfig.APP_VERSION
+
+ /**
+ * RQES Config.
+ */
+ val rqesConfig: EudiRQESUiConfig
}
enum class AppFlavor {
diff --git a/dashboard-feature/build.gradle.kts b/dashboard-feature/build.gradle.kts
index 7f603193..e09f32f4 100644
--- a/dashboard-feature/build.gradle.kts
+++ b/dashboard-feature/build.gradle.kts
@@ -20,6 +20,7 @@ import project.convention.logic.kover.excludeFromKoverReport
plugins {
id("project.android.feature")
+ id("project.rqes.sdk")
}
android {
diff --git a/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/di/FeatureDashboardModule.kt b/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/di/FeatureDashboardModule.kt
index ff70f9a4..b93aabb3 100644
--- a/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/di/FeatureDashboardModule.kt
+++ b/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/di/FeatureDashboardModule.kt
@@ -22,6 +22,8 @@ import eu.europa.ec.corelogic.config.WalletCoreConfig
import eu.europa.ec.corelogic.controller.WalletCoreDocumentsController
import eu.europa.ec.dashboardfeature.interactor.DashboardInteractor
import eu.europa.ec.dashboardfeature.interactor.DashboardInteractorImpl
+import eu.europa.ec.dashboardfeature.interactor.DocumentSignInteractor
+import eu.europa.ec.dashboardfeature.interactor.DocumentSignInteractorImpl
import eu.europa.ec.resourceslogic.provider.ResourceProvider
import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Factory
@@ -37,7 +39,7 @@ fun provideDashboardInteractor(
walletCoreDocumentsController: WalletCoreDocumentsController,
walletCoreConfig: WalletCoreConfig,
configLogic: ConfigLogic,
- logController: LogController
+ logController: LogController,
): DashboardInteractor =
DashboardInteractorImpl(
resourceProvider,
@@ -45,4 +47,8 @@ fun provideDashboardInteractor(
walletCoreConfig,
configLogic,
logController
- )
\ No newline at end of file
+ )
+
+@Factory
+fun provideDocumentSignInteractor(): DocumentSignInteractor =
+ DocumentSignInteractorImpl()
\ No newline at end of file
diff --git a/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/interactor/DocumentSignInteractor.kt b/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/interactor/DocumentSignInteractor.kt
new file mode 100644
index 00000000..50feb729
--- /dev/null
+++ b/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/interactor/DocumentSignInteractor.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.dashboardfeature.interactor
+
+import android.content.Context
+import android.net.Uri
+import eu.europa.ec.eudi.rqesui.infrastructure.EudiRQESUi
+
+interface DocumentSignInteractor {
+ fun launchRQESSdk(context: Context, uri: Uri)
+}
+
+class DocumentSignInteractorImpl : DocumentSignInteractor {
+ override fun launchRQESSdk(context: Context, uri: Uri) {
+ EudiRQESUi.initiate(context, uri)
+ }
+}
\ No newline at end of file
diff --git a/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/router/Graph.kt b/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/router/Graph.kt
index 11ff3551..da904107 100644
--- a/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/router/Graph.kt
+++ b/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/router/Graph.kt
@@ -23,6 +23,7 @@ import androidx.navigation.compose.navigation
import androidx.navigation.navDeepLink
import eu.europa.ec.dashboardfeature.BuildConfig
import eu.europa.ec.dashboardfeature.ui.dashboard.DashboardScreen
+import eu.europa.ec.dashboardfeature.ui.sign.DocumentSignScreen
import eu.europa.ec.uilogic.navigation.DashboardScreens
import eu.europa.ec.uilogic.navigation.ModuleRoute
import org.koin.androidx.compose.koinViewModel
@@ -43,5 +44,11 @@ fun NavGraphBuilder.featureDashboardGraph(navController: NavController) {
) {
DashboardScreen(navController, koinViewModel())
}
+
+ composable(
+ route = DashboardScreens.SignDocument.screenRoute
+ ) {
+ DocumentSignScreen(navController, koinViewModel())
+ }
}
}
\ No newline at end of file
diff --git a/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/dashboard/DashboardViewModel.kt b/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/dashboard/DashboardViewModel.kt
index 5487db98..e0367109 100644
--- a/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/dashboard/DashboardViewModel.kt
+++ b/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/dashboard/DashboardViewModel.kt
@@ -107,6 +107,7 @@ sealed class Event : ViewEvent {
sealed class Options : BottomSheet() {
data object OpenChangeQuickPin : Options()
+ data object OpenSignDocument : Options()
data object OpenScanQr : Options()
data object RetrieveLogs : Options()
}
@@ -224,6 +225,11 @@ class DashboardViewModel(
icon = AppIcons.QrScanner,
event = Event.BottomSheet.Options.OpenScanQr
),
+ ModalOptionUi(
+ title = resourceProvider.getString(R.string.dashboard_bottom_sheet_options_action_4),
+ icon = AppIcons.Sign,
+ event = Event.BottomSheet.Options.OpenSignDocument
+ ),
ModalOptionUi(
title = resourceProvider.getString(R.string.dashboard_bottom_sheet_options_action_3),
icon = AppIcons.OpenNew,
@@ -270,6 +276,12 @@ class DashboardViewModel(
navigateToChangeQuickPin()
}
+ is Event.BottomSheet.Options.OpenSignDocument -> {
+ hideBottomSheet()
+ navigateToDocumentSign()
+
+ }
+
is Event.BottomSheet.Options.OpenScanQr -> {
hideBottomSheet()
navigateToQrScan()
@@ -648,6 +660,14 @@ class DashboardViewModel(
}
}
+ private fun navigateToDocumentSign() {
+ setEffect {
+ Effect.Navigation.SwitchScreen(
+ screenRoute = DashboardScreens.SignDocument.screenRoute
+ )
+ }
+ }
+
private fun startProximityFlow() {
setState { copy(bleAvailability = BleAvailability.AVAILABLE) }
// Create Koin scope for presentation
diff --git a/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/sign/DocumentSignScreen.kt b/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/sign/DocumentSignScreen.kt
new file mode 100644
index 00000000..a7634e9f
--- /dev/null
+++ b/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/sign/DocumentSignScreen.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.dashboardfeature.ui.sign
+
+import android.widget.Toast
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavController
+import eu.europa.ec.resourceslogic.R
+import eu.europa.ec.resourceslogic.theme.values.backgroundDefault
+import eu.europa.ec.resourceslogic.theme.values.textPrimaryDark
+import eu.europa.ec.uilogic.component.AppIcons
+import eu.europa.ec.uilogic.component.content.ContentScreen
+import eu.europa.ec.uilogic.component.content.ContentTitle
+import eu.europa.ec.uilogic.component.content.ScreenNavigateAction
+import eu.europa.ec.uilogic.component.utils.ALPHA_ENABLED
+import eu.europa.ec.uilogic.component.utils.SPACING_MEDIUM
+import eu.europa.ec.uilogic.component.utils.VSpacer
+import eu.europa.ec.uilogic.component.wrap.WrapCard
+import eu.europa.ec.uilogic.component.wrap.WrapIcon
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.onEach
+
+@Composable
+internal fun DocumentSignScreen(
+ navController: NavController,
+ viewModel: DocumentSignViewModel,
+) {
+ val state = viewModel.viewState.value
+
+ ContentScreen(
+ isLoading = state.isLoading,
+ navigatableAction = ScreenNavigateAction.CANCELABLE,
+ onBack = { viewModel.setEvent(Event.Pop) },
+ contentErrorConfig = state.error
+ ) { contentPadding ->
+ Content(
+ state = state,
+ effectFlow = viewModel.effect,
+ onEventSend = { viewModel.setEvent(it) },
+ onNavigationRequested = { navigationEffect ->
+ when (navigationEffect) {
+ Effect.Navigation.Pop -> navController.popBackStack()
+ }
+ },
+ paddingValues = contentPadding
+ )
+ }
+}
+
+
+@Composable
+private fun Content(
+ state: State,
+ effectFlow: Flow,
+ onEventSend: (Event) -> Unit,
+ onNavigationRequested: (Effect.Navigation) -> Unit,
+ paddingValues: PaddingValues,
+) {
+
+ val context = LocalContext.current
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ ) {
+ ContentTitle(
+ title = state.title,
+ subtitle = state.subtitle,
+ )
+
+ VSpacer.Medium()
+
+ SignButton(onEventSend)
+ }
+
+ val selectPdfLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.OpenDocument(),
+ ) { uri ->
+ uri?.let {
+ onEventSend(Event.DocumentUriRetrieved(context, it))
+ }
+ }
+
+ LaunchedEffect(Unit) {
+ effectFlow.onEach { effect ->
+ when (effect) {
+ is Effect.Navigation.Pop -> onNavigationRequested(effect)
+ is Effect.OpenDocumentSelection -> selectPdfLauncher.launch(effect.selection)
+ is Effect.LaunchedRQES -> {
+ Toast.makeText(
+ context,
+ "Launched with: ${effect.uri}",
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
+ }.collect()
+ }
+}
+
+@Composable
+private fun SignButton(onEventSend: (Event) -> Unit) {
+ WrapCard(
+ onClick = {
+ onEventSend(
+ Event.OnSelectDocument
+ )
+ },
+ throttleClicks = true,
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.backgroundDefault,
+ )
+ ) {
+ Row(
+ modifier = Modifier.padding(SPACING_MEDIUM.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ val iconsColor = MaterialTheme.colorScheme.primary
+ val iconsAlpha = ALPHA_ENABLED
+ val textColor = MaterialTheme.colorScheme.textPrimaryDark
+
+ Text(
+ modifier = Modifier.weight(1f),
+ text = stringResource(R.string.document_sign_select_document),
+ style = MaterialTheme.typography.titleMedium,
+ color = textColor
+ )
+
+ WrapIcon(
+ iconData = AppIcons.Add,
+ customTint = iconsColor,
+ contentAlpha = iconsAlpha
+ )
+ }
+ }
+}
diff --git a/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/sign/DocumentSignViewModel.kt b/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/sign/DocumentSignViewModel.kt
new file mode 100644
index 00000000..74d11df4
--- /dev/null
+++ b/dashboard-feature/src/main/java/eu/europa/ec/dashboardfeature/ui/sign/DocumentSignViewModel.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.dashboardfeature.ui.sign
+
+import android.content.Context
+import android.net.Uri
+import eu.europa.ec.dashboardfeature.interactor.DocumentSignInteractor
+import eu.europa.ec.resourceslogic.provider.ResourceProvider
+import eu.europa.ec.uilogic.component.content.ContentErrorConfig
+import eu.europa.ec.uilogic.mvi.MviViewModel
+import eu.europa.ec.uilogic.mvi.ViewEvent
+import eu.europa.ec.uilogic.mvi.ViewSideEffect
+import eu.europa.ec.uilogic.mvi.ViewState
+import org.koin.android.annotation.KoinViewModel
+
+data class State(
+ val isLoading: Boolean = false,
+ val error: ContentErrorConfig? = null,
+ val title: String,
+ val subtitle: String,
+) : ViewState
+
+sealed class Event : ViewEvent {
+ data object Pop : Event()
+ data object OnSelectDocument : Event()
+ data class DocumentUriRetrieved(val context: Context, val uri: Uri) : Event()
+}
+
+sealed class Effect : ViewSideEffect {
+ sealed class Navigation : Effect() {
+ data object Pop : Navigation()
+ }
+
+ data class OpenDocumentSelection(val selection: Array) : Effect()
+ data class LaunchedRQES(val uri: Uri) : Effect()
+}
+
+@KoinViewModel
+class DocumentSignViewModel(
+ private val documentSignInteractor: DocumentSignInteractor,
+ private val resourceProvider: ResourceProvider,
+) : MviViewModel() {
+
+ override fun setInitialState(): State = State(
+ title = resourceProvider.getString(eu.europa.ec.resourceslogic.R.string.document_sign_title),
+ subtitle = resourceProvider.getString(eu.europa.ec.resourceslogic.R.string.document_sign_subtitle)
+ )
+
+ override fun handleEvents(event: Event) {
+ when (event) {
+ is Event.OnSelectDocument -> {
+ setEffect { Effect.OpenDocumentSelection(arrayOf("application/pdf")) }
+ }
+
+ is Event.Pop -> setEffect { Effect.Navigation.Pop }
+ is Event.DocumentUriRetrieved -> documentSignInteractor.launchRQESSdk(
+ event.context,
+ event.uri
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 235e7ba8..cdf0d358 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -67,6 +67,7 @@ sonar = "5.0.0.4638"
baselineprofile = "1.3.3"
timber = "5.0.1"
treessence = "1.1.2"
+rqesUiSDK = "0.0.3-SNAPSHOT"
[libraries]
accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanist" }
@@ -154,6 +155,7 @@ appcenter-crashes = { group = "com.microsoft.appcenter", name = "appcenter-crash
appcenter-distribute = { group = "com.microsoft.appcenter", name = "appcenter-distribute", version.ref = "appCenter" }
timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" }
treessence = { group = "com.github.bastienpaulfr", name = "Treessence", version.ref = "treessence" }
+rqes-ui-sdk = { group = "eu.europa.ec.eudi", name = "eudi-lib-android-rqes-ui", version.ref = "rqesUiSDK" }
# Dependencies of the included build-logic
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
diff --git a/resources-logic/src/main/res/drawable/ic_sign_document.xml b/resources-logic/src/main/res/drawable/ic_sign_document.xml
new file mode 100644
index 00000000..96c87573
--- /dev/null
+++ b/resources-logic/src/main/res/drawable/ic_sign_document.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/resources-logic/src/main/res/values/strings.xml b/resources-logic/src/main/res/values/strings.xml
index 057d1db9..df898d8c 100644
--- a/resources-logic/src/main/res/values/strings.xml
+++ b/resources-logic/src/main/res/values/strings.xml
@@ -164,6 +164,7 @@
Change quick pin
Scan a QR code
Retrieve logs
+ Sign document
Enable bluetooth?
Enable bluetooth to share your information using the QR/TAP option
@@ -179,6 +180,11 @@
New documents have been added to your wallet.
Share logs
+
+ Sign Document
+ Select a document from your device to sign electronically.
+ Select document
+
Camera permission not provided\nOpen App Settings
Having trouble scanning the QR Code?\nPlease try an alternative way
diff --git a/ui-logic/build.gradle.kts b/ui-logic/build.gradle.kts
index ee5a8919..2328d558 100644
--- a/ui-logic/build.gradle.kts
+++ b/ui-logic/build.gradle.kts
@@ -21,6 +21,7 @@ import project.convention.logic.kover.excludeFromKoverReport
plugins {
id("project.android.library")
id("project.android.library.compose")
+ id("project.rqes.sdk")
}
android {
diff --git a/ui-logic/src/main/java/eu/europa/ec/uilogic/component/AppIcons.kt b/ui-logic/src/main/java/eu/europa/ec/uilogic/component/AppIcons.kt
index e2b52970..afc3baac 100644
--- a/ui-logic/src/main/java/eu/europa/ec/uilogic/component/AppIcons.kt
+++ b/ui-logic/src/main/java/eu/europa/ec/uilogic/component/AppIcons.kt
@@ -178,6 +178,12 @@ object AppIcons {
imageVector = null
)
+ val Sign: IconData = IconData(
+ resourceId = R.drawable.ic_sign_document,
+ contentDescriptionId = R.string.content_description_edit_icon,
+ imageVector = null
+ )
+
val QrScanner: IconData = IconData(
resourceId = R.drawable.ic_qr_scanner,
contentDescriptionId = R.string.content_description_qr_scanner_icon,
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 9cef3087..56f3b451 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
@@ -74,7 +74,7 @@ open class EudiComponentActivity : FragmentActivity() {
}
}
- override fun onNewIntent(intent: Intent?) {
+ override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (flowStarted) {
handleDeepLink(intent)
diff --git a/ui-logic/src/main/java/eu/europa/ec/uilogic/navigation/RouterContract.kt b/ui-logic/src/main/java/eu/europa/ec/uilogic/navigation/RouterContract.kt
index a46f5676..e7d707bd 100644
--- a/ui-logic/src/main/java/eu/europa/ec/uilogic/navigation/RouterContract.kt
+++ b/ui-logic/src/main/java/eu/europa/ec/uilogic/navigation/RouterContract.kt
@@ -45,6 +45,8 @@ sealed class CommonScreens {
sealed class DashboardScreens {
data object Dashboard : Screen(name = "DASHBOARD")
+ data object SignDocument :
+ Screen(name = "SIGN_DOCUMENT")
}
sealed class LoginScreens {
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 b44430c5..97d7f7c4 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
@@ -25,6 +25,7 @@ import androidx.core.os.bundleOf
import androidx.navigation.NavController
import eu.europa.ec.businesslogic.util.safeLet
import eu.europa.ec.corelogic.util.CoreActions
+import eu.europa.ec.eudi.rqesui.infrastructure.EudiRQESUi
import eu.europa.ec.uilogic.BuildConfig
import eu.europa.ec.uilogic.container.EudiComponentActivity
import eu.europa.ec.uilogic.extension.openUrl
@@ -137,6 +138,16 @@ fun handleDeepLinkAction(
)
return
}
+
+ DeepLinkType.RQES -> {
+ action.link.getQueryParameter("code")?.let {
+ EudiRQESUi.resume(
+ context = navController.context,
+ authorizationCode = it
+ )
+ }
+ return
+ }
}
val navigationLink = arguments?.let {
@@ -173,6 +184,10 @@ enum class DeepLinkType(val schemas: List, val host: String? = null) {
),
DYNAMIC_PRESENTATION(
emptyList()
+ ),
+ RQES(
+ schemas = listOf(BuildConfig.RQES_SCHEME),
+ host = BuildConfig.RQES_HOST
);
companion object {
@@ -190,6 +205,10 @@ enum class DeepLinkType(val schemas: List, val host: String? = null) {
ISSUANCE
}
+ RQES.schemas.contains(scheme) && host == RQES.host -> {
+ RQES
+ }
+
else -> EXTERNAL
}
}
@@ -197,7 +216,7 @@ enum class DeepLinkType(val schemas: List, val host: String? = null) {
private fun notify(context: Context, action: String, bundle: Bundle? = null) {
Intent().also { intent ->
- intent.setAction(action)
+ intent.action = action
bundle?.let { intent.putExtras(it) }
context.sendBroadcast(intent)
}