From d7e390bb8102eff79976e60fcd57198e998a3d05 Mon Sep 17 00:00:00 2001 From: Iulia Stana Date: Thu, 25 May 2023 09:25:01 +0200 Subject: [PATCH 1/2] Edured-101: Changing the screens layout so that when the keyboard is visible, any input fields and/or buttons are still visible. To make this happen, the top bar will exclude handling the window insets for the navigation bars and ime and pass the content padding like the standard scaffold. Moreover, the keyboard state can be observed as a state and elements in the screen can be hidden/made visible based on the keyboard state. This is preferable over handling it via focusevents because the focus request/status are asynchronous and can easily result in an unwanted state when there is more than one focusable input field. Button must require specific height, otherwise height becomes 0 when keyboard is shown. Screens impacted: DeleteAccountFirstConfirmScreen, ManageAccountScreen, OauthScreen, RequestEduIdEmailSentScreen, RequestEduIdStartScreen, SecurityScreen, WelcomeStartScreen, TwoFactorKeyScreen, TwoFactorKeyDeleteScreen, RequestAuthenticationScreen, DeepLinkScreen, AuthenticationCompletedScreen, PersonalInfoScreen, DataAndActivityScreen,RequestEduIdCreatedScreen, EnableBiometricScreen Automatically showing keyboard, focus on input field, submit button(s) visible: DeleteAccountSecondConfirmScreen, EditNameScreen, PhoneRequestCodeScreen, RequestEduIdFormScreen, ConfirmCodeScreen(Added automatic paste of the sms code. That is why the keyboard is not automatically shown for the confirm sms code screen, it's either or.), ResetPasswordConfirmScreen(includes animating visibility for title & spacing to make room for keyboard & button), RequestEduIdCreatedScreen, AuthenticationPinBiometricScreen: show keyboard when biometric is skipped or fails. Makes sure buttons are visible above the keyboard. Making sure the PIN input during enrollment is also correct. Other fixes: Fixed correctly showing error messages for during registration Correct the ripple animation by applying the padding to the right elements Fix issue with capitalized letter for email field Edured-110: only show number's keyboard on the PIN input screens --- app/src/main/AndroidManifest.xml | 3 +- .../kotlin/nl/eduid/MainComposeActivity.kt | 2 + .../main/kotlin/nl/eduid/graphs/MainGraph.kt | 12 +- .../accountlinked/AccountLinkedScreen.kt | 170 ++++++------ .../AuthenticationCompletedScreen.kt | 27 +- .../AuthenticationPinBiometricScreen.kt | 57 +++- .../authorize/RequestAuthenticationScreen.kt | 82 +++--- .../biometric/EnableBiometricScreen.kt | 42 +-- .../created/RequestEduIdCreatedScreen.kt | 83 +++--- .../dataactivity/DataAndActivityScreen.kt | 39 +-- .../dataactivity/DeleteServiceContent.kt | 14 +- .../eduid/screens/deeplinks/DeepLinkScreen.kt | 67 +++-- .../DeleteAccountFirstConfirmScreen.kt | 38 ++- .../DeleteAccountSecondConfirmScreen.kt | 148 +++++++---- .../eduid/screens/editname/EditNameScreen.kt | 15 +- .../firsttimedialog/FirstTimeDialogScreen.kt | 2 +- .../manageaccount/ManageAccountScreen.kt | 46 ++-- .../nl/eduid/screens/oauth/OAuthScreen.kt | 24 +- .../personalinfo/PersonalInfoScreen.kt | 47 ++-- .../nl/eduid/screens/pinsetup/PinContent.kt | 56 ++-- .../pinsetup/RegistrationPinSetupScreen.kt | 14 +- .../recovery/confirmsms/ConfirmCodeScreen.kt | 118 +++++---- .../requestsms/PhoneRequestCodeScreen.kt | 32 ++- .../RequestEduIdFormScreen.kt | 243 ++++++++++-------- .../RequestEduIdFormViewModel.kt | 118 ++++----- .../RequestEduIdEmailSentScreen.kt | 53 ++-- .../requestidstart/RequestEduIdStartScreen.kt | 48 ++-- .../resetpassword/ResetPasswordScreen.kt | 84 +++--- .../ResetPasswordConfirmScreen.kt | 196 ++++++++------ .../eduid/screens/security/SecurityScreen.kt | 20 +- .../eduid/screens/start/WelcomeStartScreen.kt | 37 ++- .../twofactorkey/TwoFactorKeyScreen.kt | 60 +++-- .../TwoFactorKeyDeleteScreen.kt | 67 +++-- app/src/main/kotlin/nl/eduid/ui/Buttons.kt | 17 +- .../main/kotlin/nl/eduid/ui/EduIdTopAppBar.kt | 41 +-- app/src/main/kotlin/nl/eduid/ui/Ime.kt | 24 ++ app/src/main/kotlin/nl/eduid/ui/InfoField.kt | 7 +- .../main/kotlin/nl/eduid/ui/PinInputField.kt | 65 +++-- .../main/kotlin/nl/eduid/ui/theme/Theme.kt | 10 +- 39 files changed, 1326 insertions(+), 902 deletions(-) create mode 100644 app/src/main/kotlin/nl/eduid/ui/Ime.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5a6aec80..484a7cc0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,7 +28,8 @@ android:label="@string/app_name" android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/ic_launcher_round" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:windowSoftInputMode="adjustResize"> Unit, +) = EduIdTopAppBar( + withBackIcon = false ) { - AccountLinkedContent( - personalInfo = viewModel.uiState.personalInfo, - isLoading = viewModel.uiState.isLoading, - errorData = viewModel.uiState.errorData, - dismissError = viewModel::clearErrorData, - continueToHome = continueToHome, - removeConnection = { index -> viewModel.removeConnection(index) }, - ) + Column( + modifier = Modifier + .fillMaxSize() + .padding(it) + ) { + AccountLinkedContent( + personalInfo = viewModel.uiState.personalInfo, + isLoading = viewModel.uiState.isLoading, + errorData = viewModel.uiState.errorData, + dismissError = viewModel::clearErrorData, + continueToHome = continueToHome, + removeConnection = { index -> viewModel.removeConnection(index) }, + ) + } } @Composable @@ -52,88 +63,85 @@ private fun AccountLinkedContent( dismissError: () -> Unit = {}, continueToHome: () -> Unit = {}, removeConnection: (Int) -> Unit = {}, -) = EduIdTopAppBar( - withBackIcon = false +) = Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp) ) { - Column( - modifier = Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()) - ) { - if (errorData != null) { - val context = LocalContext.current - AlertDialogWithSingleButton( - title = errorData.title(context), - explanation = errorData.message(context), - buttonLabel = stringResource(R.string.button_ok), - onDismiss = dismissError - ) - } - Spacer(Modifier.height(36.dp)) - Text( - style = MaterialTheme.typography.titleLarge.copy( - textAlign = TextAlign.Start, color = TextGreen - ), - text = stringResource(R.string.account_linked_title), - modifier = Modifier.fillMaxWidth() + if (errorData != null) { + val context = LocalContext.current + AlertDialogWithSingleButton( + title = errorData.title(context), + explanation = errorData.message(context), + buttonLabel = stringResource(R.string.button_ok), + onDismiss = dismissError ) - Spacer(Modifier.height(12.dp)) - Text( - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.SemiBold), - text = stringResource(R.string.account_linked_subtitle), + } + Spacer(Modifier.height(36.dp)) + Text( + style = MaterialTheme.typography.titleLarge.copy( + textAlign = TextAlign.Start, color = TextGreen + ), + text = stringResource(R.string.account_linked_title), + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(12.dp)) + Text( + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.SemiBold), + text = stringResource(R.string.account_linked_subtitle), + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(12.dp)) + Text( + style = MaterialTheme.typography.bodyLarge, + text = stringResource(R.string.account_linked_description), + modifier = Modifier.fillMaxWidth() + ) + if (isLoading) { + Spacer(modifier = Modifier.height(16.dp)) + LinearProgressIndicator( modifier = Modifier.fillMaxWidth() ) + Spacer(modifier = Modifier.height(16.dp)) + } else { Spacer(Modifier.height(12.dp)) - Text( - style = MaterialTheme.typography.bodyLarge, - text = stringResource(R.string.account_linked_description), - modifier = Modifier.fillMaxWidth() - ) - if (isLoading) { - Spacer(modifier = Modifier.height(16.dp)) - LinearProgressIndicator( - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(16.dp)) + } + InfoField( + title = personalInfo.name, + subtitle = if (personalInfo.nameProvider == null) { + stringResource(R.string.infotab_providedby_you) } else { - Spacer(Modifier.height(12.dp)) - } - InfoField( - title = personalInfo.name, - subtitle = if (personalInfo.nameProvider == null) { - stringResource(R.string.infotab_providedby_you) - } else { - stringResource(R.string.infotab_providedby, personalInfo.nameProvider) - }, - endIcon = R.drawable.shield_tick_blue, - label = stringResource(R.string.infotab_fullname) + stringResource(R.string.infotab_providedby, personalInfo.nameProvider) + }, + endIcon = R.drawable.shield_tick_blue, + 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(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 -> - ConnectionCard( - title = account.role, - subtitle = stringResource(R.string.infotab_at, account.roleProvider), - institutionInfo = account, - onRemoveConnection = { removeConnection(index) }, - ) - } - PrimaryButton( - text = stringResource(R.string.button_continue), - onClick = continueToHome, - modifier = Modifier.fillMaxWidth(), + Spacer(Modifier.height(6.dp)) + } + personalInfo.institutionAccounts.forEachIndexed { index, account -> + ConnectionCard( + title = account.role, + subtitle = stringResource(R.string.infotab_at, account.roleProvider), + institutionInfo = account, + onRemoveConnection = { removeConnection(index) }, ) - Spacer(Modifier.height(40.dp)) } + PrimaryButton( + text = stringResource(R.string.button_continue), + onClick = continueToHome, + modifier = Modifier + .fillMaxWidth() , + ) } diff --git a/app/src/main/kotlin/nl/eduid/screens/authorize/AuthenticationCompletedScreen.kt b/app/src/main/kotlin/nl/eduid/screens/authorize/AuthenticationCompletedScreen.kt index e2151f5d..f017afeb 100644 --- a/app/src/main/kotlin/nl/eduid/screens/authorize/AuthenticationCompletedScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/authorize/AuthenticationCompletedScreen.kt @@ -3,10 +3,11 @@ package nl.eduid.screens.authorize 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.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -25,21 +26,25 @@ import nl.eduid.ui.PrimaryButton import nl.eduid.ui.theme.EduidAppAndroidTheme import nl.eduid.ui.theme.TextGreen import nl.eduid.ui.theme.findActivity -import timber.log.Timber @Composable fun AuthenticationCompletedScreen(goHome: () -> Unit = {}) = EduIdTopAppBar( withBackIcon = false ) { - val activity = LocalContext.current.findActivity() - Column(modifier = Modifier.fillMaxSize()) { + val context = LocalContext.current + Column( + modifier = Modifier + .fillMaxSize() + .padding(it) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.SpaceBetween + ) { Text( style = MaterialTheme.typography.titleLarge.copy( textAlign = TextAlign.Start, color = TextGreen ), text = stringResource(R.string.authorize_title), modifier = Modifier.fillMaxWidth() ) Column( - modifier = Modifier.weight(1f), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { @@ -60,15 +65,17 @@ fun AuthenticationCompletedScreen(goHome: () -> Unit = {}) = EduIdTopAppBar( ) } PrimaryButton( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .weight(1f, false) + .navigationBarsPadding() + .padding(bottom = 24.dp), text = stringResource(R.string.button_ok), onClick = { - Timber.e("Finishing activity: ${activity.hashCode()}") goHome() - activity.finish() + context.findActivity().finish() }, ) - Spacer(Modifier.height(24.dp)) } } diff --git a/app/src/main/kotlin/nl/eduid/screens/authorize/AuthenticationPinBiometricScreen.kt b/app/src/main/kotlin/nl/eduid/screens/authorize/AuthenticationPinBiometricScreen.kt index 5fbddd2e..a12e5c41 100644 --- a/app/src/main/kotlin/nl/eduid/screens/authorize/AuthenticationPinBiometricScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/authorize/AuthenticationPinBiometricScreen.kt @@ -3,11 +3,15 @@ package nl.eduid.screens.authorize import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -21,6 +25,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner @@ -60,6 +65,7 @@ fun AuthenticationPinBiometricScreen( AuthenticationPinBiometricContent( shouldPromptBiometric = context.biometricUsable() && authChallenge?.identity?.biometricInUse == true, challengeComplete = challengeComplete, + padding = it, onBiometricResult = { biometricSignIn -> viewModel.authenticateWithBiometric(biometricSignIn) }, @@ -71,7 +77,7 @@ fun AuthenticationPinBiometricScreen( goToAuthenticationComplete(authChallenge, pin) }, clearCompleteChallenge = viewModel::clearCompleteChallenge, - goHomeOnFail = onCancel + goHomeOnFail = onCancel, ) } @@ -80,6 +86,7 @@ private fun AuthenticationPinBiometricContent( shouldPromptBiometric: Boolean, challengeComplete: ChallengeCompleteResult? = null, isPinInvalid: Boolean = false, + padding: PaddingValues = PaddingValues(), onBiometricResult: (BiometricSignIn) -> Unit = {}, submitPin: (String) -> Unit = {}, onCancel: () -> Unit = {}, @@ -89,6 +96,7 @@ private fun AuthenticationPinBiometricContent( ) { var isCheckingSecret by rememberSaveable { mutableStateOf(false) } var pinValue by rememberSaveable { mutableStateOf("") } + var shouldShowKeyboard by remember { mutableStateOf(!shouldPromptBiometric) } val owner = LocalLifecycleOwner.current if (isCheckingSecret && challengeComplete != null) { @@ -157,8 +165,13 @@ private fun AuthenticationPinBiometricContent( val launchBiometricSignIn = rememberLauncherForActivityResult( contract = SignInWithBiometricsContract(), ) { result -> - isCheckingSecret = true - onBiometricResult(result) + when (result) { + is BiometricSignIn.Failed -> shouldShowKeyboard = true + is BiometricSignIn.Success -> { + isCheckingSecret = true + onBiometricResult(result) + } + } } if (!hasAutoRequestedBiometrics) { SideEffect { @@ -167,28 +180,46 @@ private fun AuthenticationPinBiometricContent( } } } - Column(modifier = Modifier.fillMaxSize()) { - Text( - style = MaterialTheme.typography.titleLarge.copy( - textAlign = TextAlign.Start, color = TextGreen - ), text = stringResource(R.string.auth_pin_title), modifier = Modifier.fillMaxWidth() - ) + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.SpaceBetween + ) { Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.Start, + modifier = Modifier + .fillMaxWidth() ) { + Text( + style = MaterialTheme.typography.titleLarge.copy( + textAlign = TextAlign.Start, color = TextGreen + ), + text = stringResource(R.string.auth_pin_title), + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(36.dp)) PinInputField(label = stringResource(R.string.auth_pin_subtitle), pinCode = pinValue, isPinInvalid = isPinInvalid, + shouldShowKeyboard = shouldShowKeyboard, modifier = Modifier.fillMaxWidth(), onPinChange = { newValue -> pinValue = newValue }, submitPin = { isCheckingSecret = true submitPin(pinValue) }) + } Row( - horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxWidth() + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier + .fillMaxWidth() + .imePadding() + .navigationBarsPadding() + .padding(bottom = 24.dp) ) { SecondaryButton( modifier = Modifier.widthIn(min = 140.dp), @@ -204,8 +235,6 @@ private fun AuthenticationPinBiometricContent( }, ) } - Spacer(Modifier.height(24.dp)) - } } diff --git a/app/src/main/kotlin/nl/eduid/screens/authorize/RequestAuthenticationScreen.kt b/app/src/main/kotlin/nl/eduid/screens/authorize/RequestAuthenticationScreen.kt index 438d96eb..f2927668 100644 --- a/app/src/main/kotlin/nl/eduid/screens/authorize/RequestAuthenticationScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/authorize/RequestAuthenticationScreen.kt @@ -2,11 +2,13 @@ package nl.eduid.screens.authorize import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -39,22 +41,29 @@ fun RequestAuthenticationScreen( val authChallenge by viewModel.challenge.observeAsState(null) RequestAuthenticationContent( loginToService = authChallenge?.serviceProviderDisplayName, + padding = it, onLogin = { onLogin(authChallenge) }, - onCancel = onCancel + onCancel = onCancel, ) } @Composable private fun RequestAuthenticationContent( loginToService: String?, + padding: PaddingValues = PaddingValues(), onLogin: () -> Unit = {}, onCancel: () -> Unit = {}, ) { if (loginToService == null) { - } else { - Column(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.SpaceBetween + ) { Text( style = MaterialTheme.typography.titleLarge.copy( textAlign = TextAlign.Start, color = TextGreen @@ -62,41 +71,41 @@ private fun RequestAuthenticationContent( text = stringResource(R.string.authorize_title), modifier = Modifier.fillMaxWidth() ) - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.Center, - ) { - val loginQuestion = buildAnnotatedString { - pushStyle( - MaterialTheme.typography.titleLarge.copy( - color = TextGreen - ).toSpanStyle() - ) - append(stringResource(R.string.authorize_subtitle01)) - pop() - append("\n") - pushStyle( - MaterialTheme.typography.titleLarge.copy( - textAlign = TextAlign.Center - ).toSpanStyle() - ) - append( - stringResource( - R.string.authorize_subtitle02, loginToService - ) + val loginQuestion = buildAnnotatedString { + pushStyle( + MaterialTheme.typography.titleLarge.copy( + color = TextGreen + ).toSpanStyle() + ) + append(stringResource(R.string.authorize_subtitle01)) + pop() + append("\n") + pushStyle( + MaterialTheme.typography.titleLarge.copy( + textAlign = TextAlign.Center + ).toSpanStyle() + ) + append( + stringResource( + R.string.authorize_subtitle02, loginToService ) - } - - Text( - text = loginQuestion, - modifier = Modifier - .fillMaxWidth() - .align(Alignment.CenterHorizontally), - textAlign = TextAlign.Center ) } + + Text( + text = loginQuestion, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.CenterHorizontally), + textAlign = TextAlign.Center + ) Row( - horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxWidth() + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .weight(1f, false) + .padding(bottom = 24.dp) ) { SecondaryButton( modifier = Modifier.widthIn(min = 140.dp), @@ -109,7 +118,6 @@ private fun RequestAuthenticationContent( onClick = onLogin, ) } - Spacer(Modifier.height(24.dp)) } } } diff --git a/app/src/main/kotlin/nl/eduid/screens/biometric/EnableBiometricScreen.kt b/app/src/main/kotlin/nl/eduid/screens/biometric/EnableBiometricScreen.kt index d96d260f..617df4df 100644 --- a/app/src/main/kotlin/nl/eduid/screens/biometric/EnableBiometricScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/biometric/EnableBiometricScreen.kt @@ -3,12 +3,24 @@ package nl.eduid.screens.biometric import androidx.activity.compose.BackHandler import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale @@ -20,8 +32,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import nl.eduid.R -import nl.eduid.ui.PrimaryButton import nl.eduid.ui.EduIdTopAppBar +import nl.eduid.ui.PrimaryButton import nl.eduid.ui.SecondaryButton import nl.eduid.ui.theme.EduidAppAndroidTheme @@ -44,6 +56,7 @@ fun EnableBiometricScreen( ) { EnableBiometricContent( nextStep = nextStep, + padding = it, goToNext = goToNext, enable = { viewModel.upgradeBiometric() @@ -57,6 +70,7 @@ fun EnableBiometricScreen( @Composable fun EnableBiometricContent( nextStep: Boolean?, + padding: PaddingValues = PaddingValues(), goToNext: (Boolean) -> Unit = {}, enable: () -> Unit = {}, skip: () -> Unit = {}, @@ -73,7 +87,11 @@ fun EnableBiometricContent( } ConstraintLayout( - modifier = Modifier.fillMaxSize() + modifier = Modifier + .fillMaxSize() + .padding(padding) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp) ) { val (title, bodySpacing, description, background, backgroundFront, buttons) = createRefs() val contentTopSpacing = createGuidelineFromTop(40.dp) @@ -127,28 +145,22 @@ fun EnableBiometricContent( .fillMaxWidth() .constrainAs(buttons) { top.linkTo(background.bottom) - bottom.linkTo(parent.bottom, margin = 40.dp) + bottom.linkTo(parent.bottom) }) { PrimaryButton( - text = stringResource(R.string.biometric_enable_allow), - onClick = { + text = stringResource(R.string.biometric_enable_allow), onClick = { enable() inProgress = true - }, - modifier = Modifier + }, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 32.dp) ) Spacer(Modifier.height(24.dp)) SecondaryButton( - text = stringResource(R.string.biometric_enable_skip), - onClick = { + text = stringResource(R.string.biometric_enable_skip), onClick = { skip() inProgress = true - }, - modifier = Modifier + }, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 32.dp) ) } diff --git a/app/src/main/kotlin/nl/eduid/screens/created/RequestEduIdCreatedScreen.kt b/app/src/main/kotlin/nl/eduid/screens/created/RequestEduIdCreatedScreen.kt index 103ed7cb..7f33e3dd 100644 --- a/app/src/main/kotlin/nl/eduid/screens/created/RequestEduIdCreatedScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/created/RequestEduIdCreatedScreen.kt @@ -1,11 +1,14 @@ package nl.eduid.screens.created 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.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme @@ -54,19 +57,27 @@ fun RequestEduIdCreatedScreen( ) } - if (!justCreated) { - RequestEduIdFailedCreationContent() - } else { - RequestEduIdCreatedContent( - uiState = viewModel.uiState, - goToOAuth = { - goToOAuth() - viewModel.clearPromptForAuthTrigger() - }, - startEnrollment = viewModel::startEnrollmentAfterAccountCreation - ) { challenge -> - goToRegistrationPinSetup(challenge) - viewModel.clearCurrentChallenge() + Column( + modifier = Modifier + .fillMaxSize() + .padding(it) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp) + ) { + if (!justCreated) { + RequestEduIdFailedCreationContent() + } else { + RequestEduIdCreatedContent( + uiState = viewModel.uiState, + goToOAuth = { + goToOAuth() + viewModel.clearPromptForAuthTrigger() + }, + startEnrollment = viewModel::startEnrollmentAfterAccountCreation + ) { challenge -> + goToRegistrationPinSetup(challenge) + viewModel.clearCurrentChallenge() + } } } } @@ -77,8 +88,11 @@ private fun RequestEduIdCreatedContent( goToOAuth: () -> Unit = {}, startEnrollment: () -> Unit = {}, goToRegistrationPinSetup: (EnrollmentChallenge) -> Unit = { _ -> }, -) = Column(modifier = Modifier.fillMaxSize()) { - +) = Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween +) { var waitingForVmEvent by rememberSaveable { mutableStateOf(false) } val owner = LocalLifecycleOwner.current if (waitingForVmEvent && uiState.promptForAuth != null) { @@ -99,9 +113,9 @@ private fun RequestEduIdCreatedContent( } Column( + horizontalAlignment = Alignment.Start, modifier = Modifier - .fillMaxSize() - .weight(1f) + .fillMaxWidth() ) { Text( text = stringResource(R.string.request_id_created_title), @@ -144,26 +158,29 @@ private fun RequestEduIdCreatedContent( }, modifier = Modifier.fillMaxWidth() ) - Spacer(Modifier.height(40.dp)) } @Composable -private fun RequestEduIdFailedCreationContent() = Column(modifier = Modifier.fillMaxSize()) { - Text( - text = stringResource(R.string.request_id_created_fail_title), - style = MaterialTheme.typography.titleLarge.copy(textAlign = TextAlign.Center), - modifier = Modifier.fillMaxWidth() - ) +private fun RequestEduIdFailedCreationContent() = + Column( + modifier = Modifier + .fillMaxSize() + ) { + Text( + text = stringResource(R.string.request_id_created_fail_title), + style = MaterialTheme.typography.titleLarge.copy(textAlign = TextAlign.Center), + modifier = Modifier.fillMaxWidth() + ) - Spacer( - modifier = Modifier.height(16.dp) - ) - Text( - text = stringResource(R.string.request_id_created_fail_description), - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.fillMaxWidth() - ) -} + Spacer( + modifier = Modifier.height(16.dp) + ) + Text( + text = stringResource(R.string.request_id_created_fail_description), + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.fillMaxWidth() + ) + } @Preview diff --git a/app/src/main/kotlin/nl/eduid/screens/dataactivity/DataAndActivityScreen.kt b/app/src/main/kotlin/nl/eduid/screens/dataactivity/DataAndActivityScreen.kt index 698f9b11..2aecbb6f 100644 --- a/app/src/main/kotlin/nl/eduid/screens/dataactivity/DataAndActivityScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/dataactivity/DataAndActivityScreen.kt @@ -2,11 +2,12 @@ package nl.eduid.screens.dataactivity import androidx.activity.compose.BackHandler import androidx.activity.compose.LocalOnBackPressedDispatcherOwner -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -49,19 +50,23 @@ fun DataAndActivityScreen( onDismiss = viewModel::clearErrorData ) } - - if (viewModel.uiState.deleteService != null) { - DeleteServiceContent( - providerName = viewModel.uiState.deleteService?.providerName.orEmpty(), - inProgress = viewModel.uiState.isLoading, - removeService = { viewModel.removeService(viewModel.uiState.deleteService?.serviceProviderEntityId) }, - goBack = viewModel::cancelDeleteService - ) - } else { - DataAndActivityScreenContent( - data = viewModel.uiState.data, - isLoading = viewModel.uiState.isLoading - ) { viewModel.goToDeleteService(it) } + Column( + modifier = Modifier + .fillMaxSize() + .padding(it) + ) { + if (viewModel.uiState.deleteService != null) { + DeleteServiceContent( + providerName = viewModel.uiState.deleteService?.providerName.orEmpty(), + inProgress = viewModel.uiState.isLoading, + removeService = { viewModel.removeService(viewModel.uiState.deleteService?.serviceProviderEntityId) }, + goBack = viewModel::cancelDeleteService + ) + } else { + DataAndActivityScreenContent( + data = viewModel.uiState.data, isLoading = viewModel.uiState.isLoading + ) { viewModel.goToDeleteService(it) } + } } } } @@ -72,10 +77,10 @@ fun DataAndActivityScreenContent( isLoading: Boolean = false, goToConfirmDeleteService: (ServiceProvider) -> Unit = {}, ) = Column( - verticalArrangement = Arrangement.Bottom, - modifier = Modifier.verticalScroll(rememberScrollState()) + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(horizontal = 24.dp) ) { - Spacer(Modifier.height(36.dp)) Text( style = MaterialTheme.typography.titleLarge.copy( textAlign = TextAlign.Start, color = ButtonGreen diff --git a/app/src/main/kotlin/nl/eduid/screens/dataactivity/DeleteServiceContent.kt b/app/src/main/kotlin/nl/eduid/screens/dataactivity/DeleteServiceContent.kt index 6a6fa400..c93ad3eb 100644 --- a/app/src/main/kotlin/nl/eduid/screens/dataactivity/DeleteServiceContent.kt +++ b/app/src/main/kotlin/nl/eduid/screens/dataactivity/DeleteServiceContent.kt @@ -9,10 +9,9 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn -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 @@ -44,12 +43,12 @@ fun DeleteServiceContent( Column( modifier = Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.SpaceBetween ) { Column( modifier = Modifier .fillMaxSize() - .weight(1f) ) { Text( text = stringResource(R.string.delete_service_confirm_title), @@ -96,7 +95,11 @@ fun DeleteServiceContent( Spacer(Modifier.height(36.dp)) } Row( - horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxWidth() + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .padding(bottom = 24.dp), ) { SecondaryButton( text = stringResource(R.string.button_cancel), @@ -113,7 +116,6 @@ fun DeleteServiceContent( buttonTextColor = Color.White, ) } - Spacer(Modifier.height(24.dp)) } } 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 574eb38f..73ff3f0d 100644 --- a/app/src/main/kotlin/nl/eduid/screens/deeplinks/DeepLinkScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/deeplinks/DeepLinkScreen.kt @@ -1,19 +1,31 @@ package nl.eduid.screens.deeplinks import android.content.Context -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext 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.ErrorData +import nl.eduid.R import nl.eduid.ui.AlertDialogWithSingleButton import nl.eduid.ui.EduIdTopAppBar import nl.eduid.ui.theme.findActivity @@ -51,6 +63,7 @@ fun DeepLinkScreen( inProgress = isParsingLinkPayload, errorData = errorData, context = context, + paddingValues = it, ) { errorData = null } @@ -61,32 +74,36 @@ private fun DeepLinkContent( inProgress: Boolean, errorData: ErrorData?, context: Context = LocalContext.current, + paddingValues: PaddingValues = PaddingValues(), dismissError: () -> Unit, +) = Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp) ) { - Column(modifier = Modifier.fillMaxSize()) { - if (errorData != null) { - AlertDialogWithSingleButton( - title = errorData.title(context), - explanation = errorData.message(context), - buttonLabel = stringResource(R.string.button_ok), - onDismiss = dismissError - ) - } - Text( - text = stringResource(R.string.deeplink_processing), - style = MaterialTheme.typography.titleLarge.copy(textAlign = TextAlign.Center), - modifier = Modifier.fillMaxWidth() - ) - - Spacer( - modifier = Modifier.height(16.dp) + if (errorData != null) { + AlertDialogWithSingleButton( + title = errorData.title(context), + explanation = errorData.message(context), + buttonLabel = stringResource(R.string.button_ok), + onDismiss = dismissError ) + } + Text( + text = stringResource(R.string.deeplink_processing), + style = MaterialTheme.typography.titleLarge.copy(textAlign = TextAlign.Center), + modifier = Modifier.fillMaxWidth() + ) - if (inProgress) { - LinearProgressIndicator( - modifier = Modifier.fillMaxWidth() - ) - } + Spacer( + modifier = Modifier.height(16.dp) + ) + if (inProgress) { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth() + ) } } \ No newline at end of file diff --git a/app/src/main/kotlin/nl/eduid/screens/deleteaccountfirstconfirm/DeleteAccountFirstConfirmScreen.kt b/app/src/main/kotlin/nl/eduid/screens/deleteaccountfirstconfirm/DeleteAccountFirstConfirmScreen.kt index bb59d86b..a0546f38 100644 --- a/app/src/main/kotlin/nl/eduid/screens/deleteaccountfirstconfirm/DeleteAccountFirstConfirmScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/deleteaccountfirstconfirm/DeleteAccountFirstConfirmScreen.kt @@ -1,11 +1,27 @@ package nl.eduid.screens.deleteaccountfirstconfirm -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +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.runtime.* +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -33,22 +49,27 @@ fun DeleteAccountFirstConfirmScreen( onBackClicked = goBack, ) { DeleteAccountFirstConfirmScreenContent( + padding = it, onDeleteAccountPressed = onDeleteAccountPressed, ) } @Composable private fun DeleteAccountFirstConfirmScreenContent( + padding: PaddingValues = PaddingValues(), onDeleteAccountPressed: () -> Unit = {}, ) = Column( modifier = Modifier .fillMaxSize() + .padding(padding) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp), + verticalArrangement = Arrangement.SpaceBetween ) { Column( + horizontalAlignment = Alignment.Start, modifier = Modifier - .fillMaxSize() .verticalScroll(rememberScrollState()) - .weight(1f) ) { Text( text = stringResource(R.string.delete_account_one_title), @@ -105,9 +126,8 @@ private fun DeleteAccountFirstConfirmScreenContent( border = BorderStroke(1.dp, Color.Red), colors = ButtonDefaults.outlinedButtonColors(contentColor = ButtonRed), modifier = Modifier - .sizeIn(minHeight = 56.dp) - .padding(bottom = 24.dp) - .fillMaxWidth(), + .fillMaxWidth() + .sizeIn(minHeight = 56.dp), ) { Text( text = stringResource(R.string.button_manage_account_delete), diff --git a/app/src/main/kotlin/nl/eduid/screens/deleteaccountsecondconfirm/DeleteAccountSecondConfirmScreen.kt b/app/src/main/kotlin/nl/eduid/screens/deleteaccountsecondconfirm/DeleteAccountSecondConfirmScreen.kt index d5c2c4e1..b1f75ebe 100644 --- a/app/src/main/kotlin/nl/eduid/screens/deleteaccountsecondconfirm/DeleteAccountSecondConfirmScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/deleteaccountsecondconfirm/DeleteAccountSecondConfirmScreen.kt @@ -1,12 +1,35 @@ package nl.eduid.screens.deleteaccountsecondconfirm -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -27,6 +50,7 @@ import nl.eduid.R import nl.eduid.ui.AlertDialogWithSingleButton import nl.eduid.ui.EduIdTopAppBar import nl.eduid.ui.PrimaryButton +import nl.eduid.ui.keyboardAsState import nl.eduid.ui.theme.AlertRedBackground import nl.eduid.ui.theme.ButtonBorderGrey import nl.eduid.ui.theme.ButtonRed @@ -55,11 +79,12 @@ fun DeleteAccountSecondConfirmScreen( } DeleteAccountSecondConfirmScreenContent( - fullNameInput = viewModel.uiState.fullName, + fullNameInput = viewModel.uiState.fullName, + padding = it, onInputChange = { viewModel.onInputChange(it) }, - errorData = viewModel.uiState.errorData, + errorData = viewModel.uiState.errorData, dismissError = viewModel::clearErrorData, - inProgress = viewModel.uiState.inProgress, + inProgress = viewModel.uiState.inProgress, onDeleteAccountPressed = { viewModel.onDeleteAccountPressed() waitingForVmEvent = true @@ -72,6 +97,7 @@ fun DeleteAccountSecondConfirmScreen( @Composable private fun DeleteAccountSecondConfirmScreenContent( fullNameInput: String = "", + padding: PaddingValues = PaddingValues(), onInputChange: (String) -> Unit = {}, inProgress: Boolean = false, errorData: ErrorData? = null, @@ -81,7 +107,9 @@ private fun DeleteAccountSecondConfirmScreenContent( ) = Column( modifier = Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()) + .padding(padding) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.SpaceBetween ) { if (errorData != null) { val context = LocalContext.current @@ -92,22 +120,25 @@ private fun DeleteAccountSecondConfirmScreenContent( onDismiss = dismissError ) } - - val keyboardController = LocalSoftwareKeyboardController.current - Spacer(Modifier.height(36.dp)) Column( - modifier = Modifier - .fillMaxSize() - .weight(1f) + horizontalAlignment = Alignment.Start, modifier = Modifier.fillMaxWidth() ) { - Text( - text = stringResource(R.string.delete_account_two_title), - style = MaterialTheme.typography.titleLarge.copy( - color = TextGreen, textAlign = TextAlign.Start - ), - modifier = Modifier.fillMaxWidth(), - ) - Spacer(Modifier.height(18.dp)) + val keyboardController = LocalSoftwareKeyboardController.current + val isKeyboardOpen by keyboardAsState() + AnimatedVisibility( + !isKeyboardOpen, Modifier.fillMaxWidth() + ) { + Column() { + Text( + text = stringResource(R.string.delete_account_two_title), + style = MaterialTheme.typography.titleLarge.copy( + color = TextGreen, textAlign = TextAlign.Start + ), + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(18.dp)) + } + } if (inProgress) { LinearProgressIndicator( modifier = Modifier.fillMaxWidth() @@ -138,15 +169,21 @@ private fun DeleteAccountSecondConfirmScreenContent( width = Dimension.fillToConstraints }) } - Spacer(Modifier.height(18.dp)) - Text( - text = stringResource(R.string.delete_account_two_description), - style = MaterialTheme.typography.bodyLarge.copy( - color = TextBlack, textAlign = TextAlign.Start - ), - modifier = Modifier.fillMaxWidth(), - ) - Spacer(Modifier.height(36.dp)) + AnimatedVisibility( + !isKeyboardOpen, Modifier.fillMaxWidth() + ) { + Column() { + Spacer(Modifier.height(18.dp)) + Text( + text = stringResource(R.string.delete_account_two_description), + style = MaterialTheme.typography.bodyLarge.copy( + color = TextBlack, textAlign = TextAlign.Start + ), + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(24.dp)) + } + } OutlinedTextField( value = fullNameInput, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), @@ -154,33 +191,34 @@ private fun DeleteAccountSecondConfirmScreenContent( onValueChange = { onInputChange(it) }, label = { Text(stringResource(R.string.managa_account_your_full_name)) }, placeholder = { Text(stringResource(R.string.manage_account_full_name_explain)) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() ) } - Column( - Modifier.fillMaxWidth() + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .imePadding() + .padding(bottom = 24.dp), ) { - Row( - horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxWidth() - ) { - PrimaryButton( - modifier = Modifier.widthIn(min = 140.dp), - text = stringResource(R.string.button_cancel), - onClick = goBack, - buttonBackgroundColor = Color.Transparent, - buttonTextColor = TextGrey, - buttonBorderColor = ButtonBorderGrey, - ) - PrimaryButton( - modifier = Modifier.widthIn(min = 140.dp), - text = stringResource(R.string.button_confirm), - onClick = onDeleteAccountPressed, - buttonBackgroundColor = ButtonRed, - buttonTextColor = Color.White, - enabled = fullNameInput.isNotBlank(), - ) - } - Spacer(Modifier.height(24.dp)) + PrimaryButton( + modifier = Modifier.widthIn(min = 140.dp), + text = stringResource(R.string.button_cancel), + onClick = goBack, + buttonBackgroundColor = Color.Transparent, + buttonTextColor = TextGrey, + buttonBorderColor = ButtonBorderGrey, + ) + PrimaryButton( + modifier = Modifier.widthIn(min = 140.dp), + text = stringResource(R.string.button_confirm), + onClick = onDeleteAccountPressed, + buttonBackgroundColor = ButtonRed, + buttonTextColor = Color.White, + enabled = fullNameInput.isNotBlank(), + ) } } diff --git a/app/src/main/kotlin/nl/eduid/screens/editname/EditNameScreen.kt b/app/src/main/kotlin/nl/eduid/screens/editname/EditNameScreen.kt index 2e259eff..e3c3cf64 100644 --- a/app/src/main/kotlin/nl/eduid/screens/editname/EditNameScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/editname/EditNameScreen.kt @@ -3,10 +3,14 @@ package nl.eduid.screens.editname import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -61,6 +65,7 @@ fun EditNameScreen( isLoading = viewModel.uiState.isLoading, personalInfo = viewModel.uiState.personalInfo, account = viewModel.uiState.personalInfo.institutionAccounts.firstOrNull(), + padding = it, updateName = { givenName, familyName -> viewModel.updateName(givenName, familyName) }, addLinkToAccount = { isGettingLinkUrl = true @@ -75,13 +80,17 @@ private fun EditNameContent( isLoading: Boolean, personalInfo: PersonalInfo, account: PersonalInfo.InstitutionAccount? = null, + padding: PaddingValues = PaddingValues(), updateName: (String, String) -> Unit = { _, _ -> }, addLinkToAccount: () -> Unit = {}, removeConnection: (Int) -> Unit = {}, ) = Column( modifier = Modifier - .fillMaxWidth() + .fillMaxSize() .verticalScroll(rememberScrollState()) + .padding(padding) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp), ) { Text( style = MaterialTheme.typography.titleLarge, @@ -155,10 +164,6 @@ private fun EditNameContent( enabled = !isLoading, addLinkToAccount = addLinkToAccount ) - - Spacer( - modifier = Modifier.height(24.dp) - ) } diff --git a/app/src/main/kotlin/nl/eduid/screens/firsttimedialog/FirstTimeDialogScreen.kt b/app/src/main/kotlin/nl/eduid/screens/firsttimedialog/FirstTimeDialogScreen.kt index a39f862e..644e3109 100644 --- a/app/src/main/kotlin/nl/eduid/screens/firsttimedialog/FirstTimeDialogScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/firsttimedialog/FirstTimeDialogScreen.kt @@ -92,9 +92,9 @@ private fun FirstTimeDialogContent( Column( modifier = Modifier - .padding(paddingValues) .fillMaxSize() .verticalScroll(rememberScrollState()) + .padding(paddingValues) .padding(horizontal = 30.dp) ) { Column( diff --git a/app/src/main/kotlin/nl/eduid/screens/manageaccount/ManageAccountScreen.kt b/app/src/main/kotlin/nl/eduid/screens/manageaccount/ManageAccountScreen.kt index 3c48e09a..fd8c8558 100644 --- a/app/src/main/kotlin/nl/eduid/screens/manageaccount/ManageAccountScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/manageaccount/ManageAccountScreen.kt @@ -1,14 +1,30 @@ package nl.eduid.screens.manageaccount -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -50,6 +66,7 @@ fun ManageAccountScreen( ManageAccountScreenContent( dateString = viewModel.dateString, inProgress = inProgress, + padding = it, onDownloadData = viewModel::downloadAccountData, onDeleteAccountPressed = onDeleteAccountPressed, ) @@ -59,18 +76,19 @@ fun ManageAccountScreen( private fun ManageAccountScreenContent( dateString: String, inProgress: Boolean, + padding: PaddingValues = PaddingValues(), onDownloadData: () -> Unit = {}, onDeleteAccountPressed: () -> Unit = {}, ) = Column( modifier = Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()) + .padding(padding) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp), + verticalArrangement = Arrangement.SpaceBetween ) { - Spacer(Modifier.height(36.dp)) Column( - modifier = Modifier - .fillMaxSize() - .weight(1f) + horizontalAlignment = Alignment.Start, modifier = Modifier.fillMaxWidth() ) { Text( text = stringResource(R.string.manage_account_title), @@ -109,15 +127,11 @@ private fun ManageAccountScreenContent( modifier = Modifier.fillMaxWidth() ) } - Button( + OutlinedButton( shape = RoundedCornerShape(CornerSize(6.dp)), onClick = onDeleteAccountPressed, - border = BorderStroke(1.dp, Color.Red), colors = ButtonDefaults.outlinedButtonColors(contentColor = ButtonRed), - modifier = Modifier - .sizeIn(minHeight = 48.dp) - .padding(bottom = 24.dp) - .fillMaxWidth(), + modifier = Modifier.fillMaxWidth() ) { Text( text = stringResource(R.string.manage_account_delete_your_account), diff --git a/app/src/main/kotlin/nl/eduid/screens/oauth/OAuthScreen.kt b/app/src/main/kotlin/nl/eduid/screens/oauth/OAuthScreen.kt index 86aaf313..10bbd746 100644 --- a/app/src/main/kotlin/nl/eduid/screens/oauth/OAuthScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/oauth/OAuthScreen.kt @@ -2,14 +2,15 @@ package nl.eduid.screens.oauth import android.content.Intent import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -20,6 +21,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -84,6 +86,7 @@ fun OAuthScreen( OAuthContent( uiState = viewModel.uiState, isAuthorizationLaunched = oAuthUiStages.isAuthorizationLaunched, + padding = it, launchAuthorization = { intentAvailable -> // Timber.e("0 - AppAuth intent available. Launching OAuth") oAuthUiStages = OAuthUiStages( @@ -101,6 +104,7 @@ fun OAuthScreen( private fun OAuthContent( uiState: UiState, isAuthorizationLaunched: Boolean, + padding: PaddingValues = PaddingValues(), launchAuthorization: (Intent) -> Unit, dismissError: () -> Unit, onRetry: () -> Unit, @@ -121,15 +125,17 @@ private fun OAuthContent( } Column( - Modifier + modifier = Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()) - .systemBarsPadding() + .padding(padding) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp), + verticalArrangement = Arrangement.SpaceBetween ) { Column( + horizontalAlignment = Alignment.Start, modifier = Modifier .fillMaxWidth() - .weight(1f) ) { Spacer(modifier = Modifier.height(40.dp)) Text( @@ -162,9 +168,9 @@ private fun OAuthContent( PrimaryButton( text = stringResource(R.string.button_retry), onClick = onRetry, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth(), ) - Spacer(modifier = Modifier.height(40.dp)) } } } \ No newline at end of file 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 411f9748..6fa29a53 100644 --- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt @@ -2,11 +2,12 @@ package nl.eduid.screens.personalinfo import androidx.activity.compose.rememberLauncherForActivityResult 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.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.rememberScrollState @@ -69,21 +70,26 @@ fun PersonalInfoScreen( launcher.launch(viewModel.uiState.linkUrl) } } - - PersonalInfoScreenContent( - personalInfo = viewModel.uiState.personalInfo, - isLoading = viewModel.uiState.isLoading, - errorData = viewModel.uiState.errorData, - dismissError = viewModel::clearErrorData, - onEmailClicked = onEmailClicked, - onNameClicked = onNameClicked, - removeConnection = { index -> viewModel.removeConnection(index) }, - onManageAccountClicked = onManageAccountClicked, - addLinkToAccount = { - isGettingLinkUrl = true - viewModel.requestLinkUrl() - }, - ) + Column( + modifier = Modifier + .fillMaxSize() + .padding(it) + ) { + PersonalInfoScreenContent( + personalInfo = viewModel.uiState.personalInfo, + isLoading = viewModel.uiState.isLoading, + errorData = viewModel.uiState.errorData, + dismissError = viewModel::clearErrorData, + onEmailClicked = onEmailClicked, + onNameClicked = onNameClicked, + removeConnection = { index -> viewModel.removeConnection(index) }, + onManageAccountClicked = onManageAccountClicked, + addLinkToAccount = { + isGettingLinkUrl = true + viewModel.requestLinkUrl() + }, + ) + } } @Composable @@ -98,8 +104,10 @@ fun PersonalInfoScreenContent( onManageAccountClicked: (dateString: String) -> Unit = {}, addLinkToAccount: () -> Unit = {}, ) = Column( - verticalArrangement = Arrangement.Bottom, - modifier = Modifier.verticalScroll(rememberScrollState()) + modifier = Modifier + .verticalScroll(rememberScrollState()) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp) ) { if (errorData != null) { val context = LocalContext.current @@ -111,7 +119,6 @@ fun PersonalInfoScreenContent( ) } - Spacer(Modifier.height(36.dp)) Text( style = MaterialTheme.typography.titleLarge.copy( textAlign = TextAlign.Start, color = ButtonGreen @@ -156,6 +163,7 @@ fun PersonalInfoScreenContent( subtitle = stringResource(R.string.infotab_providedby_you), onClick = onEmailClicked, endIcon = R.drawable.edit_icon, + capitalizeTitle = false, label = stringResource(R.string.infotab_email), ) Spacer(Modifier.height(16.dp)) @@ -208,7 +216,6 @@ fun PersonalInfoScreenContent( ), ) } - Spacer(Modifier.height(42.dp)) } diff --git a/app/src/main/kotlin/nl/eduid/screens/pinsetup/PinContent.kt b/app/src/main/kotlin/nl/eduid/screens/pinsetup/PinContent.kt index 207c32c2..7c39cb36 100644 --- a/app/src/main/kotlin/nl/eduid/screens/pinsetup/PinContent.kt +++ b/app/src/main/kotlin/nl/eduid/screens/pinsetup/PinContent.kt @@ -1,12 +1,18 @@ package nl.eduid.screens.pinsetup -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +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.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -27,21 +33,15 @@ fun PinContent( label: String, onPinChange: (String, PinStep) -> Unit = { _, _ -> }, onClick: () -> Unit = {}, - paddingValues: PaddingValues = PaddingValues(), isProcessing: Boolean = false, ) = Column( - Modifier - .padding(paddingValues) - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .systemBarsPadding() + modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.SpaceBetween ) { Column( - modifier = Modifier - .fillMaxWidth() - .weight(1f) + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.Start, ) { - Spacer(modifier = Modifier.height(40.dp)) + Spacer(modifier = Modifier.height(36.dp)) Text( text = title, style = MaterialTheme.typography.titleLarge, @@ -68,26 +68,28 @@ fun PinContent( onPinChange = { newValue -> onPinChange(newValue, pinStep) }, submitPin = onClick ) - Spacer(modifier = Modifier.height(52.dp)) + Spacer(modifier = Modifier.height(24.dp)) } PrimaryButton( text = stringResource(R.string.button_ok), onClick = onClick, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .imePadding() + .navigationBarsPadding() + .padding(bottom = 24.dp), ) - Spacer(modifier = Modifier.height(40.dp)) } @Preview @Composable -private fun Preview_PinContent() = - EduidAppAndroidTheme { - PinContent( - pinCode = "1234", - pinStep = PinStep.PinCreate, - isPinInvalid = false, - title = "Choose a unique PIN", - description = "Enter the PIN:", - label = "Please remember this PIN, it cannot be changed!", - ) - } \ No newline at end of file +private fun Preview_PinContent() = EduidAppAndroidTheme { + PinContent( + pinCode = "1234", + pinStep = PinStep.PinCreate, + isPinInvalid = false, + title = "Choose a unique PIN", + description = "Enter the PIN:", + label = "Please remember this PIN, it cannot be changed!", + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/nl/eduid/screens/pinsetup/RegistrationPinSetupScreen.kt b/app/src/main/kotlin/nl/eduid/screens/pinsetup/RegistrationPinSetupScreen.kt index 7e985e44..48ed2130 100644 --- a/app/src/main/kotlin/nl/eduid/screens/pinsetup/RegistrationPinSetupScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/pinsetup/RegistrationPinSetupScreen.kt @@ -2,9 +2,11 @@ package nl.eduid.screens.pinsetup import androidx.activity.compose.BackHandler import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -17,6 +19,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import nl.eduid.R import nl.eduid.ui.AlertDialogWithSingleButton import nl.eduid.ui.EduIdTopAppBar @@ -41,6 +44,7 @@ fun RegistrationPinSetupScreen( RegistrationPinSetupContent( uiState = viewModel.uiState, isAuthorized = isAuthorized, + padding = it, goToNextStep = goToNextStep, promptAuth = promptAuth, viewModel = viewModel, @@ -52,6 +56,7 @@ fun RegistrationPinSetupScreen( private fun RegistrationPinSetupContent( uiState: UiState, isAuthorized: Boolean? = null, + padding: PaddingValues = PaddingValues(), goToNextStep: (NextStep) -> Unit = {}, promptAuth: () -> Unit = {}, viewModel: RegistrationPinSetupViewModel, @@ -80,7 +85,13 @@ private fun RegistrationPinSetupContent( } } - Column(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.SpaceBetween + ) { if (uiState.errorData != null) { AlertDialogWithSingleButton(title = uiState.errorData.title(context), explanation = uiState.errorData.message(context), @@ -116,7 +127,6 @@ private fun RegistrationPinSetupContent( viewModel.submitPin(context, uiState.pinStep) enrollmentInProgress = uiState.pinStep == PinStep.PinConfirm }, - paddingValues = PaddingValues(), isProcessing = enrollmentInProgress || authInProgress ) } diff --git a/app/src/main/kotlin/nl/eduid/screens/recovery/confirmsms/ConfirmCodeScreen.kt b/app/src/main/kotlin/nl/eduid/screens/recovery/confirmsms/ConfirmCodeScreen.kt index 7ab7a952..05b3922c 100644 --- a/app/src/main/kotlin/nl/eduid/screens/recovery/confirmsms/ConfirmCodeScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/recovery/confirmsms/ConfirmCodeScreen.kt @@ -1,29 +1,39 @@ package nl.eduid.screens.recovery.confirmsms +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalSoftwareKeyboardController @@ -49,7 +59,7 @@ fun ConfirmCodeScreen( phoneNumber: String, goToStartScreen: () -> Unit, goBack: () -> Unit, -) = EduIdTopAppBar( +) = EduIdTopAppBar( onBackClicked = goBack ) { var waitForVmEvent by rememberSaveable { mutableStateOf(false) } @@ -70,6 +80,7 @@ fun ConfirmCodeScreen( ConfirmCodeContent( uiState = viewModel.uiState, phoneNumber = phoneNumber, + padding = it, onClick = { waitForVmEvent = true viewModel.confirmPhoneCode() @@ -84,11 +95,20 @@ fun ConfirmCodeScreen( private fun ConfirmCodeContent( uiState: UiState, phoneNumber: String, + padding: PaddingValues = PaddingValues(), onClick: () -> Unit = {}, dismissError: () -> Unit = {}, onValueChange: (String) -> Unit = {}, +) = Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.SpaceBetween ) { val keyboardController = LocalSoftwareKeyboardController.current + val focusRequester = remember { FocusRequester() } + if (uiState.errorData != null) { val context = LocalContext.current AlertDialogWithSingleButton( @@ -98,52 +118,64 @@ private fun ConfirmCodeContent( onDismiss = dismissError ) } - Column( - Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) + horizontalAlignment = Alignment.Start, + modifier = Modifier + .fillMaxWidth() ) { - Column( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) { - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = stringResource(R.string.confirm_sms_code_title), - style = MaterialTheme.typography.titleLarge, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth(), - ) - Spacer(modifier = Modifier.height(32.dp)) - Text( - text = stringResource(R.string.confirm_sms_code_subtitle, phoneNumber), - style = MaterialTheme.typography.bodyLarge, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth(), - ) - Spacer(modifier = Modifier.height(16.dp)) - - OutlinedTextField( - value = uiState.input, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Done, keyboardType = KeyboardType.Number - ), - keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }), - onValueChange = onValueChange, - modifier = Modifier.fillMaxWidth() - ) - } - PrimaryButton( - text = stringResource(R.string.confirm_sms_code_button), - enabled = uiState.input.isNotEmpty(), - onClick = onClick, + Text( + text = stringResource(R.string.confirm_sms_code_title), + style = MaterialTheme.typography.titleLarge, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(32.dp)) + Text( + text = stringResource(R.string.confirm_sms_code_subtitle, phoneNumber), + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(16.dp)) + val clipboardManager = LocalClipboardManager.current + OutlinedTextField( + value = uiState.input, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done, keyboardType = KeyboardType.Number + ), + keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }), + onValueChange = onValueChange, modifier = Modifier .fillMaxWidth() - .padding(bottom = 24.dp), + .focusRequester(focusRequester) + .onFocusChanged { focusState -> + if (focusState.isFocused && clipboardManager.hasText()) { + val clipText = clipboardManager.getText()?.text ?: "" + if (clipText.isNotEmpty() && clipText.length == 6) { + onValueChange(clipText) + } + } + } + .requiredHeight(TextFieldDefaults.MinHeight) ) +// We can either have automatic focus on text field and showing keyboard +// OR +// We have automatic paste for the SMS code when the focus is done manually. +// LaunchedEffect(focusRequester) { +// awaitFrame() +// focusRequester.requestFocus() +// } } + PrimaryButton( + text = stringResource(R.string.confirm_sms_code_button), + enabled = uiState.input.isNotEmpty(), + onClick = onClick, + modifier = Modifier + .fillMaxWidth() + .imePadding() + .navigationBarsPadding() + .padding(bottom = 24.dp), + ) } @Preview() diff --git a/app/src/main/kotlin/nl/eduid/screens/recovery/requestsms/PhoneRequestCodeScreen.kt b/app/src/main/kotlin/nl/eduid/screens/recovery/requestsms/PhoneRequestCodeScreen.kt index 185be723..a66bd2f1 100644 --- a/app/src/main/kotlin/nl/eduid/screens/recovery/requestsms/PhoneRequestCodeScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/recovery/requestsms/PhoneRequestCodeScreen.kt @@ -1,12 +1,16 @@ package nl.eduid.screens.recovery.requestsms +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeight -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.ExperimentalMaterial3Api @@ -18,6 +22,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -25,6 +30,8 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalSoftwareKeyboardController @@ -34,6 +41,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.flowWithLifecycle +import kotlinx.coroutines.android.awaitFrame import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import nl.eduid.R @@ -77,6 +85,7 @@ fun PhoneRequestCodeScreen( PhoneRequestCodeContent( uiState = viewModel.uiState, + padding = it, onClick = { waitForVmEvent = true viewModel.requestPhoneCode() @@ -91,19 +100,23 @@ fun PhoneRequestCodeScreen( ) private fun PhoneRequestCodeContent( uiState: UiState, + padding: PaddingValues = PaddingValues(), onClick: () -> Unit = {}, onValueChange: (String) -> Unit = {}, ) = Column( modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() + .fillMaxSize() + .padding(padding) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.SpaceBetween ) { Column( - horizontalAlignment = Alignment.Start, modifier = Modifier + horizontalAlignment = Alignment.Start, + modifier = Modifier .fillMaxWidth() - .weight(1f) ) { val keyboardController = LocalSoftwareKeyboardController.current + val focusRequester = remember { FocusRequester() } Text( text = stringResource(R.string.request_id_recovery_title), style = MaterialTheme.typography.titleLarge, @@ -141,8 +154,13 @@ private fun PhoneRequestCodeContent( onValueChange = onValueChange, modifier = Modifier .fillMaxWidth() + .focusRequester(focusRequester) .requiredHeight(TextFieldDefaults.MinHeight) ) + LaunchedEffect(focusRequester) { + awaitFrame() + focusRequester.requestFocus() + } } PrimaryButton( text = stringResource(R.string.request_id_recovery_button), @@ -150,7 +168,9 @@ private fun PhoneRequestCodeContent( enabled = uiState.input.isNotEmpty(), modifier = Modifier .fillMaxWidth() - .padding(top = 12.dp, bottom = 24.dp), + .imePadding() + .navigationBarsPadding() + .padding(bottom = 24.dp), ) } diff --git a/app/src/main/kotlin/nl/eduid/screens/requestiddetails/RequestEduIdFormScreen.kt b/app/src/main/kotlin/nl/eduid/screens/requestiddetails/RequestEduIdFormScreen.kt index 7b01fd4b..2e329c3e 100644 --- a/app/src/main/kotlin/nl/eduid/screens/requestiddetails/RequestEduIdFormScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/requestiddetails/RequestEduIdFormScreen.kt @@ -1,30 +1,56 @@ package nl.eduid.screens.requestiddetails -import androidx.compose.foundation.layout.* +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout +import kotlinx.coroutines.android.awaitFrame import nl.eduid.R import nl.eduid.ui.AlertDialogWithSingleButton import nl.eduid.ui.CheckToSAndPrivacyPolicy import nl.eduid.ui.EduIdTopAppBar import nl.eduid.ui.PrimaryButton +import nl.eduid.ui.keyboardAsState import nl.eduid.ui.theme.EduidAppAndroidTheme @Composable @@ -37,10 +63,10 @@ fun RequestEduIdFormScreen( ) { var processingRequest by rememberSaveable { mutableStateOf(false) } val context = LocalContext.current - if (viewModel.inputForm.errorData != null) { + viewModel.inputForm.errorData?.let { errorData -> AlertDialogWithSingleButton( - title = viewModel.inputForm.errorData!!.title, - explanation = viewModel.inputForm.errorData!!.message, + title = errorData.title(context), + explanation = errorData.message(context), buttonLabel = stringResource(R.string.button_ok), onDismiss = viewModel::dismissError ) @@ -53,6 +79,7 @@ fun RequestEduIdFormScreen( } } RequestEduIdFormContent(inputFormData = viewModel.inputForm, + padding = it, onEmailChange = { viewModel.onEmailChange(it) }, onFirstNameChange = { viewModel.onFirstNameChange(it) }, onLastNameChange = { viewModel.onLastNameChange(it) }, @@ -64,126 +91,130 @@ fun RequestEduIdFormScreen( } @Composable -@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@OptIn( + ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class, + ExperimentalFoundationApi::class, ExperimentalLayoutApi::class +) private fun RequestEduIdFormContent( inputFormData: InputForm, + padding: PaddingValues = PaddingValues(), onEmailChange: (String) -> Unit = {}, onFirstNameChange: (String) -> Unit = {}, onLastNameChange: (String) -> Unit = {}, onAcceptToC: (Boolean) -> Unit = {}, onRequestEduIdAccount: () -> Unit = {}, +) = Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(padding) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.SpaceBetween ) { - ConstraintLayout( - modifier = Modifier.fillMaxSize() + val focusManager = LocalFocusManager.current + val focusRequester = remember { FocusRequester() } + Column( + horizontalAlignment = Alignment.Start, + modifier = Modifier + .fillMaxWidth() ) { - val (content, bottomButton, bottomSpacer) = createRefs() - val focusManager = LocalFocusManager.current - val keyboardController = LocalSoftwareKeyboardController.current - Column(horizontalAlignment = Alignment.Start, - modifier = Modifier - .fillMaxWidth() - .constrainAs(content) { - top.linkTo(parent.top) - }) { - Text( - text = stringResource(R.string.request_id_details_screen_title), - style = MaterialTheme.typography.titleLarge.copy( - textAlign = TextAlign.Start - ), - modifier = Modifier.fillMaxWidth() - ) - - Spacer( - modifier = Modifier.height(24.dp) - ) - if (inputFormData.isProcessing) { - LinearProgressIndicator( + val isKeyboardOpen by keyboardAsState() + AnimatedVisibility( + !isKeyboardOpen, + Modifier.fillMaxWidth() + ) { + Column() { + Text( + text = stringResource(R.string.request_id_details_screen_title), + style = MaterialTheme.typography.titleLarge.copy( + textAlign = TextAlign.Start + ), modifier = Modifier.fillMaxWidth() ) + Spacer( + modifier = Modifier.height(24.dp) + ) } - - OutlinedTextField( - value = inputFormData.email, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next, keyboardType = KeyboardType.Email - ), - keyboardActions = KeyboardActions(onNext = { - focusManager.moveFocus( - FocusDirection.Down - ) - }), - isError = (inputFormData.email.length > 2 && !inputFormData.emailValid), - onValueChange = onEmailChange, - label = { Text(stringResource(R.string.request_id_details_screen_email_input_title)) }, - placeholder = { Text(stringResource(R.string.request_id_details_screen_email_input_hint)) }, - modifier = Modifier.fillMaxWidth() - ) - - Spacer( - modifier = Modifier.height(12.dp) - ) - - OutlinedTextField( - value = inputFormData.firstName, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), - keyboardActions = KeyboardActions(onNext = { - focusManager.moveFocus( - FocusDirection.Down - ) - }), - onValueChange = onFirstNameChange, - label = { Text(stringResource(R.string.request_id_details_screen_first_name_input_title)) }, - placeholder = { Text(stringResource(R.string.request_id_details_screen_first_name_input_hint)) }, - modifier = Modifier.fillMaxWidth() - ) - - Spacer( - modifier = Modifier.height(12.dp) - ) - - OutlinedTextField( - value = inputFormData.lastName, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }), - onValueChange = onLastNameChange, - label = { Text(stringResource(R.string.request_id_details_screen_last_name_input_title)) }, - placeholder = { Text(stringResource(R.string.request_id_details_screen_last_name_input_hint)) }, + } + if (inputFormData.isProcessing) { + LinearProgressIndicator( modifier = Modifier.fillMaxWidth() ) + } - Spacer( - modifier = Modifier.height(24.dp) - ) - - CheckToSAndPrivacyPolicy( - onAcceptChange = onAcceptToC, - hasAcceptedToC = inputFormData.termsAccepted, - modifier = Modifier.fillMaxWidth() - ) + OutlinedTextField( + value = inputFormData.email, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Next, keyboardType = KeyboardType.Email + ), + keyboardActions = KeyboardActions(onNext = { + focusManager.moveFocus( + FocusDirection.Down + ) + }), + isError = (inputFormData.email.length > 2 && !inputFormData.emailValid), + onValueChange = onEmailChange, + label = { Text(stringResource(R.string.request_id_details_screen_email_input_title)) }, + placeholder = { Text(stringResource(R.string.request_id_details_screen_email_input_hint)) }, + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester) + ) - Spacer( - modifier = Modifier.height(20.dp) - ) + Spacer(modifier = Modifier.height(8.dp)) - } - PrimaryButton( - text = stringResource(R.string.request_id_screen_create_id_button), - onClick = onRequestEduIdAccount, + OutlinedTextField( + value = inputFormData.firstName, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions(onNext = { + focusManager.moveFocus( + FocusDirection.Down + ) + }), + onValueChange = onFirstNameChange, + label = { Text(stringResource(R.string.request_id_details_screen_first_name_input_title)) }, + placeholder = { Text(stringResource(R.string.request_id_details_screen_first_name_input_hint)) }, modifier = Modifier .fillMaxWidth() - .constrainAs(bottomButton) { - bottom.linkTo(bottomSpacer.top) - }, - enabled = inputFormData.isFormValid, ) - Spacer( - Modifier - .height(40.dp) - .constrainAs(bottomSpacer) { - bottom.linkTo(parent.bottom) - }, + Spacer(modifier = Modifier.height(8.dp)) + + OutlinedTextField( + value = inputFormData.lastName, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { + focusManager.clearFocus() + }), + onValueChange = onLastNameChange, + label = { Text(stringResource(R.string.request_id_details_screen_last_name_input_title)) }, + placeholder = { Text(stringResource(R.string.request_id_details_screen_last_name_input_hint)) }, + modifier = Modifier + .fillMaxWidth() ) + + Spacer(modifier = Modifier.height(12.dp)) + + CheckToSAndPrivacyPolicy( + onAcceptChange = onAcceptToC, + hasAcceptedToC = inputFormData.termsAccepted, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(16.dp)) + LaunchedEffect(focusRequester) { + awaitFrame() + focusRequester.requestFocus() + } } + PrimaryButton( + text = stringResource(R.string.request_id_screen_create_id_button), + onClick = onRequestEduIdAccount, + modifier = Modifier + .fillMaxWidth() + .imePadding() + .navigationBarsPadding() + .padding(bottom = 24.dp), + enabled = inputFormData.isFormValid, + ) } @Preview() 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 3c592c08..867e412d 100644 --- a/app/src/main/kotlin/nl/eduid/screens/requestiddetails/RequestEduIdFormViewModel.kt +++ b/app/src/main/kotlin/nl/eduid/screens/requestiddetails/RequestEduIdFormViewModel.kt @@ -9,7 +9,6 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Job import kotlinx.coroutines.launch import nl.eduid.BuildConfig import nl.eduid.ErrorData @@ -51,56 +50,53 @@ class RequestEduIdFormViewModel @Inject constructor( inputForm = inputForm.copy(errorData = null) } - fun requestNewEduIdAccount(context: Context): Job { - return viewModelScope.launch { - val relyingPartClientId = getClientIdFromOAuthConfig(context.resources) - inputForm = inputForm.copy(isProcessing = true, requestComplete = false) - val responseStatus = eduIdRepo.requestEnroll( - RequestEduIdAccount( - email = inputForm.email, - givenName = inputForm.firstName, - familyName = inputForm.lastName, - relyingPartClientId = relyingPartClientId - ) + fun requestNewEduIdAccount(context: Context) = viewModelScope.launch { + val relyingPartClientId = getClientIdFromOAuthConfig(context.resources) + inputForm = inputForm.copy(isProcessing = true, requestComplete = false) + val responseStatus = eduIdRepo.requestEnroll( + RequestEduIdAccount( + email = inputForm.email, + givenName = inputForm.firstName, + familyName = inputForm.lastName, + relyingPartClientId = relyingPartClientId ) - val newData = when (responseStatus) { - CREATE_EMAIL_SENT -> { - inputForm.copy(isProcessing = false, requestComplete = true) - } - - FAIL_EMAIL_IN_USE -> { - inputForm.copy( - isProcessing = false, errorData = ErrorData( - titleId = R.string.err_title_email_in_use, - messageId = R.string.err_msg_email_in_use, - messageArg = inputForm.email - ) - ) - } - - EMAIL_DOMAIN_FORBIDDEN -> { - inputForm.copy( - isProcessing = false, errorData = ErrorData( - titleId = R.string.err_title_email_domain_forbidden, - messageId = R.string.err_msg_email_domain_forbidden, - messageArg = inputForm.email - ) + ) + val newData = when (responseStatus) { + CREATE_EMAIL_SENT -> { + inputForm.copy(isProcessing = false, requestComplete = true) + } + + FAIL_EMAIL_IN_USE -> { + inputForm.copy( + isProcessing = false, errorData = ErrorData( + titleId = R.string.err_title_email_in_use, + messageId = R.string.err_msg_email_in_use, + messageArg = inputForm.email ) - } - - else -> { - inputForm.copy( - isProcessing = false, errorData = ErrorData( - titleId = R.string.err_title_auth_unexpected_fail, - messageId = R.string.err_msg_create_unknown_fail, - messageArg = inputForm.email - ) + ) + } + + EMAIL_DOMAIN_FORBIDDEN -> { + inputForm.copy( + isProcessing = false, errorData = ErrorData( + titleId = R.string.err_title_email_domain_forbidden, + messageId = R.string.err_msg_email_domain_forbidden, + messageArg = inputForm.email ) - } + ) } - inputForm = newData + else -> { + inputForm.copy( + isProcessing = false, errorData = ErrorData( + titleId = R.string.err_title_auth_unexpected_fail, + messageId = R.string.err_msg_create_unknown_fail, + messageArg = inputForm.email + ) + ) + } } + inputForm = newData } private fun getClientIdFromOAuthConfig(resources: Resources): String { @@ -113,22 +109,22 @@ class RequestEduIdFormViewModel @Inject constructor( BuildConfig.CLIENT_ID } } - } -data class InputForm( - val email: String = "", - val firstName: String = "", - val lastName: String = "", - val termsAccepted: Boolean = false, - val isProcessing: Boolean = false, - val requestComplete: Boolean = false, - val errorData: ErrorData? = null, -) { - val emailValid: Boolean - get() = Patterns.EMAIL_ADDRESS.matcher(email.trim()).matches() - - val isFormValid: Boolean - get() = (emailValid && firstName.isNotEmpty() && lastName.isNotEmpty() && termsAccepted) -} + + data class InputForm( + val email: String = "", + val firstName: String = "", + val lastName: String = "", + val termsAccepted: Boolean = false, + val isProcessing: Boolean = false, + val requestComplete: Boolean = false, + val errorData: ErrorData? = null, + ) { + val emailValid: Boolean + get() = Patterns.EMAIL_ADDRESS.matcher(email.trim()).matches() + + val isFormValid: Boolean + get() = (emailValid && firstName.isNotEmpty() && lastName.isNotEmpty() && termsAccepted) + } diff --git a/app/src/main/kotlin/nl/eduid/screens/requestidlinksent/RequestEduIdEmailSentScreen.kt b/app/src/main/kotlin/nl/eduid/screens/requestidlinksent/RequestEduIdEmailSentScreen.kt index d633e609..349faf8b 100644 --- a/app/src/main/kotlin/nl/eduid/screens/requestidlinksent/RequestEduIdEmailSentScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/requestidlinksent/RequestEduIdEmailSentScreen.kt @@ -1,10 +1,13 @@ package nl.eduid.screens.requestidlinksent import android.content.Intent +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.CircularProgressIndicator @@ -52,7 +55,11 @@ fun RequestEduIdEmailSentScreen( } } Column( - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxSize() + .padding(it) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.SpaceBetween ) { Text( text = stringResource(R.string.request_id_link_title), @@ -93,26 +100,36 @@ fun RequestEduIdEmailSentScreen( Spacer( modifier = Modifier.height(32.dp) ) - PrimaryButton( - text = stringResource(R.string.request_id_link_open_email_client), onClick = { - val intent = - Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_EMAIL) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(intent) - }, modifier = Modifier + Column( + modifier = Modifier .fillMaxWidth() - .padding(horizontal = 32.dp) - ) + .weight(1f, false) + .navigationBarsPadding() + .padding(bottom = 24.dp), + ) { + PrimaryButton( + text = stringResource(R.string.request_id_link_open_email_client), onClick = { + val intent = + Intent.makeMainSelectorActivity( + Intent.ACTION_MAIN, + Intent.CATEGORY_APP_EMAIL + ) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + }, modifier = Modifier + .fillMaxWidth() + ) - Spacer( - modifier = Modifier.height(32.dp) - ) + Spacer( + modifier = Modifier.height(32.dp) + ) - Text( - text = stringResource(R.string.request_id_link_spam_text), - style = MaterialTheme.typography.bodyMedium.copy(color = TextGrey), - modifier = Modifier.align(Alignment.CenterHorizontally) - ) + Text( + text = stringResource(R.string.request_id_link_spam_text), + style = MaterialTheme.typography.bodyMedium.copy(color = TextGrey), + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + } } } diff --git a/app/src/main/kotlin/nl/eduid/screens/requestidstart/RequestEduIdStartScreen.kt b/app/src/main/kotlin/nl/eduid/screens/requestidstart/RequestEduIdStartScreen.kt index bf044705..ed109d10 100644 --- a/app/src/main/kotlin/nl/eduid/screens/requestidstart/RequestEduIdStartScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/requestidstart/RequestEduIdStartScreen.kt @@ -1,7 +1,15 @@ package nl.eduid.screens.requestidstart -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -9,11 +17,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout import nl.eduid.R import nl.eduid.ui.BulletPoint -import nl.eduid.ui.PrimaryButton import nl.eduid.ui.EduIdTopAppBar +import nl.eduid.ui.PrimaryButton import nl.eduid.ui.theme.EduidAppAndroidTheme import nl.eduid.ui.theme.TextBlack @@ -24,24 +31,24 @@ fun RequestEduIdStartScreen( ) = EduIdTopAppBar( onBackClicked = onBackClicked, ) { - ConstraintLayout( + Column( modifier = Modifier .fillMaxSize() + .padding(it) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.SpaceBetween ) { - val (content, bottomButton, bottomSpacer) = createRefs() - Column( - horizontalAlignment = Alignment.CenterHorizontally, + horizontalAlignment = Alignment.Start, modifier = Modifier .fillMaxWidth() - .constrainAs(content) { - top.linkTo(parent.top) - } ) { - Text( text = stringResource(R.string.request_id_screen_title), - style = MaterialTheme.typography.titleLarge.copy(textAlign = TextAlign.Start, color = TextBlack), + style = MaterialTheme.typography.titleLarge.copy( + textAlign = TextAlign.Start, + color = TextBlack + ), modifier = Modifier .fillMaxWidth() ) @@ -82,21 +89,14 @@ fun RequestEduIdStartScreen( .fillMaxWidth() ) } + PrimaryButton( text = stringResource(R.string.request_id_screen_create_id_button), onClick = requestId, modifier = Modifier .fillMaxWidth() - .constrainAs(bottomButton) { - bottom.linkTo(bottomSpacer.top) - }, - ) - Spacer( - Modifier - .height(40.dp) - .constrainAs(bottomSpacer) { - bottom.linkTo(parent.bottom) - }, + .navigationBarsPadding() + .padding(bottom = 24.dp), ) } } @@ -105,7 +105,7 @@ fun RequestEduIdStartScreen( @Composable private fun PreviewEnroll() { EduidAppAndroidTheme { - RequestEduIdStartScreen({},{}) + RequestEduIdStartScreen({}, {}) } } diff --git a/app/src/main/kotlin/nl/eduid/screens/resetpassword/ResetPasswordScreen.kt b/app/src/main/kotlin/nl/eduid/screens/resetpassword/ResetPasswordScreen.kt index 5c7e2f8f..738d0983 100644 --- a/app/src/main/kotlin/nl/eduid/screens/resetpassword/ResetPasswordScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/resetpassword/ResetPasswordScreen.kt @@ -2,14 +2,15 @@ package nl.eduid.screens.resetpassword import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn -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 @@ -21,6 +22,7 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -29,7 +31,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout import androidx.lifecycle.flowWithLifecycle import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -84,6 +85,7 @@ fun ResetPasswordScreen( ResetPasswordScreenContent( password = viewModel.uiState.password, inProgress = viewModel.uiState.inProgress, + padding = it, onResetPasswordClicked = { viewModel.resetPasswordLink() waitForVmEvent = true @@ -96,21 +98,21 @@ fun ResetPasswordScreen( fun ResetPasswordScreenContent( password: Password = Password.Add, inProgress: Boolean, + padding: PaddingValues = PaddingValues(), onResetPasswordClicked: () -> Unit = {}, goBack: () -> Unit = {}, -) = ConstraintLayout( - modifier = Modifier.fillMaxSize() +) = Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.SpaceBetween ) { - - val (body, bottomColumn) = createRefs() - Column(verticalArrangement = Arrangement.Top, modifier = Modifier - .constrainAs(body) { - linkTo(parent.top, bottomColumn.top, bias = 0F) - } - .verticalScroll(rememberScrollState()) - + Column( + modifier = Modifier + .fillMaxWidth(), + horizontalAlignment = Alignment.Start, ) { - Spacer(Modifier.height(36.dp)) if (inProgress) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } else { @@ -136,38 +138,32 @@ fun ResetPasswordScreenContent( ) Spacer(Modifier.height(36.dp)) } - Column( - verticalArrangement = Arrangement.Bottom, - modifier = Modifier.constrainAs(bottomColumn) { - bottom.linkTo(parent.bottom, margin = 24.dp) - }, + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .padding(bottom = 24.dp), ) { - Column( - Modifier.fillMaxWidth() - ) { - Row( - horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxWidth() - ) { - PrimaryButton( - enabled = !inProgress, - modifier = Modifier.widthIn(min = 140.dp), - text = stringResource(R.string.reset_password_cancel_button), - onClick = goBack, - buttonBackgroundColor = Color.Transparent, - buttonTextColor = TextGrey, - buttonBorderColor = ButtonBorderGrey, - ) - PrimaryButton( - enabled = !inProgress, - modifier = Modifier.widthIn(min = 140.dp), - text = stringResource(R.string.reset_password_confirm_button), - onClick = onResetPasswordClicked, - buttonBackgroundColor = ButtonBlue, - buttonTextColor = Color.White, - ) - } - } + PrimaryButton( + enabled = !inProgress, + modifier = Modifier.widthIn(min = 140.dp), + text = stringResource(R.string.reset_password_cancel_button), + onClick = goBack, + buttonBackgroundColor = Color.Transparent, + buttonTextColor = TextGrey, + buttonBorderColor = ButtonBorderGrey, + ) + PrimaryButton( + enabled = !inProgress, + modifier = Modifier.widthIn(min = 140.dp), + text = stringResource(R.string.reset_password_confirm_button), + onClick = onResetPasswordClicked, + buttonBackgroundColor = ButtonBlue, + buttonTextColor = Color.White, + ) } + } @Preview diff --git a/app/src/main/kotlin/nl/eduid/screens/resetpasswordconfirm/ResetPasswordConfirmScreen.kt b/app/src/main/kotlin/nl/eduid/screens/resetpasswordconfirm/ResetPasswordConfirmScreen.kt index 285fe48d..66584a7a 100644 --- a/app/src/main/kotlin/nl/eduid/screens/resetpasswordconfirm/ResetPasswordConfirmScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/resetpasswordconfirm/ResetPasswordConfirmScreen.kt @@ -1,11 +1,19 @@ package nl.eduid.screens.resetpasswordconfirm -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.relocation.BringIntoViewRequester +import androidx.compose.foundation.relocation.bringIntoViewRequester import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions @@ -20,6 +28,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -27,6 +36,7 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.focus.onFocusEvent import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager @@ -39,14 +49,15 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout import androidx.lifecycle.flowWithLifecycle import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.launch import nl.eduid.R import nl.eduid.ui.AlertDialogWithSingleButton import nl.eduid.ui.EduIdTopAppBar import nl.eduid.ui.PrimaryButton +import nl.eduid.ui.keyboardAsState import nl.eduid.ui.theme.ButtonBorderGrey import nl.eduid.ui.theme.ButtonGreen import nl.eduid.ui.theme.EduidAppAndroidTheme @@ -91,6 +102,7 @@ fun ResetPasswordConfirmScreen( confirmPasswordInput = viewModel.uiState.confirmPasswordInput, isAddPassword = isAddPassword, inProgress = viewModel.uiState.inProgress, + padding = it, onNewPasswordChange = { viewModel.onNewPasswordInput(it) }, onConfirmPasswordChange = { viewModel.onConfirmPasswordInput(it) @@ -105,90 +117,117 @@ fun ResetPasswordConfirmScreen( } } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@OptIn( + ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class, ExperimentalLayoutApi::class, + ExperimentalFoundationApi::class +) @Composable fun ResetPasswordConfirmScreenContent( newPasswordInput: String = "", confirmPasswordInput: String = "", isAddPassword: Boolean = false, inProgress: Boolean = false, + padding: PaddingValues = PaddingValues(), onNewPasswordChange: (newValue: String) -> Unit = {}, onConfirmPasswordChange: (newValue: String) -> Unit = {}, onResetPasswordClicked: () -> Unit = {}, onDeletePasswordClicked: () -> Unit = {}, ) { val keyboardController = LocalSoftwareKeyboardController.current - val focusManager = LocalFocusManager.current - ConstraintLayout( + Column( modifier = Modifier .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(padding) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp), ) { - val (body, bottomColumn) = createRefs() - Column( - verticalArrangement = Arrangement.Top, - modifier = Modifier - .constrainAs(body) { - linkTo(parent.top, bottomColumn.top, bias = 0F) - } - .verticalScroll(rememberScrollState()) - + val isKeyboardOpen by keyboardAsState() + AnimatedVisibility( + !isKeyboardOpen, ) { - Spacer(Modifier.height(36.dp)) - Text( - style = MaterialTheme.typography.titleLarge.copy( - textAlign = TextAlign.Start, - color = ButtonGreen - ), - text = if (isAddPassword) { - stringResource(R.string.reset_password_add_title) - } else { - stringResource(R.string.reset_password_change_title) - }, - modifier = Modifier - .fillMaxWidth() - ) - Spacer(Modifier.height(32.dp)) - - if (inProgress) { - LinearProgressIndicator( - modifier = Modifier.fillMaxWidth() + Column() { + Text( + style = MaterialTheme.typography.titleLarge.copy( + textAlign = TextAlign.Start, color = ButtonGreen + ), text = if (isAddPassword) { + stringResource(R.string.reset_password_add_title) + } else { + stringResource(R.string.reset_password_change_title) + }, modifier = Modifier.fillMaxWidth() ) - Spacer(Modifier.height(16.dp)) + Spacer(Modifier.height(32.dp)) } - Text( - style = MaterialTheme.typography.bodyLarge.copy(textAlign = TextAlign.Start), - text = stringResource(R.string.reset_password_confirm_subtitle), - modifier = Modifier - .fillMaxWidth() + } + + if (inProgress) { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth() ) - Spacer(Modifier.height(20.dp)) + Spacer(Modifier.height(16.dp)) + } + Text( + style = MaterialTheme.typography.bodyLarge.copy( + textAlign = + TextAlign.Start + ), + text = stringResource(R.string.reset_password_confirm_subtitle), + modifier = Modifier.fillMaxWidth() + ) + val bringIntoViewRequester = BringIntoViewRequester() + Column( + modifier = Modifier + .imePadding() + .bringIntoViewRequester(bringIntoViewRequester) + ) { + val coroutineScope = rememberCoroutineScope() + val focusManager = LocalFocusManager.current + Spacer(Modifier.height(8.dp)) OutlinedTextField( value = newPasswordInput, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next, - keyboardType = KeyboardType.Password + imeAction = ImeAction.Next, keyboardType = KeyboardType.Password ), keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Down) }), onValueChange = { onNewPasswordChange(it) }, label = { Text(stringResource(R.string.reset_password_password_label)) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .onFocusEvent { event -> + if (event.isFocused) { + coroutineScope.launch { + bringIntoViewRequester.bringIntoView() + } + } + } ) - Spacer(Modifier.height(24.dp)) + Spacer(Modifier.height(16.dp)) OutlinedTextField( value = confirmPasswordInput, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Done, - keyboardType = KeyboardType.Password + imeAction = ImeAction.Done, keyboardType = KeyboardType.Password ), - keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }), + keyboardActions = KeyboardActions(onDone = { + keyboardController?.hide() + focusManager.clearFocus() + }), onValueChange = { onConfirmPasswordChange(it) }, label = { Text(stringResource(R.string.reset_password_repeat_password_label)) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .onFocusEvent { event -> + if (event.isFocused) { + coroutineScope.launch { + bringIntoViewRequester.bringIntoView() + } + } + } + ) - Spacer(Modifier.height(24.dp)) + Spacer(Modifier.height(16.dp)) PrimaryButton( text = if (isAddPassword) { stringResource(R.string.button_add_password) @@ -200,37 +239,34 @@ fun ResetPasswordConfirmScreenContent( modifier = Modifier .fillMaxWidth() ) - Spacer(Modifier.height(30.dp)) - if (!isAddPassword) { - Divider(color = TextBlack, thickness = 1.dp) - Spacer(Modifier.height(16.dp)) - Text( - style = MaterialTheme.typography.titleLarge.copy( - textAlign = TextAlign.Start, - color = ButtonGreen - ), - text = stringResource(R.string.reset_password_confirm_second_title), - modifier = Modifier - .fillMaxWidth() - ) - Spacer(Modifier.height(6.dp)) - Text( - style = MaterialTheme.typography.bodyLarge.copy(textAlign = TextAlign.Start), - text = stringResource(R.string.reset_password_confirm_second_subtitle), - modifier = Modifier - .fillMaxWidth() - ) - Spacer(Modifier.height(12.dp)) - PrimaryButton( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.button_delete_password), - enabled = !inProgress, - onClick = onDeletePasswordClicked, - buttonBackgroundColor = Color.Transparent, - buttonTextColor = TextGrey, - buttonBorderColor = ButtonBorderGrey, - ) - } + } + if (!isAddPassword) { + Spacer(Modifier.height(16.dp)) + Divider(color = TextBlack, thickness = 1.dp) + Spacer(Modifier.height(16.dp)) + Text( + style = MaterialTheme.typography.titleLarge.copy( + textAlign = TextAlign.Start, color = ButtonGreen + ), + text = stringResource(R.string.reset_password_confirm_second_title), + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(6.dp)) + Text( + style = MaterialTheme.typography.bodyLarge.copy(textAlign = TextAlign.Start), + text = stringResource(R.string.reset_password_confirm_second_subtitle), + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(12.dp)) + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.button_delete_password), + enabled = !inProgress, + onClick = onDeletePasswordClicked, + buttonBackgroundColor = Color.Transparent, + buttonTextColor = TextGrey, + buttonBorderColor = ButtonBorderGrey, + ) } } } diff --git a/app/src/main/kotlin/nl/eduid/screens/security/SecurityScreen.kt b/app/src/main/kotlin/nl/eduid/screens/security/SecurityScreen.kt index 97185d11..985e3d0f 100644 --- a/app/src/main/kotlin/nl/eduid/screens/security/SecurityScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/security/SecurityScreen.kt @@ -1,12 +1,13 @@ package nl.eduid.screens.security -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize 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.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -36,6 +37,7 @@ fun SecurityScreen( ) { SecurityScreenContent( securityInfo = viewModel.uiState, + padding = it, onConfigurePasswordClicked = onConfigurePasswordClick, onEditEmailClicked = onEditEmailClicked, on2FaClicked = on2FaClicked, @@ -46,15 +48,18 @@ fun SecurityScreen( @Composable fun SecurityScreenContent( securityInfo: SecurityScreenData, + padding: PaddingValues = PaddingValues(), onConfigurePasswordClicked: () -> Unit = {}, onEditEmailClicked: () -> Unit = {}, on2FaClicked: () -> Unit = {}, - dismissError:()->Unit = {} + dismissError: () -> Unit = {}, ) = Column( - verticalArrangement = Arrangement.Bottom, - modifier = Modifier.verticalScroll(rememberScrollState()) + modifier = Modifier + .fillMaxSize() + .padding(padding) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp), ) { - Spacer(Modifier.height(36.dp)) if (securityInfo.errorData != null) { val context = LocalContext.current AlertDialogWithSingleButton( @@ -117,7 +122,6 @@ fun SecurityScreenContent( "" }, onClick = onConfigurePasswordClicked, endIcon = R.drawable.edit_icon ) - Spacer(Modifier.height(16.dp)) } diff --git a/app/src/main/kotlin/nl/eduid/screens/start/WelcomeStartScreen.kt b/app/src/main/kotlin/nl/eduid/screens/start/WelcomeStartScreen.kt index a0d6981a..0cc92055 100644 --- a/app/src/main/kotlin/nl/eduid/screens/start/WelcomeStartScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/start/WelcomeStartScreen.kt @@ -1,8 +1,20 @@ package nl.eduid.screens.start import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -32,19 +44,25 @@ fun WelcomeStartScreen( ) { val uiState by viewModel.uiState.observeAsState(UiState()) - WelcomeStartContent(uiState) { + WelcomeStartContent(uiState, padding = it) { onNext(uiState.isAccountLinked) } } @Composable -private fun WelcomeStartContent(uiState: UiState, onNext: () -> Unit = {}) { +private fun WelcomeStartContent( + uiState: UiState, + padding: PaddingValues = PaddingValues(), + onNext: () -> Unit = {}, +) { ConstraintLayout( modifier = Modifier .fillMaxSize() - .padding(horizontal = 30.dp) + .padding(padding) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp) ) { - val (bottomButton, bottomSpacer) = createRefs() + val (bottomButton) = createRefs() Column( modifier = Modifier.fillMaxSize() ) { @@ -148,13 +166,6 @@ private fun WelcomeStartContent(uiState: UiState, onNext: () -> Unit = {}) { modifier = Modifier .fillMaxWidth() .constrainAs(bottomButton) { - bottom.linkTo(bottomSpacer.top) - }, - ) - Spacer( - Modifier - .height(40.dp) - .constrainAs(bottomSpacer) { bottom.linkTo(parent.bottom) }, ) diff --git a/app/src/main/kotlin/nl/eduid/screens/twofactorkey/TwoFactorKeyScreen.kt b/app/src/main/kotlin/nl/eduid/screens/twofactorkey/TwoFactorKeyScreen.kt index 0928123f..9088873f 100644 --- a/app/src/main/kotlin/nl/eduid/screens/twofactorkey/TwoFactorKeyScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/twofactorkey/TwoFactorKeyScreen.kt @@ -1,12 +1,15 @@ package nl.eduid.screens.twofactorkey -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize 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.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -32,7 +35,7 @@ fun TwoFactorKeyScreen( goBack: () -> Unit, ) = EduIdTopAppBar( onBackClicked = goBack -) { +) { padding -> viewModel.uiState.errorData?.let { errorData -> val context = LocalContext.current AlertDialogWithSingleButton( @@ -48,6 +51,7 @@ fun TwoFactorKeyScreen( } else { TwoFactorKeyScreenContent(keyList = viewModel.uiState.keys, isLoading = viewModel.uiState.isLoading, + padding = padding, onDeleteKeyPressed = onDeleteKeyPressed, onChangeBiometric = { key, biometricFlag -> viewModel.changeBiometric( @@ -62,14 +66,16 @@ fun TwoFactorKeyScreen( fun TwoFactorKeyScreenContent( keyList: List, isLoading: Boolean = false, + padding: PaddingValues = PaddingValues(), onDeleteKeyPressed: (id: String) -> Unit = {}, onChangeBiometric: (IdentityData, Boolean) -> Unit = { _, _ -> }, - onExpand: (IdentityData?) -> Unit = { _ -> } + onExpand: (IdentityData?) -> Unit = { _ -> }, ) = Column( - verticalArrangement = Arrangement.Bottom, - modifier = Modifier.verticalScroll(rememberScrollState()) + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(horizontal = 24.dp), ) { - Spacer(Modifier.height(36.dp)) Text( style = MaterialTheme.typography.titleLarge.copy( @@ -98,25 +104,28 @@ fun TwoFactorKeyScreenContent( Spacer(Modifier.height(6.dp)) } - keyList.forEach { twoFaInfo -> - KeyInfoCard( - title = twoFaInfo.title, - subtitle = twoFaInfo.subtitle, - keyData = twoFaInfo, - onDeleteButtonClicked = { onDeleteKeyPressed(twoFaInfo.uniqueKey) }, - onChangeBiometric = onChangeBiometric, - onExpand = onExpand - ) + LazyColumn( + modifier = Modifier + .navigationBarsPadding() + .padding(bottom = 24.dp) + ) { + items(keyList) { twoFaInfo -> + KeyInfoCard( + title = twoFaInfo.title, + subtitle = twoFaInfo.subtitle, + keyData = twoFaInfo, + onDeleteButtonClicked = { onDeleteKeyPressed(twoFaInfo.uniqueKey) }, + onChangeBiometric = onChangeBiometric, + onExpand = onExpand + ) + } } } @Composable fun TwoFactorKeyScreenNoContent( ) = Column( - verticalArrangement = Arrangement.Bottom, - modifier = Modifier.verticalScroll(rememberScrollState()) ) { - Spacer(Modifier.height(36.dp)) Text( style = MaterialTheme.typography.titleLarge.copy( @@ -136,6 +145,15 @@ fun TwoFactorKeyScreenNoContent( @Composable private fun PreviewSecurityScreenContent() = EduidAppAndroidTheme { TwoFactorKeyScreenContent( - keyList = listOf(), + keyList = listOf( + IdentityData( + uniqueKey = "uniqueId", + title = "title", + subtitle = "subtitle", + account = "account", + biometricFlag = true, + isExpanded = true + ) + ), ) } \ No newline at end of file diff --git a/app/src/main/kotlin/nl/eduid/screens/twofactorkeydelete/TwoFactorKeyDeleteScreen.kt b/app/src/main/kotlin/nl/eduid/screens/twofactorkeydelete/TwoFactorKeyDeleteScreen.kt index fcdb4839..08fc81e0 100644 --- a/app/src/main/kotlin/nl/eduid/screens/twofactorkeydelete/TwoFactorKeyDeleteScreen.kt +++ b/app/src/main/kotlin/nl/eduid/screens/twofactorkeydelete/TwoFactorKeyDeleteScreen.kt @@ -4,14 +4,15 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn -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 @@ -23,6 +24,7 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -82,9 +84,9 @@ fun TwoFactorKeyDeleteScreen( } } } - TwoFactorKeyDeleteScreenContent( inProgress = viewModel.uiState.inProgress, + padding = it, onDeleteClicked = { waitForVmEvent = true viewModel.deleteKey(twoFaKeyId) @@ -96,20 +98,18 @@ fun TwoFactorKeyDeleteScreen( @Composable private fun TwoFactorKeyDeleteScreenContent( inProgress: Boolean = false, + padding: PaddingValues = PaddingValues(), onDeleteClicked: () -> Unit = {}, goBack: () -> Unit = {}, ) = Column( modifier = Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()) + .padding(padding) + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp), + verticalArrangement = Arrangement.SpaceBetween ) { - - Spacer(Modifier.height(36.dp)) - Column( - modifier = Modifier - .fillMaxSize() - .weight(1f) - ) { + Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.Start) { Text( text = stringResource(R.string.delete_two_key_title), style = MaterialTheme.typography.titleLarge.copy( @@ -154,31 +154,28 @@ private fun TwoFactorKeyDeleteScreenContent( modifier = Modifier.fillMaxWidth(), ) } - Column( - Modifier.fillMaxWidth() + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier + .fillMaxWidth(), ) { - Row( - horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxWidth() - ) { - PrimaryButton( - enabled = !inProgress, - text = stringResource(id = R.string.button_cancel), - modifier = Modifier.widthIn(min = 140.dp), - onClick = goBack, - buttonBackgroundColor = Color.Transparent, - buttonTextColor = TextGrey, - buttonBorderColor = ButtonBorderGrey, - ) - PrimaryButton( - enabled = !inProgress, - text = stringResource(id = R.string.button_confirm), - modifier = Modifier.widthIn(min = 140.dp), - onClick = onDeleteClicked, - buttonBackgroundColor = ButtonRed, - buttonTextColor = Color.White, - ) - } - Spacer(Modifier.height(24.dp)) + PrimaryButton( + enabled = !inProgress, + text = stringResource(id = R.string.button_cancel), + modifier = Modifier.widthIn(min = 140.dp), + onClick = goBack, + buttonBackgroundColor = Color.Transparent, + buttonTextColor = TextGrey, + buttonBorderColor = ButtonBorderGrey, + ) + PrimaryButton( + enabled = !inProgress, + text = stringResource(id = R.string.button_confirm), + modifier = Modifier.widthIn(min = 140.dp), + onClick = onDeleteClicked, + buttonBackgroundColor = ButtonRed, + buttonTextColor = Color.White, + ) } } diff --git a/app/src/main/kotlin/nl/eduid/ui/Buttons.kt b/app/src/main/kotlin/nl/eduid/ui/Buttons.kt index b87e637e..9e874750 100644 --- a/app/src/main/kotlin/nl/eduid/ui/Buttons.kt +++ b/app/src/main/kotlin/nl/eduid/ui/Buttons.kt @@ -4,10 +4,21 @@ import androidx.annotation.DrawableRes import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -37,7 +48,7 @@ fun PrimaryButton( shape = RoundedCornerShape(CornerSize(6.dp)), border = BorderStroke(1.dp, buttonBorderColor), colors = ButtonDefaults.buttonColors(containerColor = buttonBackgroundColor), - modifier = modifier.sizeIn(minHeight = 48.dp) + modifier = modifier.requiredHeight(height = 48.dp) ) { Text( text = text, diff --git a/app/src/main/kotlin/nl/eduid/ui/EduIdTopAppBar.kt b/app/src/main/kotlin/nl/eduid/ui/EduIdTopAppBar.kt index 1caa5555..7036a160 100644 --- a/app/src/main/kotlin/nl/eduid/ui/EduIdTopAppBar.kt +++ b/app/src/main/kotlin/nl/eduid/ui/EduIdTopAppBar.kt @@ -23,27 +23,29 @@ fun EduIdTopAppBar( onBackClicked: () -> Unit = {}, withBackIcon: Boolean = true, snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, - content: @Composable () -> Unit, + content: @Composable (PaddingValues) -> Unit, ) { - val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() - Scaffold(modifier = Modifier - .systemBarsPadding() - .imePadding() - .nestedScroll(scrollBehavior.nestedScrollConnection), + val topBarState = rememberTopAppBarState() + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topBarState) + Scaffold( + modifier = Modifier + .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, topBar = { CenterAlignedTopAppBar( - modifier = Modifier - .padding(top = 16.dp, bottom = 16.dp, start = 15.dp, end = 30.dp), + modifier = Modifier.padding(top = 8.dp, bottom = 8.dp), navigationIcon = { if (withBackIcon) { - IconButton(onClick = onBackClicked) { + IconButton( + onClick = onBackClicked, + modifier = Modifier.padding(start = 8.dp) + ) { Icon( imageVector = Icons.Filled.ArrowBack, contentDescription = stringResource(R.string.button_back), tint = MaterialTheme.colorScheme.primary, modifier = Modifier - .size(width = 53.dp, height = 53.dp) + .size(width = 48.dp, height = 48.dp) ) } } @@ -52,21 +54,20 @@ fun EduIdTopAppBar( Image( painter = painterResource(R.drawable.logo_eduid_big), contentDescription = "", - modifier = Modifier.size(width = 122.dp, height = 46.dp), + modifier = Modifier + .size(width = 122.dp, height = 48.dp), alignment = Alignment.Center ) }, scrollBehavior = scrollBehavior ) - }) { paddingValues -> - Row( - modifier = Modifier - .padding(paddingValues) - .padding(horizontal = 30.dp) - .fillMaxSize() - ) { - content() - } + }, + contentWindowInsets = ScaffoldDefaults + .contentWindowInsets + .exclude(WindowInsets.navigationBars) + .exclude(WindowInsets.ime), + ) { paddingValues -> + content(paddingValues) } } diff --git a/app/src/main/kotlin/nl/eduid/ui/Ime.kt b/app/src/main/kotlin/nl/eduid/ui/Ime.kt new file mode 100644 index 00000000..dc67991c --- /dev/null +++ b/app/src/main/kotlin/nl/eduid/ui/Ime.kt @@ -0,0 +1,24 @@ +package nl.eduid.ui + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalView +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat + +@Composable +fun keyboardAsState(): State { + val keyboardState = remember { mutableStateOf(false) } + val view = LocalView.current + LaunchedEffect(view) { + ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> + keyboardState.value = insets.isVisible(WindowInsetsCompat.Type.ime()) + insets + } + } + return keyboardState +} + diff --git a/app/src/main/kotlin/nl/eduid/ui/InfoField.kt b/app/src/main/kotlin/nl/eduid/ui/InfoField.kt index f1fa3f16..6c637591 100644 --- a/app/src/main/kotlin/nl/eduid/ui/InfoField.kt +++ b/app/src/main/kotlin/nl/eduid/ui/InfoField.kt @@ -36,6 +36,7 @@ fun InfoField( subtitle: String, onClick: () -> Unit = {}, label: String = "", + capitalizeTitle: Boolean = true, @DrawableRes endIcon: Int = R.drawable.edit_icon, ) = Column(modifier = Modifier.fillMaxWidth()) { if (label.isNotBlank()) { @@ -66,7 +67,11 @@ fun InfoField( .weight(1f) ) { Text( - text = title.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }, + text = if (capitalizeTitle) { + title.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + } else { + title + }, style = MaterialTheme.typography.bodyLarge.copy( textAlign = TextAlign.Start, fontWeight = FontWeight.Bold, lineHeight = 20.sp ), diff --git a/app/src/main/kotlin/nl/eduid/ui/PinInputField.kt b/app/src/main/kotlin/nl/eduid/ui/PinInputField.kt index 63de6d96..83fed82f 100644 --- a/app/src/main/kotlin/nl/eduid/ui/PinInputField.kt +++ b/app/src/main/kotlin/nl/eduid/ui/PinInputField.kt @@ -1,27 +1,33 @@ package nl.eduid.ui +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.style.LineHeightStyle.* import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import kotlinx.coroutines.android.awaitFrame import nl.eduid.R import nl.eduid.ui.theme.EduidAppAndroidTheme + @OptIn(ExperimentalMaterial3Api::class) @Composable fun PinInputField( @@ -29,6 +35,7 @@ fun PinInputField( pinCode: String, isPinInvalid: Boolean, modifier: Modifier = Modifier, + shouldShowKeyboard: Boolean = true, onPinChange: (String) -> Unit = {}, submitPin: () -> Unit = {}, pinMaxLength: Int = PIN_MAX_LENGTH, @@ -37,22 +44,21 @@ fun PinInputField( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { + val focusRequester = remember { FocusRequester() } + if (!label.isNullOrEmpty()) { Text( text = if (isPinInvalid) "$label*" else label, style = MaterialTheme.typography.bodyLarge, - modifier = Modifier - .fillMaxWidth() + modifier = Modifier.fillMaxWidth() ) } Spacer(Modifier.height(8.dp)) Box( - modifier = Modifier - .fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), ) { Row( - modifier = Modifier - .wrapContentWidth(align = Alignment.CenterHorizontally), + modifier = Modifier.wrapContentWidth(align = Alignment.CenterHorizontally), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { for (i in 0 until pinMaxLength) { @@ -61,58 +67,63 @@ fun PinInputField( } else { "" } - OutlinedTextField( - value = code, - onValueChange = {}, - singleLine = true, - visualTransformation = PasswordVisualTransformation(), - textStyle = MaterialTheme.typography.titleLarge.copy(textAlign = TextAlign.Center), + Text( + text = if (code.isNotEmpty()) "\u2022" else code, + style = MaterialTheme.typography.titleLarge.copy( + textAlign = TextAlign.Center + ), modifier = Modifier .fillMaxWidth() + .height(TextFieldDefaults.MinHeight) + .border( + border = BorderStroke( + 1.dp, Color(0xFFC3C6CF) + ), shape = OutlinedTextFieldDefaults.shape + ) .weight(1f) ) } } val options = if (pinCode.length == pinMaxLength) { KeyboardOptions( - imeAction = ImeAction.Done, - keyboardType = KeyboardType.Number + imeAction = ImeAction.Done, keyboardType = KeyboardType.Number ) } else { KeyboardOptions( - imeAction = ImeAction.Next, - keyboardType = KeyboardType.Number + imeAction = ImeAction.Next, keyboardType = KeyboardType.Number ) } - val focusRequester = remember { FocusRequester() } OutlinedTextField( value = pinCode, onValueChange = onPinChange, singleLine = true, isError = isPinInvalid, keyboardOptions = options, - keyboardActions = KeyboardActions( - onDone = { - submitPin.invoke() - } - ), + keyboardActions = KeyboardActions(onDone = { + submitPin.invoke() + }), modifier = Modifier .fillMaxWidth() + .height(TextFieldDefaults.MinHeight) .focusRequester(focusRequester) .alpha(0f), ) } + if (shouldShowKeyboard) { + LaunchedEffect(focusRequester) { + awaitFrame() + focusRequester.requestFocus() + } + } // Supporting text for error message. if (isPinInvalid) { Text( text = stringResource(R.string.confirmpin_mismatch), style = MaterialTheme.typography.bodySmall.copy( - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.error + fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.error ), - modifier = Modifier - .padding(start = 8.dp, top = 4.dp) + modifier = Modifier.padding(start = 8.dp, top = 4.dp) ) } } diff --git a/app/src/main/kotlin/nl/eduid/ui/theme/Theme.kt b/app/src/main/kotlin/nl/eduid/ui/theme/Theme.kt index 44f5e0c5..0b32ecb4 100644 --- a/app/src/main/kotlin/nl/eduid/ui/theme/Theme.kt +++ b/app/src/main/kotlin/nl/eduid/ui/theme/Theme.kt @@ -4,8 +4,11 @@ import android.app.Activity import android.content.Context import android.content.ContextWrapper import android.os.Build -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.toArgb @@ -82,13 +85,14 @@ private val DarkColors = darkColorScheme( fun EduidAppAndroidTheme( darkTheme: Boolean = false, //isSystemInDarkTheme(), // Dynamic color is available on Android 12+ - dynamicColor: Boolean = true, content: @Composable () -> Unit + dynamicColor: Boolean = true, content: @Composable () -> Unit, ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } + darkTheme -> DarkColors else -> LightColors } From 47944dd839e19b3bb00e21e5f190e0948f846ab6 Mon Sep 17 00:00:00 2001 From: Iulia Stana Date: Thu, 25 May 2023 22:59:41 +0200 Subject: [PATCH 2/2] Fix mistake in spacing on delete service --- .../nl/eduid/screens/dataactivity/DeleteServiceContent.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/nl/eduid/screens/dataactivity/DeleteServiceContent.kt b/app/src/main/kotlin/nl/eduid/screens/dataactivity/DeleteServiceContent.kt index c93ad3eb..11a602fa 100644 --- a/app/src/main/kotlin/nl/eduid/screens/dataactivity/DeleteServiceContent.kt +++ b/app/src/main/kotlin/nl/eduid/screens/dataactivity/DeleteServiceContent.kt @@ -43,10 +43,12 @@ fun DeleteServiceContent( Column( modifier = Modifier .fillMaxSize() - .padding(horizontal = 24.dp), + .navigationBarsPadding() + .padding(start = 24.dp, end = 24.dp, bottom = 24.dp), verticalArrangement = Arrangement.SpaceBetween ) { Column( + horizontalAlignment = Alignment.Start, modifier = Modifier .fillMaxSize() ) { @@ -97,9 +99,7 @@ fun DeleteServiceContent( Row( horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier - .fillMaxWidth() - .navigationBarsPadding() - .padding(bottom = 24.dp), + .fillMaxWidth(), ) { SecondaryButton( text = stringResource(R.string.button_cancel),