Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sign Document Screen #223

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -37,12 +39,16 @@ fun provideDashboardInteractor(
walletCoreDocumentsController: WalletCoreDocumentsController,
walletCoreConfig: WalletCoreConfig,
configLogic: ConfigLogic,
logController: LogController
logController: LogController,
): DashboardInteractor =
DashboardInteractorImpl(
resourceProvider,
walletCoreDocumentsController,
walletCoreConfig,
configLogic,
logController
)
)

@Factory
fun provideDocumentSignInteractor(): DocumentSignInteractor =
DocumentSignInteractorImpl()
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.net.Uri

sealed interface DocumentSignInteractorPartialState {
data class LaunchRQES(val uri: Uri) : DocumentSignInteractorPartialState
}

interface DocumentSignInteractor {
fun launchRQESSdk(uri: Uri): DocumentSignInteractorPartialState
}

class DocumentSignInteractorImpl : DocumentSignInteractor {

override fun launchRQESSdk(uri: Uri): DocumentSignInteractorPartialState {
return DocumentSignInteractorPartialState.LaunchRQES(uri)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -43,5 +44,11 @@ fun NavGraphBuilder.featureDashboardGraph(navController: NavController) {
) {
DashboardScreen(navController, koinViewModel())
}

composable(
route = DashboardScreens.SignDocument.screenRoute
) {
DocumentSignScreen(navController, koinViewModel())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -219,6 +220,11 @@ class DashboardViewModel(
icon = AppIcons.Edit,
event = Event.BottomSheet.Options.OpenChangeQuickPin
),
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_2),
icon = AppIcons.QrScanner,
Expand Down Expand Up @@ -270,6 +276,12 @@ class DashboardViewModel(
navigateToChangeQuickPin()
}

is Event.BottomSheet.Options.OpenSignDocument -> {
hideBottomSheet()
navigateToDocumentSign()

}

is Event.BottomSheet.Options.OpenScanQr -> {
hideBottomSheet()
navigateToQrScan()
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* 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.BACKABLE,
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<Effect>,
onEventSend: (Event) -> Unit,
onNavigationRequested: (Effect.Navigation) -> Unit,
paddingValues: PaddingValues,
) {
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(it))
}
}

val context = LocalContext.current

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
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.net.Uri
import eu.europa.ec.dashboardfeature.interactor.DocumentSignInteractor
import eu.europa.ec.dashboardfeature.interactor.DocumentSignInteractorPartialState
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 uri: Uri) : Event()
}

sealed class Effect : ViewSideEffect {
sealed class Navigation : Effect() {
data object Pop : Navigation()
}

data class OpenDocumentSelection(val selection: Array<String>) : Effect()
data class LaunchedRQES(val uri: Uri) : Effect()
}

@KoinViewModel
class DocumentSignViewModel(
private val documentSignInteractor: DocumentSignInteractor,
private val resourceProvider: ResourceProvider,
) : MviViewModel<Event, State, Effect>() {

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 -> {
when (documentSignInteractor.launchRQESSdk(event.uri)) {
is DocumentSignInteractorPartialState.LaunchRQES -> setEffect {
Effect.LaunchedRQES(
event.uri
)
}
}
}
}
}
}
9 changes: 9 additions & 0 deletions resources-logic/src/main/res/drawable/ic_sign_document.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#2a5ed9" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="@android:color/white" android:pathData="M14,19.88l0,2.12l2.12,0l5.17,-5.17l-2.12,-2.12z"/>

<path android:fillColor="@android:color/white" android:pathData="M20,8l-6,-6H6C4.9,2 4.01,2.9 4.01,4L4,20c0,1.1 0.89,2 1.99,2H12v-2.95l8,-8V8zM13,9V3.5L18.5,9H13z"/>

<path android:fillColor="@android:color/white" android:pathData="M22.71,14L22,13.29c-0.39,-0.39 -1.02,-0.39 -1.41,0L19.88,14L22,16.12l0.71,-0.71C23.1,15.02 23.1,14.39 22.71,14z"/>

</vector>
Loading