From 91344a2ca55cbdc3767e4a54cb27da1cc12224f2 Mon Sep 17 00:00:00 2001 From: Iulia Stana Date: Thu, 4 May 2023 19:02:04 +0200 Subject: [PATCH] Edured-103: Prepare to add enable/disable biometric calls. Refactoring expandable card so it can more easily separate functionality for expanded actions. ConnectionCard shows information for linked institutions. --- .../accountlinked/AccountLinkedScreen.kt | 19 +- .../screens/personalinfo/PersonalInfoData.kt | 1 - .../personalinfo/PersonalInfoScreen.kt | 29 +-- .../main/kotlin/nl/eduid/ui/ConnectionCard.kt | 217 ++++++++++++++++++ app/src/main/res/values-nl/strings.xml | 5 + app/src/main/res/values/strings.xml | 4 + 6 files changed, 255 insertions(+), 20 deletions(-) create mode 100644 app/src/main/kotlin/nl/eduid/ui/ConnectionCard.kt 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 8e01d253..eb1dfb0e 100644 --- a/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt @@ -22,9 +22,9 @@ import nl.eduid.R import nl.eduid.screens.personalinfo.PersonalInfo import nl.eduid.screens.personalinfo.PersonalInfoViewModel import nl.eduid.ui.AlertDialogWithSingleButton +import nl.eduid.ui.ConnectionCard import nl.eduid.ui.EduIdTopAppBar import nl.eduid.ui.InfoField -import nl.eduid.ui.InfoTab import nl.eduid.ui.PrimaryButton import nl.eduid.ui.theme.EduidAppAndroidTheme import nl.eduid.ui.theme.TextGreen @@ -109,15 +109,22 @@ private fun AccountLinkedContent( label = stringResource(R.string.infotab_fullname) ) Spacer(Modifier.height(16.dp)) + if (personalInfo.institutionAccounts.isNotEmpty()) { + Text( + text = stringResource(R.string.infotab_role_institution), + style = MaterialTheme.typography.bodyLarge.copy( + textAlign = TextAlign.Start, + fontWeight = FontWeight.SemiBold, + ), + ) + Spacer(Modifier.height(6.dp)) + } personalInfo.institutionAccounts.forEachIndexed { index, account -> - InfoTab( - header = if (index < 1) stringResource(R.string.infotab_role_institution) else "", + ConnectionCard( title = account.role, subtitle = stringResource(R.string.infotab_at, account.roleProvider), institutionInfo = account, - onClick = {}, - onDeleteButtonClicked = { removeConnection(index) }, - endIcon = R.drawable.chevron_down, + onRemoveConnection = { removeConnection(index) }, ) } 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 c49ca035..37ecba46 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoData.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoData.kt @@ -19,7 +19,6 @@ data class PersonalInfo( val roleProvider: String, val institution: String, val affiliationString: String, - val status: InfoStatus = InfoStatus.Final, val createdStamp: Long, val expiryStamp: Long, ) 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 5f5ddbca..411f9748 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt @@ -37,9 +37,9 @@ import nl.eduid.ErrorData import nl.eduid.R import nl.eduid.screens.firsttimedialog.LinkAccountContract import nl.eduid.ui.AlertDialogWithSingleButton +import nl.eduid.ui.ConnectionCard import nl.eduid.ui.EduIdTopAppBar import nl.eduid.ui.InfoField -import nl.eduid.ui.InfoTab import nl.eduid.ui.getDateTimeString import nl.eduid.ui.theme.ButtonGreen import nl.eduid.ui.theme.ButtonTextGrey @@ -140,19 +140,15 @@ fun PersonalInfoScreenContent( } Spacer(Modifier.height(12.dp)) InfoField( - title = personalInfo.name, - subtitle = if (personalInfo.nameProvider == null) { + title = personalInfo.name, subtitle = if (personalInfo.nameProvider == null) { stringResource(R.string.infotab_providedby_you) } else { stringResource(R.string.infotab_providedby, personalInfo.nameProvider) - }, - onClick = onNameClicked, - endIcon = if (personalInfo.nameProvider == null) { + }, onClick = onNameClicked, endIcon = if (personalInfo.nameProvider == null) { R.drawable.edit_icon } else { R.drawable.shield_tick_blue - }, - label = stringResource(R.string.infotab_name) + }, label = stringResource(R.string.infotab_name) ) Spacer(Modifier.height(16.dp)) InfoField( @@ -163,15 +159,22 @@ fun PersonalInfoScreenContent( label = stringResource(R.string.infotab_email), ) Spacer(Modifier.height(16.dp)) + if (personalInfo.institutionAccounts.isNotEmpty()) { + Text( + text = stringResource(R.string.infotab_role_institution), + style = MaterialTheme.typography.bodyLarge.copy( + textAlign = TextAlign.Start, + fontWeight = FontWeight.SemiBold, + ), + ) + Spacer(Modifier.height(6.dp)) + } personalInfo.institutionAccounts.forEachIndexed { index, account -> - InfoTab( - header = if (index < 1) stringResource(R.string.infotab_role_institution) else "", + ConnectionCard( title = account.role, subtitle = stringResource(R.string.infotab_at, account.roleProvider), institutionInfo = account, - onClick = {}, - onDeleteButtonClicked = { removeConnection(index) }, - endIcon = R.drawable.chevron_down, + onRemoveConnection = { removeConnection(index) }, ) } diff --git a/app/src/main/kotlin/nl/eduid/ui/ConnectionCard.kt b/app/src/main/kotlin/nl/eduid/ui/ConnectionCard.kt new file mode 100644 index 00000000..9e75e7a7 --- /dev/null +++ b/app/src/main/kotlin/nl/eduid/ui/ConnectionCard.kt @@ -0,0 +1,217 @@ +package nl.eduid.ui + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Divider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +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 androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension +import nl.eduid.R +import nl.eduid.screens.personalinfo.PersonalInfo +import nl.eduid.ui.theme.BlueButton +import nl.eduid.ui.theme.BlueText +import nl.eduid.ui.theme.ButtonRed +import nl.eduid.ui.theme.EduidAppAndroidTheme +import nl.eduid.ui.theme.TextBlack +import nl.eduid.ui.theme.TextGrayScale +import java.util.Locale + +@Composable +fun ConnectionCard( + title: String, + subtitle: String, + institutionInfo: PersonalInfo.InstitutionAccount? = null, + isExpanded: Boolean = false, + onRemoveConnection: (id: String) -> Unit = { }, +) { + val isOpen = remember { mutableStateOf(isExpanded) } + Spacer(Modifier.height(6.dp)) + Box(modifier = Modifier + .clip(RoundedCornerShape(6.dp)) + .border( + width = 3.dp, color = BlueButton + ) + .sizeIn(minHeight = 72.dp) + .fillMaxWidth() + .clickable { + if (institutionInfo != null) { + isOpen.value = !isOpen.value + } + } + .animateContentSize()) { + ConstraintLayout( + modifier = Modifier + .fillMaxWidth() + .padding(start = 18.dp, end = 18.dp, top = 12.dp, bottom = 12.dp) + ) { + val (titleArea, endImage, expandedArea) = createRefs() + + Column(horizontalAlignment = Alignment.Start, + modifier = Modifier.constrainAs(titleArea) { + top.linkTo(parent.top) + start.linkTo(parent.start) + end.linkTo(endImage.start) + width = Dimension.fillToConstraints + }) { + Text( + text = title.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }, + style = MaterialTheme.typography.bodyLarge.copy( + textAlign = TextAlign.Start, + fontWeight = FontWeight.Bold, + lineHeight = 20.sp + ), + ) + Spacer(Modifier.height(4.dp)) + Text( + text = subtitle, + style = MaterialTheme.typography.bodySmall.copy( + textAlign = TextAlign.Start, + color = TextGrayScale, + ), + ) + } + if (isOpen.value && institutionInfo != null) { + Column(horizontalAlignment = Alignment.Start, + modifier = Modifier.constrainAs(expandedArea) { + top.linkTo(titleArea.bottom, margin = 24.dp) + start.linkTo(parent.start) + end.linkTo(parent.end) + }) { + InstitutionInfoBlock(institutionInfo, onRemoveConnection) + } + } + Image(painter = painterResource(R.drawable.chevron_down), + contentDescription = "", + modifier = Modifier + .constrainAs(endImage) { + top.linkTo(titleArea.top) + bottom.linkTo(titleArea.bottom) + end.linkTo(parent.end) + } + .padding(start = 12.dp)) + } + } + Spacer(Modifier.height(16.dp)) +} + +@Composable +private fun InstitutionInfoBlock( + institutionInfo: PersonalInfo.InstitutionAccount, + onDeleteButtonClicked: (id: String) -> Unit, +) = Column( + Modifier.fillMaxWidth() +) { + InfoRow( + label = stringResource( + R.string.personalinfo_verified_by_on, + institutionInfo.institution, + institutionInfo.createdStamp.getDateString() + ) + ) + InfoRow( + label = stringResource(R.string.personalinfo_institution), + value = institutionInfo.institution + ) + InfoRow( + label = stringResource(R.string.personalinfo_affiliations), + value = institutionInfo.affiliationString + ) + InfoRow( + label = stringResource(R.string.personalinfo_expires), + value = institutionInfo.expiryStamp.getDateString() + ) + Button( + shape = RoundedCornerShape(CornerSize(6.dp)), + onClick = { onDeleteButtonClicked(institutionInfo.id) }, + border = BorderStroke(1.dp, Color.Red), + colors = ButtonDefaults.outlinedButtonColors(contentColor = ButtonRed), + modifier = Modifier + .sizeIn(minHeight = 48.dp) + .fillMaxWidth(), + ) { + Text( + text = stringResource(R.string.infotab_remove_connection), + style = MaterialTheme.typography.bodyLarge.copy( + color = ButtonRed, fontWeight = FontWeight.SemiBold + ) + ) + } +} + +@Composable +private fun InfoRow(label: String, value: String = "") { + Row( + modifier = Modifier.fillMaxWidth() + ) { + Text( + modifier = Modifier.weight(1f), + text = label, + style = MaterialTheme.typography.bodyMedium.copy( + textAlign = TextAlign.Start, + color = BlueText, + ), + ) + if (value.isNotEmpty()) { + Text( + modifier = Modifier.weight(1f), + text = value, + style = MaterialTheme.typography.bodyMedium.copy( + textAlign = TextAlign.Start, + color = BlueText, + ), + ) + } + } + Spacer(Modifier.height(12.dp)) + Divider(color = TextBlack, thickness = 1.dp) + Spacer(Modifier.height(12.dp)) +} + +@Preview +@Composable +private fun PreviewConnectionCard() = EduidAppAndroidTheme { + ConnectionCard( + title = "Librarian", + subtitle = "Urangutan", + institutionInfo = PersonalInfo.InstitutionAccount( + id = "id", + role = "Librarian", + roleProvider = "Library", + institution = "Unseen University", + affiliationString = "Librarian", + createdStamp = 0L, + expiryStamp = 0L + ), + isExpanded = true + ) +} \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index a7c21f26..683a1e72 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -236,6 +236,11 @@ Beheer jouw gegevens Voeg een rol & instelling toe Voeg dit toe via SURFConext + Geverifieerd door %1$s op %2$s. + Instelling + Aansluiting(en) + Link verloopt + Verwijder inloggegevens * Verwijder sleutel diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7de29b51..50720f73 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -236,6 +236,10 @@ Manage your account Add role & institution Proceed to add this via SURFConext + Verified by %1$s on %2$s. + Institution + Affiliation(s) + Link expires Delete login details * Delete key