diff options
Diffstat (limited to 'anastasis/src/main/java/net/taler/anastasis/ui/recovery/SolveChallengeScreen.kt')
-rw-r--r-- | anastasis/src/main/java/net/taler/anastasis/ui/recovery/SolveChallengeScreen.kt | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SolveChallengeScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SolveChallengeScreen.kt new file mode 100644 index 0000000..ece93d5 --- /dev/null +++ b/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SolveChallengeScreen.kt @@ -0,0 +1,141 @@ +/* + * This file is part of GNU Taler + * (C) 2023 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.anastasis.ui.recovery + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import net.taler.anastasis.R +import net.taler.anastasis.models.AuthMethod +import net.taler.anastasis.models.ChallengeFeedback +import net.taler.anastasis.models.ReducerState +import net.taler.anastasis.shared.Utils +import net.taler.anastasis.ui.forms.EditAnswerForm +import net.taler.anastasis.ui.reusable.pages.WizardPage +import net.taler.anastasis.ui.theme.LocalSpacing +import net.taler.anastasis.viewmodels.ReducerViewModel +import net.taler.anastasis.viewmodels.ReducerViewModelI + +@Composable +fun SolveChallengeScreen( + viewModel: ReducerViewModelI = hiltViewModel<ReducerViewModel>(), +) { + val state by viewModel.reducerState.collectAsState() + val reducerState = state as? ReducerState.Recovery + ?: error("invalid reducer state type") + + val selectedChallengeUuid = reducerState.selectedChallengeUuid + val challenge = remember(selectedChallengeUuid) { + reducerState.recoveryInformation?.challenges?.find { + it.uuid == selectedChallengeUuid + } ?: error("empty challenge") + } + val challengeFeedback = remember(selectedChallengeUuid) { + reducerState.challengeFeedback?.get(challenge.uuid) + } + + val question by remember { mutableStateOf(challenge.instructions) } + var answer by remember { mutableStateOf("") } + + WizardPage( + title = stringResource(R.string.solve_challenge_title), + onBackClicked = { viewModel.goHome() }, + onPrevClicked = { viewModel.goBack() }, + onNextClicked = { + when (challenge.type) { + AuthMethod.Type.Question -> { + viewModel.reducerManager?.solveChallenge(answer) + } + AuthMethod.Type.Sms, AuthMethod.Type.Email -> { + viewModel.reducerManager?.solveChallenge(Utils.extractVerificationCode(answer)) + } + // TODO: handle other challenge types + else -> {} + } + }, + ) { + Column { + if (challengeFeedback != null && challengeFeedback !is ChallengeFeedback.Solved) { + Box( + Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.primary), + contentAlignment = Alignment.Center, + ) { + Text( + when (challengeFeedback) { + is ChallengeFeedback.Solved -> return@Box + is ChallengeFeedback.IncorrectAnswer -> stringResource(R.string.challenge_feedback_incorrect_answer) + is ChallengeFeedback.CodeInFile -> challengeFeedback.displayHint + is ChallengeFeedback.CodeSent -> challengeFeedback.displayHint + is ChallengeFeedback.Unsupported -> stringResource(R.string.challenge_feedback_unsupported) + is ChallengeFeedback.RateLimitExceeded -> stringResource(R.string.challenge_feedback_rate_limit_exceeded) + is ChallengeFeedback.BankTransferRequired -> stringResource(R.string.challenge_feedback_bank_transfer_required) + is ChallengeFeedback.ServerFailure -> stringResource(R.string.challenge_feedback_server_failure) + is ChallengeFeedback.TruthUnknown -> stringResource(R.string.challenge_feedback_truth_unknown) + is ChallengeFeedback.TalerPaymentRequired -> stringResource(R.string.challenge_feedback_taler_payment_required) + }, + modifier = Modifier.padding(LocalSpacing.current.small), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onPrimary, + ) + } + } + Box(Modifier.padding(LocalSpacing.current.medium)) { + when (challenge.type) { + AuthMethod.Type.Question -> EditAnswerForm( + question = question, + answerLabel = stringResource(R.string.answer), + answer = answer, + onAnswerEdited = { answer = it } + ) + + AuthMethod.Type.Sms, AuthMethod.Type.Email -> EditAnswerForm( + answerLabel = stringResource(R.string.code), + answer = answer, + onAnswerEdited = { answer = it }, + regex = "^A-\\d{5}-\\d{3}-\\d{4}-\\d{3}$", + ) + + // TODO: handle other challenge types + else -> {} + } + } + } + } +} + +@Preview +@Composable +fun SolveChallengeScreenPreview() { + SolveChallengeScreen() +}
\ No newline at end of file |