From 44f762408935161b2e44d7f13947bacb585a4979 Mon Sep 17 00:00:00 2001 From: Iulia Stana Date: Tue, 11 Apr 2023 13:56:21 +0200 Subject: [PATCH 1/6] Edured-79: Moved ErrorData to it's own file in the main package --- app/src/main/kotlin/nl/eduid/ErrorData.kt | 3 +++ .../accountlinked/AccountLinkedScreen.kt | 10 ++++++-- .../eduid/screens/deeplinks/DeepLinkScreen.kt | 2 +- .../firsttimedialog/LinkAccountViewModel.kt | 2 +- .../eduid/screens/firsttimedialog/UiState.kt | 2 +- .../screens/homepage/HomePageViewModel.kt | 2 +- .../nl/eduid/screens/homepage/UiState.kt | 2 +- .../nl/eduid/screens/oauth/OAuthStep.kt | 2 +- .../nl/eduid/screens/oauth/OAuthViewModel.kt | 2 +- .../screens/personalinfo/PersonalInfoData.kt | 1 + .../personalinfo/PersonalInfoScreen.kt | 25 ++++++++++++++----- .../personalinfo/PersonalInfoViewModel.kt | 1 + .../pinsetup/RegistrationPinSetupViewModel.kt | 2 +- .../nl/eduid/screens/pinsetup/UiState.kt | 2 +- .../RequestEduIdFormViewModel.kt | 2 +- .../requestidpin/ConfirmCodeViewModel.kt | 2 +- .../PhoneRequestCodeViewModel.kt | 2 +- .../screens/requestidrecovery/UiState.kt | 2 +- .../nl/eduid/screens/scan/ScanScreen.kt | 1 + .../kotlin/nl/eduid/screens/scan/ScanState.kt | 8 +++--- 20 files changed, 50 insertions(+), 25 deletions(-) create mode 100644 app/src/main/kotlin/nl/eduid/ErrorData.kt diff --git a/app/src/main/kotlin/nl/eduid/ErrorData.kt b/app/src/main/kotlin/nl/eduid/ErrorData.kt new file mode 100644 index 00000000..f61b4b65 --- /dev/null +++ b/app/src/main/kotlin/nl/eduid/ErrorData.kt @@ -0,0 +1,3 @@ +package nl.eduid + +data class ErrorData(val title: String, val message: String) \ No newline at end of file diff --git a/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt b/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt index 5c4d7dee..d237a5d5 100644 --- a/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt @@ -20,6 +20,8 @@ import androidx.compose.ui.unit.dp import nl.eduid.R import nl.eduid.screens.personalinfo.PersonalInfo import nl.eduid.screens.personalinfo.PersonalInfoViewModel +import nl.eduid.screens.personalinfo.UiState +import nl.eduid.ErrorData import nl.eduid.ui.EduIdTopAppBar import nl.eduid.ui.InfoTab import nl.eduid.ui.PrimaryButton @@ -31,9 +33,11 @@ fun AccountLinkedScreen( viewModel: PersonalInfoViewModel, continueToHome: () -> Unit, ) { - val personalInfo by viewModel.personalInfo.observeAsState(PersonalInfo()) + val uiState by viewModel.uiState.observeAsState(UiState()) AccountLinkedContent( - personalInfo = personalInfo, + personalInfo = uiState.personalInfo, + isLoading = uiState.isLoading, + errorData = uiState.errorData, continueToHome = continueToHome, ) } @@ -41,6 +45,8 @@ fun AccountLinkedScreen( @Composable private fun AccountLinkedContent( personalInfo: PersonalInfo, + isLoading: Boolean = false, + errorData: ErrorData? = null, continueToHome: () -> Unit = {}, ) = EduIdTopAppBar( withBackIcon = false diff --git a/app/src/main/kotlin/nl/eduid/screens/deeplinks/DeepLinkScreen.kt b/app/src/main/kotlin/nl/eduid/screens/deeplinks/DeepLinkScreen.kt index ec47c4c2..712cb0cd 100644 --- a/app/src/main/kotlin/nl/eduid/screens/deeplinks/DeepLinkScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/deeplinks/DeepLinkScreen.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import nl.eduid.R -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData import nl.eduid.ui.AlertDialogWithSingleButton import nl.eduid.ui.EduIdTopAppBar import nl.eduid.ui.theme.findActivity diff --git a/app/src/main/kotlin/nl/eduid/screens/firsttimedialog/LinkAccountViewModel.kt b/app/src/main/kotlin/nl/eduid/screens/firsttimedialog/LinkAccountViewModel.kt index 4d660848..59c5df2e 100644 --- a/app/src/main/kotlin/nl/eduid/screens/firsttimedialog/LinkAccountViewModel.kt +++ b/app/src/main/kotlin/nl/eduid/screens/firsttimedialog/LinkAccountViewModel.kt @@ -8,7 +8,7 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import nl.eduid.di.api.EduIdApi -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData import timber.log.Timber import javax.inject.Inject diff --git a/app/src/main/kotlin/nl/eduid/screens/firsttimedialog/UiState.kt b/app/src/main/kotlin/nl/eduid/screens/firsttimedialog/UiState.kt index 0941b93a..32168421 100644 --- a/app/src/main/kotlin/nl/eduid/screens/firsttimedialog/UiState.kt +++ b/app/src/main/kotlin/nl/eduid/screens/firsttimedialog/UiState.kt @@ -1,7 +1,7 @@ package nl.eduid.screens.firsttimedialog import android.content.Intent -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData data class UiState( val linkUrl: Intent? = null, diff --git a/app/src/main/kotlin/nl/eduid/screens/homepage/HomePageViewModel.kt b/app/src/main/kotlin/nl/eduid/screens/homepage/HomePageViewModel.kt index 1ae1ba80..0407dba5 100644 --- a/app/src/main/kotlin/nl/eduid/screens/homepage/HomePageViewModel.kt +++ b/app/src/main/kotlin/nl/eduid/screens/homepage/HomePageViewModel.kt @@ -11,7 +11,7 @@ import nl.eduid.BaseViewModel import nl.eduid.R import nl.eduid.di.api.EduIdApi import nl.eduid.di.repository.StorageRepository -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData import nl.eduid.screens.splash.SplashWaitTime import org.tiqr.data.model.* import org.tiqr.data.repository.AuthenticationRepository diff --git a/app/src/main/kotlin/nl/eduid/screens/homepage/UiState.kt b/app/src/main/kotlin/nl/eduid/screens/homepage/UiState.kt index a5f33556..3a60a1f5 100644 --- a/app/src/main/kotlin/nl/eduid/screens/homepage/UiState.kt +++ b/app/src/main/kotlin/nl/eduid/screens/homepage/UiState.kt @@ -1,6 +1,6 @@ package nl.eduid.screens.homepage -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData import org.tiqr.data.model.Challenge data class UiState( diff --git a/app/src/main/kotlin/nl/eduid/screens/oauth/OAuthStep.kt b/app/src/main/kotlin/nl/eduid/screens/oauth/OAuthStep.kt index c89f0f1a..1c786b3f 100644 --- a/app/src/main/kotlin/nl/eduid/screens/oauth/OAuthStep.kt +++ b/app/src/main/kotlin/nl/eduid/screens/oauth/OAuthStep.kt @@ -1,7 +1,7 @@ package nl.eduid.screens.oauth import android.content.Intent -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData /** * Loading diff --git a/app/src/main/kotlin/nl/eduid/screens/oauth/OAuthViewModel.kt b/app/src/main/kotlin/nl/eduid/screens/oauth/OAuthViewModel.kt index 34ede517..a28c929d 100644 --- a/app/src/main/kotlin/nl/eduid/screens/oauth/OAuthViewModel.kt +++ b/app/src/main/kotlin/nl/eduid/screens/oauth/OAuthViewModel.kt @@ -19,7 +19,7 @@ import net.openid.appauth.ClientAuthentication.UnsupportedAuthenticationMethod import nl.eduid.R import nl.eduid.di.assist.AuthenticationAssistant import nl.eduid.di.repository.StorageRepository -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData import org.tiqr.data.service.DatabaseService import timber.log.Timber import java.io.IOException diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoData.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoData.kt index 21aa21e6..7b27c8e3 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoData.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoData.kt @@ -20,6 +20,7 @@ data class PersonalInfo( val createdStamp: Long, val expiryStamp: Long, ) + fun demoData(): PersonalInfo { return PersonalInfo( name = "R. van Hamersdonksveer", diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt index 43121952..1e6356dd 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt @@ -1,15 +1,26 @@ package nl.eduid.screens.personalinfo -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterStart import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -17,12 +28,14 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import nl.eduid.ErrorData import nl.eduid.R +import nl.eduid.ui.EduIdTopAppBar import nl.eduid.ui.InfoTab import nl.eduid.ui.getDateTimeString import nl.eduid.ui.theme.ButtonGreen +import nl.eduid.ui.theme.ButtonTextGrey import nl.eduid.ui.theme.EduidAppAndroidTheme -import nl.eduid.ui.theme.TextGrayScale @Composable fun PersonalInfoScreen( diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt index f0e75085..883718b9 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch +import nl.eduid.ErrorData import nl.eduid.di.model.UserDetails import javax.inject.Inject diff --git a/app/src/main/kotlin/nl/eduid/screens/pinsetup/RegistrationPinSetupViewModel.kt b/app/src/main/kotlin/nl/eduid/screens/pinsetup/RegistrationPinSetupViewModel.kt index 257185e5..c9a1d740 100644 --- a/app/src/main/kotlin/nl/eduid/screens/pinsetup/RegistrationPinSetupViewModel.kt +++ b/app/src/main/kotlin/nl/eduid/screens/pinsetup/RegistrationPinSetupViewModel.kt @@ -13,7 +13,7 @@ import nl.eduid.BaseViewModel import nl.eduid.di.repository.StorageRepository import nl.eduid.graphs.Account import nl.eduid.screens.personalinfo.PersonalInfoRepository -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData import nl.eduid.ui.PIN_MAX_LENGTH import org.tiqr.core.util.extensions.biometricUsable import org.tiqr.data.model.ChallengeCompleteResult diff --git a/app/src/main/kotlin/nl/eduid/screens/pinsetup/UiState.kt b/app/src/main/kotlin/nl/eduid/screens/pinsetup/UiState.kt index ee167281..b45ad7fb 100644 --- a/app/src/main/kotlin/nl/eduid/screens/pinsetup/UiState.kt +++ b/app/src/main/kotlin/nl/eduid/screens/pinsetup/UiState.kt @@ -1,6 +1,6 @@ package nl.eduid.screens.pinsetup -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData import org.tiqr.data.model.Challenge data class UiState( diff --git a/app/src/main/kotlin/nl/eduid/screens/requestiddetails/RequestEduIdFormViewModel.kt b/app/src/main/kotlin/nl/eduid/screens/requestiddetails/RequestEduIdFormViewModel.kt index 8c39762e..c393dd78 100644 --- a/app/src/main/kotlin/nl/eduid/screens/requestiddetails/RequestEduIdFormViewModel.kt +++ b/app/src/main/kotlin/nl/eduid/screens/requestiddetails/RequestEduIdFormViewModel.kt @@ -16,7 +16,7 @@ import nl.eduid.di.model.EMAIL_DOMAIN_FORBIDDEN import nl.eduid.di.model.FAIL_EMAIL_IN_USE import nl.eduid.di.model.RequestEduIdAccount import nl.eduid.di.repository.EduIdRepository -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData import org.json.JSONObject import timber.log.Timber import java.io.IOException diff --git a/app/src/main/kotlin/nl/eduid/screens/requestidpin/ConfirmCodeViewModel.kt b/app/src/main/kotlin/nl/eduid/screens/requestidpin/ConfirmCodeViewModel.kt index d2088199..e2b170eb 100644 --- a/app/src/main/kotlin/nl/eduid/screens/requestidpin/ConfirmCodeViewModel.kt +++ b/app/src/main/kotlin/nl/eduid/screens/requestidpin/ConfirmCodeViewModel.kt @@ -7,7 +7,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import nl.eduid.screens.requestidrecovery.RecoveryRepository import nl.eduid.screens.requestidrecovery.UiState -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData import javax.inject.Inject @HiltViewModel diff --git a/app/src/main/kotlin/nl/eduid/screens/requestidrecovery/PhoneRequestCodeViewModel.kt b/app/src/main/kotlin/nl/eduid/screens/requestidrecovery/PhoneRequestCodeViewModel.kt index 85779f51..06770d13 100644 --- a/app/src/main/kotlin/nl/eduid/screens/requestidrecovery/PhoneRequestCodeViewModel.kt +++ b/app/src/main/kotlin/nl/eduid/screens/requestidrecovery/PhoneRequestCodeViewModel.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData import javax.inject.Inject @HiltViewModel diff --git a/app/src/main/kotlin/nl/eduid/screens/requestidrecovery/UiState.kt b/app/src/main/kotlin/nl/eduid/screens/requestidrecovery/UiState.kt index a313fa48..901e7030 100644 --- a/app/src/main/kotlin/nl/eduid/screens/requestidrecovery/UiState.kt +++ b/app/src/main/kotlin/nl/eduid/screens/requestidrecovery/UiState.kt @@ -1,6 +1,6 @@ package nl.eduid.screens.requestidrecovery -import nl.eduid.screens.scan.ErrorData +import nl.eduid.ErrorData data class UiState( val input: String = "", diff --git a/app/src/main/kotlin/nl/eduid/screens/scan/ScanScreen.kt b/app/src/main/kotlin/nl/eduid/screens/scan/ScanScreen.kt index c3370ee9..a52f3e30 100644 --- a/app/src/main/kotlin/nl/eduid/screens/scan/ScanScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/scan/ScanScreen.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.constraintlayout.compose.ConstraintLayout import androidx.lifecycle.LifecycleOwner +import nl.eduid.ErrorData import nl.eduid.R import nl.eduid.ui.AlertDialogWithTwoButton import nl.eduid.ui.theme.EduidAppAndroidTheme diff --git a/app/src/main/kotlin/nl/eduid/screens/scan/ScanState.kt b/app/src/main/kotlin/nl/eduid/screens/scan/ScanState.kt index d05c3dc3..c4516c31 100644 --- a/app/src/main/kotlin/nl/eduid/screens/scan/ScanState.kt +++ b/app/src/main/kotlin/nl/eduid/screens/scan/ScanState.kt @@ -12,6 +12,7 @@ import androidx.lifecycle.LifecycleOwner import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import nl.eduid.ErrorData import org.tiqr.core.scan.ScanComponent import org.tiqr.data.model.Challenge import org.tiqr.data.model.ChallengeParseResult @@ -25,7 +26,7 @@ fun rememberScanState( goToNext: (Challenge) -> Unit, lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, context: Context = LocalContext.current, - coroutineScope: CoroutineScope = rememberCoroutineScope() + coroutineScope: CoroutineScope = rememberCoroutineScope(), ): ScanState { val currentGoBack by rememberUpdatedState(newValue = goBack) val currentGoToNext by rememberUpdatedState(newValue = goToNext) @@ -74,6 +75,7 @@ class ScanState( delay(200L) // delay a bit, otherwise beep sound is cutoff goToNext(parseResult.value) } + is ChallengeParseResult.Failure -> { errorData = ErrorData(parseResult.failure.title, parseResult.failure.message) } @@ -98,6 +100,4 @@ class ScanState( fun updateScanComponent(component: ScanComponent) { scanComponent = component } -} - -data class ErrorData(val title: String, val message: String) \ No newline at end of file +} \ No newline at end of file From 3d7e9b4ebf7543f556c70a5f369589521cb068a2 Mon Sep 17 00:00:00 2001 From: Iulia Stana Date: Tue, 11 Apr 2023 14:03:19 +0200 Subject: [PATCH 2/6] Edured-79: Implemented remove linked account connection. Moved labels to string resources. Refactored personal info screen to reflect progress is ongoing when loading data at start & when deleting linked account. --- .../main/kotlin/nl/eduid/di/api/EduIdApi.kt | 3 + .../kotlin/nl/eduid/di/model/EduIdModels.kt | 1 + .../main/kotlin/nl/eduid/graphs/MainGraph.kt | 4 +- .../personalinfo/PersonalInfoRepository.kt | 18 ++ .../personalinfo/PersonalInfoScreen.kt | 246 +++++++----------- .../personalinfo/PersonalInfoViewModel.kt | 89 +++++-- .../nl/eduid/screens/personalinfo/UiState.kt | 9 + app/src/main/kotlin/nl/eduid/ui/InfoTab.kt | 8 +- app/src/main/res/values/strings.xml | 8 + 9 files changed, 204 insertions(+), 182 deletions(-) create mode 100644 app/src/main/kotlin/nl/eduid/screens/personalinfo/UiState.kt diff --git a/app/src/main/kotlin/nl/eduid/di/api/EduIdApi.kt b/app/src/main/kotlin/nl/eduid/di/api/EduIdApi.kt index 74bf53f9..abaa97ee 100644 --- a/app/src/main/kotlin/nl/eduid/di/api/EduIdApi.kt +++ b/app/src/main/kotlin/nl/eduid/di/api/EduIdApi.kt @@ -35,4 +35,7 @@ interface EduIdApi { @PUT("/mobile/api/sp/email") suspend fun requestEmailChange(@Body email: EmailChangeRequest): Response + + @PUT("/mobile/api/sp/institution") + suspend fun removeConnection(@Body account: LinkedAccount): Response } \ No newline at end of file diff --git a/app/src/main/kotlin/nl/eduid/di/model/EduIdModels.kt b/app/src/main/kotlin/nl/eduid/di/model/EduIdModels.kt index 594125d9..ad701331 100644 --- a/app/src/main/kotlin/nl/eduid/di/model/EduIdModels.kt +++ b/app/src/main/kotlin/nl/eduid/di/model/EduIdModels.kt @@ -89,6 +89,7 @@ data class LinkedAccount( val institutionIdentifier: String, val schacHomeOrganization: String, val eduPersonPrincipalName: String, + val subjectId: String?, val givenName: String, val familyName: String, val eduPersonAffiliations: List, diff --git a/app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt b/app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt index 480218b6..c0b5d949 100644 --- a/app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt +++ b/app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt @@ -372,7 +372,6 @@ fun MainGraph( onNameClicked = { }, onEmailClicked = { navController.navigate(Graph.EDIT_EMAIL) }, onRoleClicked = { }, - onInstitutionClicked = { }, onManageAccountClicked = { dateString -> navController.navigate( ManageAccountRoute.routeWithArgs( @@ -380,8 +379,7 @@ fun MainGraph( ) ) }, - goBack = { navController.popBackStack() }, - ) + ) { navController.popBackStack() } } //endregion composable(Graph.DATA_AND_ACTIVITY) { diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoRepository.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoRepository.kt index fc8bd266..973bb501 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoRepository.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoRepository.kt @@ -1,6 +1,7 @@ package nl.eduid.screens.personalinfo import nl.eduid.di.api.EduIdApi +import nl.eduid.di.model.LinkedAccount import nl.eduid.di.model.UserDetails import timber.log.Timber @@ -23,6 +24,23 @@ class PersonalInfoRepository(private val eduIdApi: EduIdApi) { null } + suspend fun removeConnection(linkedAccount: LinkedAccount): UserDetails? = try { + val response = eduIdApi.removeConnection(linkedAccount) + if (response.isSuccessful) { + response.body() + } else { + Timber.w( + "Failed to remove connection for [${response.code()}/${response.message()}]${ + response.errorBody()?.string() + }" + ) + null + } + } catch (e: Exception) { + Timber.e(e, "Failed to remove connection for ${linkedAccount.institutionIdentifier}") + null + } + suspend fun getInstitutionName(schac_home: String): String? = try { val response = eduIdApi.getInstitutionName(schac_home) if (response.isSuccessful) { diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt index 1e6356dd..3059c927 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt @@ -43,167 +43,125 @@ fun PersonalInfoScreen( onNameClicked: () -> Unit, onEmailClicked: () -> Unit, onRoleClicked: () -> Unit, - onInstitutionClicked: () -> Unit, onManageAccountClicked: (dateString: String) -> Unit, goBack: () -> Unit, +) = EduIdTopAppBar( + onBackClicked = goBack, ) { - val personalInfo by viewModel.personalInfo.observeAsState(PersonalInfo()) + val uiState by viewModel.uiState.observeAsState(UiState()) PersonalInfoScreenContent( + personalInfo = uiState.personalInfo, + isLoading = uiState.isLoading, + errorData = uiState.errorData, onNameClicked = onNameClicked, onEmailClicked = onEmailClicked, onRoleClicked = onRoleClicked, - onInstitutionClicked = onInstitutionClicked, + removeConnection = { index -> viewModel.removeConnection(index) }, onManageAccountClicked = onManageAccountClicked, - goBack = goBack, - personalInfo = personalInfo, ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun PersonalInfoScreenContent( + personalInfo: PersonalInfo, + isLoading: Boolean = false, + errorData: ErrorData? = null, onNameClicked: () -> Unit, onEmailClicked: () -> Unit, onRoleClicked: () -> Unit, - onInstitutionClicked: () -> Unit, + removeConnection: (Int) -> Unit, onManageAccountClicked: (dateString: String) -> Unit, - goBack: () -> Unit, - personalInfo: PersonalInfo, +) = Column( + verticalArrangement = Arrangement.Bottom, + modifier = Modifier + .verticalScroll(rememberScrollState()) ) { - Scaffold( - topBar = { - CenterAlignedTopAppBar( - modifier = Modifier.padding(top = 42.dp, start = 26.dp, end = 26.dp), - navigationIcon = { - Image( - painter = painterResource(R.drawable.back_button_icon), - contentDescription = "", - modifier = Modifier - .size(width = 46.dp, height = 46.dp) - .clickable { - goBack.invoke() - }, - alignment = Alignment.Center - ) - }, - title = { - Image( - painter = painterResource(R.drawable.ic_top_logo), - contentDescription = "", - modifier = Modifier.size(width = 122.dp, height = 46.dp), - alignment = Alignment.Center - ) - }, - ) - }, - ) { paddingValues -> - Column( - verticalArrangement = Arrangement.Bottom, - modifier = Modifier - .padding(paddingValues) - .padding(start = 26.dp, end = 26.dp) - .verticalScroll(rememberScrollState()) - ) { - Spacer(Modifier.height(36.dp)) - Text( - style = MaterialTheme.typography.titleLarge.copy( - textAlign = TextAlign.Start, - color = ButtonGreen - ), - text = stringResource(R.string.personal_info_title), - modifier = Modifier - .fillMaxWidth() - ) - Spacer(Modifier.height(12.dp)) - Text( - style = MaterialTheme.typography.bodyLarge.copy(textAlign = TextAlign.Start), - text = stringResource(R.string.personal_info_subtitle), - modifier = Modifier - .fillMaxWidth() - ) - Spacer(Modifier.height(12.dp)) - Text( - style = MaterialTheme.typography.titleLarge.copy( - textAlign = TextAlign.Start, - color = ButtonGreen, - fontSize = 20.sp - ), - text = stringResource(R.string.personal_info_info_header), - modifier = Modifier - .fillMaxWidth() - ) - Spacer(Modifier.height(12.dp)) - if (personalInfo.name.isBlank()) { - Spacer(Modifier.height(24.dp)) - CircularProgressIndicator( - modifier = Modifier - .height(80.dp) - .width(80.dp) - .align(alignment = Alignment.CenterHorizontally) - ) - } else { - InfoTab( - header = "Name", - title = personalInfo.name, - subtitle = "Provided by ${personalInfo.nameProvider}", - onClick = { }, - endIcon = R.drawable.shield_tick_blue - ) - InfoTab( - header = "Email", - title = personalInfo.email, - subtitle = "Provided by ${personalInfo.emailProvider}", - onClick = onEmailClicked, - endIcon = R.drawable.edit_icon - ) + Spacer(Modifier.height(36.dp)) + Text( + style = MaterialTheme.typography.titleLarge.copy( + textAlign = TextAlign.Start, color = ButtonGreen + ), + text = stringResource(R.string.personal_info_title), + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(12.dp)) + Text( + style = MaterialTheme.typography.bodyLarge.copy(textAlign = TextAlign.Start), + text = stringResource(R.string.personal_info_subtitle), + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(12.dp)) + Text( + style = MaterialTheme.typography.titleLarge.copy( + textAlign = TextAlign.Start, color = ButtonGreen, fontSize = 20.sp + ), + text = stringResource(R.string.personal_info_info_header), + modifier = Modifier.fillMaxWidth() + ) + if (isLoading) { + Spacer(modifier = Modifier.height(16.dp)) + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(16.dp)) + } + Spacer(Modifier.height(12.dp)) + InfoTab( + header = stringResource(R.string.infotab_name), + title = personalInfo.name, + subtitle = stringResource( + R.string.infotab_providedby, personalInfo.nameProvider + ), + onClick = { }, + endIcon = R.drawable.shield_tick_blue + ) + InfoTab( + header = stringResource(R.string.infotab_email), + title = personalInfo.email, + subtitle = stringResource( + R.string.infotab_providedby, personalInfo.emailProvider + ), + onClick = onEmailClicked, + endIcon = R.drawable.edit_icon + ) - personalInfo.institutionAccounts.forEachIndexed {index, it -> - InfoTab( - header = if (index < 1) "Role & institution" else "", - title = it.role, - subtitle = "At ${it.roleProvider}", - institutionInfo = it, - onClick = { }, - endIcon = R.drawable.chevron_down, - ) - } + personalInfo.institutionAccounts.forEachIndexed { index, account -> + InfoTab( + header = if (index < 1) stringResource(R.string.infotab_role_institution) else "", + title = account.role, + subtitle = stringResource(R.string.infotab_at, account.roleProvider), + institutionInfo = account, + onClick = {}, + onDeleteButtonClicked = { removeConnection(index) }, + endIcon = R.drawable.chevron_down, + ) + } - Spacer(Modifier.height(42.dp)) - Box( - modifier = Modifier - .clip(RoundedCornerShape(6.dp)) - .border( - width = 1.dp, - color = TextGrayScale - ) - .sizeIn(minHeight = 48.dp) - .fillMaxWidth() - .clickable { - onManageAccountClicked(personalInfo.dateCreated.getDateTimeString("EEEE, dd MMMM yyyy 'at' HH:MM")) - } - ) { - Image( - painter = painterResource(R.drawable.cog_icon), - contentDescription = "", - modifier = Modifier - .align(Alignment.CenterStart) - .padding(start = 12.dp) - ) - Text( - modifier = Modifier - .align(Alignment.Center), - text = "Manage your account", - style = MaterialTheme.typography.bodyLarge.copy( - textAlign = TextAlign.Start, - color = TextGrayScale, - fontWeight = FontWeight.SemiBold, - ), - ) - } - } - Spacer(Modifier.height(42.dp)) - } + Spacer(Modifier.height(42.dp)) + OutlinedButton( + onClick = { onManageAccountClicked(personalInfo.dateCreated.getDateTimeString("EEEE, dd MMMM yyyy 'at' HH:MM")) }, + shape = RoundedCornerShape(CornerSize(6.dp)), + modifier = Modifier + .sizeIn(minHeight = 48.dp) + .fillMaxWidth() + ) { + Image( + painter = painterResource(R.drawable.cog_icon), + alignment = CenterStart, + contentDescription = "", + modifier = Modifier + .padding(end = 48.dp) + ) + Text( + text = stringResource(R.string.personalinfo_manage_your_account), + style = MaterialTheme.typography.bodyLarge.copy( + textAlign = TextAlign.Start, + color = ButtonTextGrey, + fontWeight = FontWeight.SemiBold, + ), + ) } + Spacer(Modifier.height(42.dp)) } @@ -211,12 +169,10 @@ fun PersonalInfoScreenContent( @Composable private fun PreviewPersonalInfoScreenContent() = EduidAppAndroidTheme { PersonalInfoScreenContent( + personalInfo = PersonalInfo.demoData(), onNameClicked = {}, onEmailClicked = {}, onRoleClicked = {}, - onInstitutionClicked = {}, - goBack = {}, - personalInfo = PersonalInfo.demoData(), - onManageAccountClicked = {}, - ) + removeConnection = {}, + ) {} } \ No newline at end of file diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt index 883718b9..6d7c92df 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt @@ -7,45 +7,78 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import nl.eduid.ErrorData import nl.eduid.di.model.UserDetails +import timber.log.Timber import javax.inject.Inject @HiltViewModel class PersonalInfoViewModel @Inject constructor(private val repository: PersonalInfoRepository) : ViewModel() { - val personalInfo = MutableLiveData() + private var cachedUserDetails: UserDetails? = null + val uiState = MutableLiveData() init { viewModelScope.launch { - val userDetails = repository.getUserDetails() - if (userDetails != null) { - var uiData = convertToUiData(userDetails) - val nameMap = mutableMapOf() - for (account in userDetails.linkedAccounts) { - val mappedName = repository.getInstitutionName(account.schacHomeOrganization) - mappedName?.let { - //If name found, add to list of mapped names - nameMap[account.schacHomeOrganization] = mappedName - //Get name provider from FIRST linked account - if (account.schacHomeOrganization == userDetails.linkedAccounts.firstOrNull()?.schacHomeOrganization) { - uiData = uiData.copy( - nameProvider = nameMap[account.schacHomeOrganization] - ?: uiData.nameProvider - ) - } - //Update UI data to include mapped institution names - uiData = - uiData.copy(institutionAccounts = uiData.institutionAccounts.map { institution -> - institution.copy( - roleProvider = nameMap[institution.roleProvider] - ?: institution.roleProvider - ) - }) - personalInfo.postValue(uiData) - } + uiState.postValue(UiState(isLoading = true)) + cachedUserDetails = repository.getUserDetails() + cachedUserDetails?.let { details -> + val personalInfo = mapUserDetailsToPersonalInfo(details) + uiState.postValue(UiState(isLoading = false, personalInfo = personalInfo)) + } ?: uiState.postValue( + UiState( + isLoading = false, errorData = ErrorData( + "Failed to load data", "Could not load personal details." + ) + ) + ) + } + } + + private suspend fun mapUserDetailsToPersonalInfo(userDetails: UserDetails): PersonalInfo { + var personalInfo = convertToUiData(userDetails) + val nameMap = mutableMapOf() + for (account in userDetails.linkedAccounts) { + val mappedName = repository.getInstitutionName(account.schacHomeOrganization) + mappedName?.let { + //If name found, add to list of mapped names + nameMap[account.schacHomeOrganization] = mappedName + //Get name provider from FIRST linked account + if (account.schacHomeOrganization == userDetails.linkedAccounts.firstOrNull()?.schacHomeOrganization) { + personalInfo = personalInfo.copy( + nameProvider = nameMap[account.schacHomeOrganization] + ?: personalInfo.nameProvider + ) } - personalInfo.postValue(uiData) + //Update UI data to include mapped institution names + personalInfo = + personalInfo.copy(institutionAccounts = personalInfo.institutionAccounts.map { institution -> + institution.copy( + roleProvider = nameMap[institution.roleProvider] + ?: institution.roleProvider + ) + }) } } + return personalInfo + } + + fun removeConnection(index: Int) = viewModelScope.launch { + Timber.e("Launching remove for index $index") + val details = cachedUserDetails ?: return@launch + Timber.e("Cached details are non-null") + val currentUiState = uiState.value ?: UiState() + uiState.postValue(currentUiState.copy(isLoading = true)) + val linkedAccount = details.linkedAccounts[index] + val newDetails = repository.removeConnection(linkedAccount) + newDetails?.let { updatedDetails -> + cachedUserDetails = updatedDetails + val personalInfo = mapUserDetailsToPersonalInfo(updatedDetails) + uiState.postValue(currentUiState.copy(isLoading = false, personalInfo = personalInfo)) + } ?: uiState.postValue( + currentUiState.copy( + isLoading = false, + errorData = ErrorData("Failed to remove connection", "Could not remove connection") + ) + ) } private fun convertToUiData(userDetails: UserDetails): PersonalInfo { diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/UiState.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/UiState.kt new file mode 100644 index 00000000..712fc130 --- /dev/null +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/UiState.kt @@ -0,0 +1,9 @@ +package nl.eduid.screens.personalinfo + +import nl.eduid.ErrorData + +data class UiState( + val personalInfo: PersonalInfo = PersonalInfo(), + val isLoading: Boolean = false, + val errorData: ErrorData? = null, +) \ No newline at end of file diff --git a/app/src/main/kotlin/nl/eduid/ui/InfoTab.kt b/app/src/main/kotlin/nl/eduid/ui/InfoTab.kt index d01e07d6..320261eb 100644 --- a/app/src/main/kotlin/nl/eduid/ui/InfoTab.kt +++ b/app/src/main/kotlin/nl/eduid/ui/InfoTab.kt @@ -1,6 +1,5 @@ package nl.eduid.ui -import android.annotation.SuppressLint import androidx.annotation.DrawableRes import androidx.compose.animation.animateContentSize import androidx.compose.foundation.BorderStroke @@ -35,7 +34,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.capitalize import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import coil.compose.AsyncImage @@ -44,7 +42,6 @@ import nl.eduid.ui.theme.BlueText import nl.eduid.ui.theme.ButtonRed import nl.eduid.ui.theme.InfoTabDarkFill import nl.eduid.ui.theme.TextBlack -import java.text.SimpleDateFormat import java.util.* @Composable @@ -58,7 +55,6 @@ fun InfoTab( serviceProviderInfo: DataAndActivityData.Companion.Provider? = null, onDeleteButtonClicked: () -> Unit = { }, startIconLargeUrl: String = "", - @DrawableRes startIconSmall: Int = 0, @DrawableRes endIcon: Int = 0, ) { val isOpen = remember { mutableStateOf(false) } @@ -287,7 +283,7 @@ private fun InstitutionInfoBlock( .fillMaxWidth(), ) { Text( - text = "Remove connection", + text = stringResource(R.string.infotab_remove_connection), style = MaterialTheme.typography.bodyLarge.copy( color = ButtonRed, fontWeight = FontWeight.SemiBold ) @@ -302,7 +298,7 @@ private fun serviceProviderBlock( ): @Composable() (ColumnScope.() -> Unit) = { Text( - text = "Login Details", + text = stringResource(R.string.infotab_login_details), style = MaterialTheme.typography.bodyMedium.copy( textAlign = TextAlign.Start, color = BlueText, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a1c75393..cfca0999 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -192,5 +192,13 @@ Delete your account for all eternity? There is no way to undo this action If you wish to proceed, please type in your full name for confirmation. + Remove connection + Login Details + Name + Email + Provided by %s + At %s + + Manage your account From 69c404366f175484341a7818a23855f7168cc23b Mon Sep 17 00:00:00 2001 From: Iulia Stana Date: Tue, 11 Apr 2023 14:41:02 +0200 Subject: [PATCH 3/6] Edured-79: Make sure linked account is shown correctly in the flow of creating the edudid account on the app side. Removed unused lambdas for name & role clicking starting from the graph level. Email provider will always be the user, changed PersonalInfo to reflect that. It now also allows for a null name provider when there are no account linked to the eduid account. This will make the name provider default to the user. Make sure error messages are shown and cleared for both personal info and account linked screens. --- .../main/kotlin/nl/eduid/graphs/MainGraph.kt | 2 - .../accountlinked/AccountLinkedScreen.kt | 51 +++++++++++++++---- .../screens/personalinfo/PersonalInfoData.kt | 5 +- .../personalinfo/PersonalInfoScreen.kt | 46 +++++++++-------- .../personalinfo/PersonalInfoViewModel.kt | 11 ++-- app/src/main/res/values/strings.xml | 2 + 6 files changed, 75 insertions(+), 42 deletions(-) diff --git a/app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt b/app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt index c0b5d949..f22eb988 100644 --- a/app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt +++ b/app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt @@ -369,9 +369,7 @@ fun MainGraph( val viewModel = hiltViewModel(it) PersonalInfoScreen( viewModel = viewModel, - onNameClicked = { }, onEmailClicked = { navController.navigate(Graph.EDIT_EMAIL) }, - onRoleClicked = { }, onManageAccountClicked = { dateString -> navController.navigate( ManageAccountRoute.routeWithArgs( diff --git a/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt b/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt index d237a5d5..6f17a79c 100644 --- a/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -22,6 +23,7 @@ import nl.eduid.screens.personalinfo.PersonalInfo import nl.eduid.screens.personalinfo.PersonalInfoViewModel import nl.eduid.screens.personalinfo.UiState import nl.eduid.ErrorData +import nl.eduid.ui.AlertDialogWithSingleButton import nl.eduid.ui.EduIdTopAppBar import nl.eduid.ui.InfoTab import nl.eduid.ui.PrimaryButton @@ -38,7 +40,9 @@ fun AccountLinkedScreen( personalInfo = uiState.personalInfo, isLoading = uiState.isLoading, errorData = uiState.errorData, + dismissError = viewModel::clearErrorData, continueToHome = continueToHome, + removeConnection = { index -> viewModel.removeConnection(index) }, ) } @@ -47,13 +51,23 @@ private fun AccountLinkedContent( personalInfo: PersonalInfo, isLoading: Boolean = false, errorData: ErrorData? = null, + dismissError: () -> Unit = {}, continueToHome: () -> Unit = {}, + removeConnection: (Int) -> Unit = {}, ) = EduIdTopAppBar( withBackIcon = false ) { Column( modifier = Modifier.verticalScroll(rememberScrollState()) ) { + if (errorData != null) { + AlertDialogWithSingleButton( + title = errorData.title, + explanation = errorData.message, + buttonLabel = stringResource(R.string.button_ok), + onDismiss = dismissError + ) + } Spacer(Modifier.height(36.dp)) Text( style = MaterialTheme.typography.titleLarge.copy( @@ -74,23 +88,40 @@ private fun AccountLinkedContent( text = stringResource(R.string.account_linked_description), modifier = Modifier.fillMaxWidth() ) - Spacer(Modifier.height(12.dp)) - + if (isLoading) { + Spacer(modifier = Modifier.height(16.dp)) + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(16.dp)) + } else { + Spacer(Modifier.height(12.dp)) + } InfoTab( - header = "Name", + header = stringResource(R.string.infotab_fullname), title = personalInfo.name, - subtitle = "Provided by ${personalInfo.nameProvider}", + subtitle = if (personalInfo.nameProvider == null) { + stringResource( + R.string.infotab_providedby_you + ) + } else { + stringResource( + R.string.infotab_providedby, personalInfo.nameProvider + ) + }, onClick = { }, endIcon = R.drawable.shield_tick_blue ) - personalInfo.institutionAccounts.forEach { + personalInfo.institutionAccounts.forEachIndexed { index, account -> InfoTab( - header = "Role & institution", - title = it.role, - subtitle = it.institution, - onClick = { }, - endIcon = R.drawable.shield_tick_blue, + header = if (index < 1) stringResource(R.string.infotab_role_institution) else "", + title = account.role, + subtitle = stringResource(R.string.infotab_at, account.roleProvider), + institutionInfo = account, + onClick = {}, + onDeleteButtonClicked = { removeConnection(index) }, + endIcon = R.drawable.chevron_down, ) } PrimaryButton( diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoData.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoData.kt index 7b27c8e3..15979fe4 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoData.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoData.kt @@ -2,10 +2,9 @@ package nl.eduid.screens.personalinfo data class PersonalInfo( val name: String = "", - val nameProvider: String = "", + val nameProvider: String? = null, val nameStatus: InfoStatus = InfoStatus.Final, val email: String = "", - val emailProvider: String = "", val emailStatus: InfoStatus = InfoStatus.Final, val institutionAccounts: List = emptyList(), val dateCreated: Long = 0, @@ -27,7 +26,6 @@ data class PersonalInfo( nameProvider = "Universiteit van Amsterdam", nameStatus = InfoStatus.Final, email = "r.v.hamersdonksveer@uva.nl", - emailProvider = "You", emailStatus = InfoStatus.Editable, institutionAccounts = emptyList(), ) @@ -35,7 +33,6 @@ data class PersonalInfo( } sealed class InfoStatus { - object Empty : InfoStatus() object Editable : InfoStatus() object Final : InfoStatus() } diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt index 3059c927..dbec101f 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import nl.eduid.ErrorData import nl.eduid.R +import nl.eduid.ui.AlertDialogWithSingleButton import nl.eduid.ui.EduIdTopAppBar import nl.eduid.ui.InfoTab import nl.eduid.ui.getDateTimeString @@ -40,9 +41,7 @@ import nl.eduid.ui.theme.EduidAppAndroidTheme @Composable fun PersonalInfoScreen( viewModel: PersonalInfoViewModel, - onNameClicked: () -> Unit, onEmailClicked: () -> Unit, - onRoleClicked: () -> Unit, onManageAccountClicked: (dateString: String) -> Unit, goBack: () -> Unit, ) = EduIdTopAppBar( @@ -53,9 +52,8 @@ fun PersonalInfoScreen( personalInfo = uiState.personalInfo, isLoading = uiState.isLoading, errorData = uiState.errorData, - onNameClicked = onNameClicked, + dismissError = viewModel::clearErrorData, onEmailClicked = onEmailClicked, - onRoleClicked = onRoleClicked, removeConnection = { index -> viewModel.removeConnection(index) }, onManageAccountClicked = onManageAccountClicked, ) @@ -66,16 +64,24 @@ fun PersonalInfoScreenContent( personalInfo: PersonalInfo, isLoading: Boolean = false, errorData: ErrorData? = null, - onNameClicked: () -> Unit, - onEmailClicked: () -> Unit, - onRoleClicked: () -> Unit, - removeConnection: (Int) -> Unit, - onManageAccountClicked: (dateString: String) -> Unit, + dismissError: () -> Unit = {}, + onEmailClicked: () -> Unit = {}, + removeConnection: (Int) -> Unit = {}, + onManageAccountClicked: (dateString: String) -> Unit = {}, ) = Column( verticalArrangement = Arrangement.Bottom, modifier = Modifier .verticalScroll(rememberScrollState()) ) { + if (errorData != null) { + AlertDialogWithSingleButton( + title = errorData.title, + explanation = errorData.message, + buttonLabel = stringResource(R.string.button_ok), + onDismiss = dismissError + ) + } + Spacer(Modifier.height(36.dp)) Text( style = MaterialTheme.typography.titleLarge.copy( @@ -109,18 +115,22 @@ fun PersonalInfoScreenContent( InfoTab( header = stringResource(R.string.infotab_name), title = personalInfo.name, - subtitle = stringResource( - R.string.infotab_providedby, personalInfo.nameProvider - ), + subtitle = if (personalInfo.nameProvider == null) { + stringResource( + R.string.infotab_providedby_you + ) + } else { + stringResource( + R.string.infotab_providedby, personalInfo.nameProvider + ) + }, onClick = { }, endIcon = R.drawable.shield_tick_blue ) InfoTab( header = stringResource(R.string.infotab_email), title = personalInfo.email, - subtitle = stringResource( - R.string.infotab_providedby, personalInfo.emailProvider - ), + subtitle = stringResource(R.string.infotab_providedby_you), onClick = onEmailClicked, endIcon = R.drawable.edit_icon ) @@ -170,9 +180,5 @@ fun PersonalInfoScreenContent( private fun PreviewPersonalInfoScreenContent() = EduidAppAndroidTheme { PersonalInfoScreenContent( personalInfo = PersonalInfo.demoData(), - onNameClicked = {}, - onEmailClicked = {}, - onRoleClicked = {}, - removeConnection = {}, - ) {} + ) } \ No newline at end of file diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt index 6d7c92df..203e767d 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt @@ -7,7 +7,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import nl.eduid.ErrorData import nl.eduid.di.model.UserDetails -import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -61,10 +60,12 @@ class PersonalInfoViewModel @Inject constructor(private val repository: Personal return personalInfo } + fun clearErrorData() { + uiState.value = uiState.value?.copy(errorData = null) + } + fun removeConnection(index: Int) = viewModelScope.launch { - Timber.e("Launching remove for index $index") val details = cachedUserDetails ?: return@launch - Timber.e("Cached details are non-null") val currentUiState = uiState.value ?: UiState() uiState.postValue(currentUiState.copy(isLoading = true)) val linkedAccount = details.linkedAccounts[index] @@ -88,12 +89,11 @@ class PersonalInfoViewModel @Inject constructor(private val repository: Personal //Not sure if we should use the eduPersonAffiliations or the schacHomeOrganisation to get the institution name //val affiliation = linkedAccounts.firstOrNull()?.eduPersonAffiliations?.firstOrNull() //val nameProvider = affiliation?.substring(affiliation.indexOf("@"),affiliation.length) ?: "You" - val nameProvider = linkedAccounts.firstOrNull()?.schacHomeOrganization ?: "You" + val nameProvider = linkedAccounts.firstOrNull()?.schacHomeOrganization val name: String = linkedAccounts.firstOrNull()?.let { "${it.givenName} ${it.familyName}" } ?: "${userDetails.givenName} ${userDetails.familyName}" - val emailProvider = "You" val email: String = userDetails.email val institutionAccounts = linkedAccounts.mapNotNull { account -> @@ -120,7 +120,6 @@ class PersonalInfoViewModel @Inject constructor(private val repository: Personal nameProvider = nameProvider, nameStatus = PersonalInfo.InfoStatus.Final, email = email, - emailProvider = emailProvider, emailStatus = PersonalInfo.InfoStatus.Editable, institutionAccounts = institutionAccounts, dateCreated = dateCreated, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cfca0999..006405a9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -195,8 +195,10 @@ Remove connection Login Details Name + Full Name Email Provided by %s + Provided by you At %s Manage your account From 626d503da4793f6f0dfaa6095dde27ee58106eb3 Mon Sep 17 00:00:00 2001 From: Iulia Stana Date: Tue, 11 Apr 2023 14:44:24 +0200 Subject: [PATCH 4/6] Edured-79: When name is not verified by an institution via a linked account, then the name is "editable". --- .../nl/eduid/screens/personalinfo/PersonalInfoScreen.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt index dbec101f..06fbe1f9 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt @@ -125,7 +125,11 @@ fun PersonalInfoScreenContent( ) }, onClick = { }, - endIcon = R.drawable.shield_tick_blue + endIcon = if (personalInfo.nameProvider == null) { + R.drawable.edit_icon + } else { + R.drawable.shield_tick_blue + } ) InfoTab( header = stringResource(R.string.infotab_email), From 33f2d045fb0bf0bf2589705b81767291f0d45b96 Mon Sep 17 00:00:00 2001 From: Iulia Stana Date: Tue, 11 Apr 2023 15:08:08 +0200 Subject: [PATCH 5/6] Edured-79: Remove personal info screen from backstack when editing email --- app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt b/app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt index f22eb988..fd0aa6f2 100644 --- a/app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt +++ b/app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt @@ -369,7 +369,7 @@ fun MainGraph( val viewModel = hiltViewModel(it) PersonalInfoScreen( viewModel = viewModel, - onEmailClicked = { navController.navigate(Graph.EDIT_EMAIL) }, + onEmailClicked = { navController.goToWithPopCurrent(Graph.EDIT_EMAIL) }, onManageAccountClicked = { dateString -> navController.navigate( ManageAccountRoute.routeWithArgs( From 034c78d20d144549523dda513ebde17749a7232d Mon Sep 17 00:00:00 2001 From: Iulia Stana Date: Tue, 11 Apr 2023 15:21:37 +0200 Subject: [PATCH 6/6] Escape ampersand in string resource --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 006405a9..67fd2bb4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -200,7 +200,7 @@ Provided by %s Provided by you At %s - + Role & institution Manage your account