summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2023-08-31 22:11:06 -0600
committerIván Ávalos <avalos@disroot.org>2023-11-11 13:20:09 -0600
commitbba3c0c3e2c89f2a9b37a2d8851b8e0741f25b0e (patch)
tree1bce7e7b12f3af03cda638b7f7c5e2d384b10005
parent5a6a280fef4b91c18b73763420b961e602507052 (diff)
downloadtaler-android-bba3c0c3e2c89f2a9b37a2d8851b8e0741f25b0e.tar.gz
taler-android-bba3c0c3e2c89f2a9b37a2d8851b8e0741f25b0e.tar.bz2
taler-android-bba3c0c3e2c89f2a9b37a2d8851b8e0741f25b0e.zip
Refactor FakeReducerViewModel + refactor provider management
Signed-off-by: Iván Ávalos <avalos@disroot.org>
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupFinishedScreen.kt30
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/backup/EditSecretScreen.kt24
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/backup/ReviewPoliciesScreen.kt76
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/backup/SelectAuthMethodsScreen.kt186
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/common/ManageProvidersScreen.kt171
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/common/SelectContinentScreen.kt14
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/common/SelectCountryScreen.kt22
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/common/SelectUserAttributesScreen.kt30
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/recovery/RecoveryFinishedScreen.kt21
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/recovery/SelectChallengeScreen.kt35
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/recovery/SelectSecretScreen.kt123
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/recovery/SolveChallengeScreen.kt8
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/viewmodels/FakeReducerViewModel.kt274
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/viewmodels/ReducerViewModel.kt23
-rw-r--r--anastasis/src/main/res/values/strings.xml1
15 files changed, 573 insertions, 465 deletions
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupFinishedScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupFinishedScreen.kt
index fc89caa..df499be 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupFinishedScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupFinishedScreen.kt
@@ -53,10 +53,9 @@ import net.taler.anastasis.models.SuccessDetail
import net.taler.anastasis.shared.Utils
import net.taler.anastasis.ui.reusable.pages.WizardPage
import net.taler.anastasis.ui.theme.LocalSpacing
-import net.taler.anastasis.viewmodels.FakeReducerViewModel
+import net.taler.anastasis.viewmodels.FakeBackupViewModel
import net.taler.anastasis.viewmodels.ReducerViewModel
import net.taler.anastasis.viewmodels.ReducerViewModelI
-import net.taler.common.Timestamp
@Composable
fun BackupFinishedScreen(
@@ -168,29 +167,8 @@ fun ProviderCard(
@Composable
fun BackupFinishedScreenPreview() {
BackupFinishedScreen(
- viewModel = FakeReducerViewModel(
- state = ReducerState.Backup(
- backupState = BackupStates.BackupFinished,
- authenticationProviders = mapOf(
- "https://localhost:8088/" to AuthenticationProviderStatus.Ok(
- httpStatus = 200,
- methods = listOf(),
- annualFee = "EUR:0.99",
- truthUploadFee = "EUR:3.99",
- liabilityLimit = "EUR:1",
- currency = "EUR",
- storageLimitInMegabytes = 1,
- businessName = "Anastasis 42",
- providerSalt = "BXAPCKSH9D3MYJTS9536RHJHCX",
- )
- ),
- successDetails = mapOf(
- "https://localhost:8088/" to SuccessDetail(
- policyVersion = 1,
- policyExpiration = Timestamp.now(),
- ),
- ),
- ),
- ),
+ viewModel = FakeBackupViewModel(
+ backupState = BackupStates.BackupFinished,
+ )
)
} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/backup/EditSecretScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/backup/EditSecretScreen.kt
index 9706ceb..799ebf2 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/backup/EditSecretScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/backup/EditSecretScreen.kt
@@ -29,17 +29,14 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import net.taler.anastasis.R
import net.taler.anastasis.models.BackupStates
-import net.taler.anastasis.models.CoreSecret
import net.taler.anastasis.models.ReducerArgs
import net.taler.anastasis.models.ReducerState
import net.taler.anastasis.ui.forms.EditSecretForm
import net.taler.anastasis.ui.reusable.pages.WizardPage
-import net.taler.anastasis.viewmodels.FakeReducerViewModel
+import net.taler.anastasis.viewmodels.FakeBackupViewModel
import net.taler.anastasis.viewmodels.ReducerViewModel
import net.taler.anastasis.viewmodels.ReducerViewModelI
-import net.taler.common.Amount
import net.taler.common.CryptoUtils
-import net.taler.common.Timestamp
@Composable
fun EditSecretScreen(
@@ -90,21 +87,8 @@ fun EditSecretScreen(
@Composable
fun EditSecretScreenPreview() {
EditSecretScreen(
- viewModel = FakeReducerViewModel(
- state = ReducerState.Backup(
- backupState = BackupStates.SecretEditing,
- secretName = "_TALERWALLET_MyPinePhone",
- coreSecret = CoreSecret(
- value = "EDJP6WK5EG50",
- mime = "text/plain",
- ),
- expiration = Timestamp.never(),
- uploadFees = listOf(
- ReducerState.Backup.UploadFee(
- fee = Amount("KUDOS", 42L, 0),
- ),
- ),
- ),
- ),
+ viewModel = FakeBackupViewModel(
+ backupState = BackupStates.SecretEditing,
+ )
)
} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/backup/ReviewPoliciesScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/backup/ReviewPoliciesScreen.kt
index edd6ddb..0055589 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/backup/ReviewPoliciesScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/backup/ReviewPoliciesScreen.kt
@@ -55,14 +55,13 @@ import net.taler.anastasis.R
import net.taler.anastasis.models.AuthMethod
import net.taler.anastasis.models.AuthenticationProviderStatus
import net.taler.anastasis.models.BackupStates
-import net.taler.anastasis.models.MethodSpec
import net.taler.anastasis.models.Policy
import net.taler.anastasis.models.ReducerState
import net.taler.anastasis.ui.dialogs.EditPolicyDialog
import net.taler.anastasis.ui.forms.EditPolicyForm
import net.taler.anastasis.ui.reusable.pages.WizardPage
import net.taler.anastasis.ui.theme.LocalSpacing
-import net.taler.anastasis.viewmodels.FakeReducerViewModel
+import net.taler.anastasis.viewmodels.FakeBackupViewModel
import net.taler.anastasis.viewmodels.ReducerViewModel
import net.taler.anastasis.viewmodels.ReducerViewModelI
@@ -263,75 +262,8 @@ fun PolicyMethodCard(
@Composable
fun ReviewPoliciesScreenPreview() {
ReviewPoliciesScreen(
- viewModel = FakeReducerViewModel(
- state = ReducerState.Backup(
- backupState = BackupStates.PoliciesReviewing,
- authenticationMethods = listOf(
- AuthMethod(
- type = AuthMethod.Type.Question,
- mimeType = "text/plain",
- challenge = "E1QPPS8A",
- instructions = "What is your favorite GNU package?",
- ),
- AuthMethod(
- type = AuthMethod.Type.Email,
- instructions = "E-mail to user@*le.com",
- challenge = "ENSPAWJ0CNW62VBGDHJJWRVFDM50",
- )
- ),
- authenticationProviders = mapOf(
- "http://localhost:8088/" to AuthenticationProviderStatus.Ok(
- httpStatus = 200,
- methods = listOf(
- MethodSpec(type = AuthMethod.Type.Question, usageFee = "EUR:0.001"),
- MethodSpec(type = AuthMethod.Type.Sms, usageFee = "EUR:0.55"),
- ),
- annualFee = "EUR:0.99",
- truthUploadFee = "EUR:3.99",
- liabilityLimit = "EUR:1",
- currency = "EUR",
- storageLimitInMegabytes = 1,
- businessName = "Anastasis 4",
- providerSalt = "CXAPCKSH9D3MYJTS9536RHJHCW",
- ),
- "http://localhost:8089/" to AuthenticationProviderStatus.Ok(
- httpStatus = 200,
- methods = listOf(
- MethodSpec(type = AuthMethod.Type.Question, usageFee = "EUR:0.001"),
- MethodSpec(type = AuthMethod.Type.Sms, usageFee = "EUR:0.55"),
- ),
- annualFee = "EUR:0.99",
- truthUploadFee = "EUR:3.99",
- liabilityLimit = "EUR:1",
- currency = "EUR",
- storageLimitInMegabytes = 1,
- businessName = "Anastasis 2",
- providerSalt = "CXAPCKSH9D3MYJTS9536RHJHCW",
- ),
- ),
- policies = listOf(
- Policy(
- methods = listOf(
- Policy.PolicyMethod(
- authenticationMethod = 0,
- provider = "http://localhost:8089/",
- ),
- Policy.PolicyMethod(
- authenticationMethod = 1,
- provider = "http://localhost:8088/",
- ),
- ),
- ),
- Policy(
- methods = listOf(
- Policy.PolicyMethod(
- authenticationMethod = 0,
- provider = "http://localhost:8089/",
- ),
- ),
- ),
- ),
- ),
- ),
+ viewModel = FakeBackupViewModel(
+ backupState = BackupStates.PoliciesReviewing,
+ )
)
} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/backup/SelectAuthMethodsScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/backup/SelectAuthMethodsScreen.kt
index 0b42534..584d853 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/backup/SelectAuthMethodsScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/backup/SelectAuthMethodsScreen.kt
@@ -20,29 +20,20 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
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.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.EditOff
-import androidx.compose.material.icons.filled.Error
-import androidx.compose.material.icons.filled.QuestionMark
-import androidx.compose.material.icons.filled.SyncDisabled
import androidx.compose.material3.Divider
import androidx.compose.material3.ElevatedCard
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@@ -62,14 +53,13 @@ import net.taler.anastasis.R
import net.taler.anastasis.models.AuthMethod
import net.taler.anastasis.models.AuthenticationProviderStatus
import net.taler.anastasis.models.BackupStates
-import net.taler.anastasis.models.MethodSpec
import net.taler.anastasis.models.ReducerState
+import net.taler.anastasis.ui.common.ManageProvidersScreen
import net.taler.anastasis.ui.dialogs.EditMethodDialog
-import net.taler.anastasis.ui.dialogs.EditProviderDialog
import net.taler.anastasis.ui.reusable.components.ActionCard
import net.taler.anastasis.ui.reusable.pages.WizardPage
import net.taler.anastasis.ui.theme.LocalSpacing
-import net.taler.anastasis.viewmodels.FakeReducerViewModel
+import net.taler.anastasis.viewmodels.FakeBackupViewModel
import net.taler.anastasis.viewmodels.ReducerViewModel
import net.taler.anastasis.viewmodels.ReducerViewModelI
import net.taler.common.CryptoUtils
@@ -131,7 +121,7 @@ fun SelectAuthMethodsScreen(
isLoading = isLoading,
) { scroll ->
if (manageProviders) {
- ManageBackupProviders(
+ ManageProvidersScreen(
nestedScrollConnection = scroll,
authProviders = authProviders,
onAddProvider = {
@@ -284,173 +274,13 @@ private fun ChallengeCard(
}
}
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun ManageBackupProviders(
- nestedScrollConnection: NestedScrollConnection,
- authProviders: Map<String, AuthenticationProviderStatus>,
- onAddProvider: (url: String) -> Unit,
- onDeleteProvider: (url: String) -> Unit,
-) {
- var showEditDialog by remember { mutableStateOf(false) }
-
- if (showEditDialog) {
- EditProviderDialog(
- onProviderEdited = onAddProvider,
- onCancel = { showEditDialog = false }
- )
- }
-
- Scaffold(
- floatingActionButton = {
- FloatingActionButton(onClick = {
- showEditDialog = true
- }) {
- Icon(
- Icons.Default.Add,
- contentDescription = stringResource(R.string.add),
- )
- }
- },
- ) { padding ->
- LazyColumn(
- modifier = Modifier
- .padding(padding)
- .nestedScroll(nestedScrollConnection)
- .fillMaxSize(),
- verticalArrangement = Arrangement.Top,
- ) {
- items(items = authProviders.keys.toList()) { url ->
- val status = authProviders[url]!!
- ProviderCard(
- modifier = Modifier
- .padding(
- end = LocalSpacing.current.medium,
- bottom = LocalSpacing.current.small,
- start = LocalSpacing.current.medium,
- )
- .fillMaxWidth(),
- url = url,
- status = status,
- onDelete = { onDeleteProvider(url) },
- )
- }
- }
- }
-}
-
-@Composable
-fun ProviderCard(
- modifier: Modifier = Modifier,
- url: String,
- status: AuthenticationProviderStatus,
- onDelete: () -> Unit,
-) {
- ElevatedCard(
- modifier = modifier,
- ) {
- Row(
- modifier = Modifier
- .padding(LocalSpacing.current.medium)
- .fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- when (status) {
- is AuthenticationProviderStatus.Ok -> Icon(
- Icons.Default.CheckCircle,
- contentDescription = stringResource(R.string.provider_status_ok),
- tint = MaterialTheme.colorScheme.primary,
- )
- is AuthenticationProviderStatus.Disabled -> Icon(
- Icons.Default.SyncDisabled,
- contentDescription = stringResource(R.string.provider_status_disabled),
- tint = MaterialTheme.colorScheme.primary,
- )
- is AuthenticationProviderStatus.Error -> Icon(
- Icons.Default.Error,
- contentDescription = stringResource(R.string.provider_status_error),
- tint = MaterialTheme.colorScheme.error,
- )
- is AuthenticationProviderStatus.NotContacted -> Icon(
- Icons.Default.QuestionMark,
- contentDescription = stringResource(R.string.provider_status_not_contacted),
- tint = MaterialTheme.colorScheme.primary,
- )
- }
- Spacer(Modifier.width(12.dp))
- Column(
- modifier = Modifier.weight(1f),
- ) {
- Text(url, style = MaterialTheme.typography.labelLarge)
- if (status is AuthenticationProviderStatus.Ok) {
- Text(status.businessName, style = MaterialTheme.typography.labelMedium)
- }
- }
- Spacer(Modifier.width(12.dp))
- IconButton(onClick = onDelete) {
- Icon(
- Icons.Default.Delete,
- contentDescription = stringResource(R.string.delete),
- )
- }
- }
- }
-}
-
-private val fakeViewModel = FakeReducerViewModel(
- state = ReducerState.Backup(
- backupState = BackupStates.AuthenticationsEditing,
- authenticationMethods = listOf(
- AuthMethod(
- type = AuthMethod.Type.Question,
- mimeType = "text/plain",
- challenge = "E1QPPS8A",
- instructions = "What is your favorite GNU package?",
- ),
- AuthMethod(
- type = AuthMethod.Type.Email,
- instructions = "E-mail to user@*le.com",
- challenge = "ENSPAWJ0CNW62VBGDHJJWRVFDM50",
- )
- ),
- authenticationProviders = mapOf(
- "http://localhost:8088/" to AuthenticationProviderStatus.Ok(
- httpStatus = 200,
- methods = listOf(
- MethodSpec(type = AuthMethod.Type.Question, usageFee = "EUR:0.001"),
- MethodSpec(type = AuthMethod.Type.Sms, usageFee = "EUR:0.55"),
- ),
- annualFee = "EUR:0.99",
- truthUploadFee = "EUR:3.99",
- liabilityLimit = "EUR:1",
- currency = "EUR",
- storageLimitInMegabytes = 1,
- businessName = "Anastasis 4",
- providerSalt = "CXAPCKSH9D3MYJTS9536RHJHCW",
- ),
- "http://localhost:8089/" to AuthenticationProviderStatus.Ok(
- httpStatus = 200,
- methods = listOf(
- MethodSpec(type = AuthMethod.Type.Question, usageFee = "EUR:0.001"),
- MethodSpec(type = AuthMethod.Type.Sms, usageFee = "EUR:0.55"),
- ),
- annualFee = "EUR:0.99",
- truthUploadFee = "EUR:3.99",
- liabilityLimit = "EUR:1",
- currency = "EUR",
- storageLimitInMegabytes = 1,
- businessName = "Anastasis 2",
- providerSalt = "CXAPCKSH9D3MYJTS9536RHJHCW",
- ),
- ),
- ),
-)
-
@Preview
@Composable
fun SelectAuthMethodsScreenPreview() {
SelectAuthMethodsScreen(
- viewModel = fakeViewModel,
+ viewModel = FakeBackupViewModel(
+ backupState = BackupStates.AuthenticationsEditing,
+ ),
)
}
@@ -458,7 +288,9 @@ fun SelectAuthMethodsScreenPreview() {
@Composable
fun ManageBackupProvidersPreview() {
SelectAuthMethodsScreen(
- viewModel = fakeViewModel,
+ viewModel = FakeBackupViewModel(
+ backupState = BackupStates.AuthenticationsEditing,
+ ),
showManageProviders = true,
)
} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/common/ManageProvidersScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/common/ManageProvidersScreen.kt
new file mode 100644
index 0000000..e0d4b7e
--- /dev/null
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/common/ManageProvidersScreen.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.common
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+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.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.CheckCircle
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.Error
+import androidx.compose.material.icons.filled.QuestionMark
+import androidx.compose.material.icons.filled.SyncDisabled
+import androidx.compose.material3.ElevatedCard
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+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.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import net.taler.anastasis.R
+import net.taler.anastasis.models.AuthenticationProviderStatus
+import net.taler.anastasis.ui.dialogs.EditProviderDialog
+import net.taler.anastasis.ui.theme.LocalSpacing
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ManageProvidersScreen(
+ nestedScrollConnection: NestedScrollConnection,
+ authProviders: Map<String, AuthenticationProviderStatus>,
+ onAddProvider: (url: String) -> Unit,
+ onDeleteProvider: (url: String) -> Unit,
+) {
+ var showEditDialog by remember { mutableStateOf(false) }
+
+ if (showEditDialog) {
+ EditProviderDialog(
+ onProviderEdited = onAddProvider,
+ onCancel = { showEditDialog = false }
+ )
+ }
+
+ Scaffold(
+ floatingActionButton = {
+ FloatingActionButton(onClick = {
+ showEditDialog = true
+ }) {
+ Icon(
+ Icons.Default.Add,
+ contentDescription = stringResource(R.string.add),
+ )
+ }
+ },
+ ) { padding ->
+ LazyColumn(
+ modifier = Modifier
+ .padding(padding)
+ .nestedScroll(nestedScrollConnection)
+ .fillMaxSize(),
+ verticalArrangement = Arrangement.Top,
+ ) {
+ items(items = authProviders.keys.toList()) { url ->
+ val status = authProviders[url]!!
+ ProviderCard(
+ modifier = Modifier
+ .padding(
+ end = LocalSpacing.current.medium,
+ bottom = LocalSpacing.current.small,
+ start = LocalSpacing.current.medium,
+ )
+ .fillMaxWidth(),
+ url = url,
+ status = status,
+ onDelete = { onDeleteProvider(url) },
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun ProviderCard(
+ modifier: Modifier = Modifier,
+ url: String,
+ status: AuthenticationProviderStatus,
+ onDelete: () -> Unit,
+) {
+ ElevatedCard(
+ modifier = modifier,
+ ) {
+ Row(
+ modifier = Modifier
+ .padding(LocalSpacing.current.medium)
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ when (status) {
+ is AuthenticationProviderStatus.Ok -> Icon(
+ Icons.Default.CheckCircle,
+ contentDescription = stringResource(R.string.provider_status_ok),
+ tint = MaterialTheme.colorScheme.primary,
+ )
+ is AuthenticationProviderStatus.Disabled -> Icon(
+ Icons.Default.SyncDisabled,
+ contentDescription = stringResource(R.string.provider_status_disabled),
+ tint = MaterialTheme.colorScheme.primary,
+ )
+ is AuthenticationProviderStatus.Error -> Icon(
+ Icons.Default.Error,
+ contentDescription = stringResource(R.string.provider_status_error),
+ tint = MaterialTheme.colorScheme.error,
+ )
+ is AuthenticationProviderStatus.NotContacted -> Icon(
+ Icons.Default.QuestionMark,
+ contentDescription = stringResource(R.string.provider_status_not_contacted),
+ tint = MaterialTheme.colorScheme.primary,
+ )
+ }
+ Spacer(Modifier.width(12.dp))
+ Column(
+ modifier = Modifier.weight(1f),
+ ) {
+ Text(url, style = MaterialTheme.typography.labelLarge)
+ if (status is AuthenticationProviderStatus.Ok) {
+ Text(status.businessName, style = MaterialTheme.typography.labelMedium)
+ }
+ }
+ Spacer(Modifier.width(12.dp))
+ IconButton(onClick = onDelete) {
+ Icon(
+ Icons.Default.Delete,
+ contentDescription = stringResource(R.string.delete),
+ )
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectContinentScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectContinentScreen.kt
index 10a8978..c803ffe 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectContinentScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectContinentScreen.kt
@@ -32,12 +32,11 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import net.taler.anastasis.R
import net.taler.anastasis.models.BackupStates
-import net.taler.anastasis.models.ContinentInfo
import net.taler.anastasis.models.ReducerState
import net.taler.anastasis.ui.reusable.components.Picker
import net.taler.anastasis.ui.reusable.pages.WizardPage
import net.taler.anastasis.ui.theme.LocalSpacing
-import net.taler.anastasis.viewmodels.FakeReducerViewModel
+import net.taler.anastasis.viewmodels.FakeBackupViewModel
import net.taler.anastasis.viewmodels.ReducerViewModel
import net.taler.anastasis.viewmodels.ReducerViewModelI
@@ -99,15 +98,8 @@ fun SelectContinentScreen(
@Composable
fun SelectContinentScreenPreview() {
SelectContinentScreen(
- viewModel = FakeReducerViewModel(
- state = ReducerState.Backup(
- backupState = BackupStates.ContinentSelecting,
- selectedContinent = "Europe",
- continents = listOf(
- ContinentInfo(name = "Europe"),
- ContinentInfo(name = "North America"),
- )
- )
+ viewModel = FakeBackupViewModel(
+ backupState = BackupStates.ContinentSelecting,
)
)
} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectCountryScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectCountryScreen.kt
index f137e5e..ba8136e 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectCountryScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectCountryScreen.kt
@@ -32,12 +32,11 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import net.taler.anastasis.R
import net.taler.anastasis.models.BackupStates
-import net.taler.anastasis.models.CountryInfo
import net.taler.anastasis.models.ReducerState
import net.taler.anastasis.ui.reusable.components.Picker
import net.taler.anastasis.ui.reusable.pages.WizardPage
import net.taler.anastasis.ui.theme.LocalSpacing
-import net.taler.anastasis.viewmodels.FakeReducerViewModel
+import net.taler.anastasis.viewmodels.FakeBackupViewModel
import net.taler.anastasis.viewmodels.ReducerViewModel
import net.taler.anastasis.viewmodels.ReducerViewModelI
@@ -99,23 +98,8 @@ fun SelectCountryScreen(
@Composable
fun SelectCountryScreenPreview() {
SelectCountryScreen(
- viewModel = FakeReducerViewModel(
- state = ReducerState.Backup(
- backupState = BackupStates.ContinentSelecting,
- selectedCountry = "ch",
- countries = listOf(
- CountryInfo(
- code = "ch",
- name = "Switzerland",
- continent = "Europe",
- ),
- CountryInfo(
- code = "de",
- name = "Germany",
- continent = "Europe",
- )
- )
- )
+ viewModel = FakeBackupViewModel(
+ backupState = BackupStates.CountrySelecting,
)
)
} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectUserAttributesScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectUserAttributesScreen.kt
index 6c09ae5..fe90eff 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectUserAttributesScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectUserAttributesScreen.kt
@@ -51,7 +51,7 @@ import net.taler.anastasis.shared.Utils
import net.taler.anastasis.ui.reusable.components.DatePickerField
import net.taler.anastasis.ui.reusable.pages.WizardPage
import net.taler.anastasis.ui.theme.LocalSpacing
-import net.taler.anastasis.viewmodels.FakeReducerViewModel
+import net.taler.anastasis.viewmodels.FakeBackupViewModel
import net.taler.anastasis.viewmodels.ReducerViewModel
import net.taler.anastasis.viewmodels.ReducerViewModelI
@@ -166,30 +166,8 @@ private fun fieldStatus(
@Composable
fun SelectUserAttributesScreenPreview() {
SelectUserAttributesScreen(
- viewModel = FakeReducerViewModel(
- state = ReducerState.Backup(
- backupState = BackupStates.UserAttributesCollecting,
- identityAttributes = mapOf(
- "full_name" to "Max Musterman",
- "birthdate" to "2000-01-01",
- ),
- requiredAttributes = listOf(
- UserAttributeSpec(
- type = "string",
- name = "full_name",
- label = "Full name",
- widget = "anastasis_gtk_ia_full_name",
- uuid = "9e8f463f-575f-42cb-85f3-759559997331",
- ),
- UserAttributeSpec(
- type = "date",
- name = "birthdate",
- label = "Birthdate",
- widget = "anastasis_gtk_ia_birthdate",
- uuid = "83d655c7-bdb6-484d-904e-80c1058c8854",
- ),
- ),
- ),
- ),
+ viewModel = FakeBackupViewModel(
+ backupState = BackupStates.UserAttributesCollecting,
+ )
)
} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/recovery/RecoveryFinishedScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/recovery/RecoveryFinishedScreen.kt
index 9cc58e5..aa43200 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/recovery/RecoveryFinishedScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/recovery/RecoveryFinishedScreen.kt
@@ -41,13 +41,11 @@ 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.CoreSecret
-import net.taler.anastasis.models.RecoveryInternalData
import net.taler.anastasis.models.RecoveryStates
import net.taler.anastasis.models.ReducerState
import net.taler.anastasis.ui.reusable.pages.WizardPage
import net.taler.anastasis.ui.theme.LocalSpacing
-import net.taler.anastasis.viewmodels.FakeReducerViewModel
+import net.taler.anastasis.viewmodels.FakeRecoveryViewModel
import net.taler.anastasis.viewmodels.ReducerViewModel
import net.taler.anastasis.viewmodels.ReducerViewModelI
import net.taler.common.CryptoUtils
@@ -124,19 +122,8 @@ fun RecoveryFinishedScreen(
@Composable
fun RecoveryFinishedScreenPreview() {
RecoveryFinishedScreen(
- viewModel = FakeReducerViewModel(
- state = ReducerState.Recovery(
- recoveryState = RecoveryStates.RecoveryFinished,
- recoveryDocument = RecoveryInternalData(
- secretName = "Secret",
- providerUrl = "http://localhost:8089",
- version = 1,
- ),
- coreSecret = CoreSecret(
- mime = "text/plain",
- value = CryptoUtils.encodeCrock("Taler".toByteArray(Charsets.UTF_8)),
- )
- ),
- ),
+ viewModel = FakeRecoveryViewModel(
+ recoveryState = RecoveryStates.RecoveryFinished,
+ )
)
} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SelectChallengeScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SelectChallengeScreen.kt
index df7afdf..742ad0b 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SelectChallengeScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SelectChallengeScreen.kt
@@ -45,7 +45,6 @@ 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.ChallengeInfo
import net.taler.anastasis.models.RecoveryInformation
@@ -53,7 +52,7 @@ import net.taler.anastasis.models.RecoveryStates
import net.taler.anastasis.models.ReducerState
import net.taler.anastasis.ui.reusable.pages.WizardPage
import net.taler.anastasis.ui.theme.LocalSpacing
-import net.taler.anastasis.viewmodels.FakeReducerViewModel
+import net.taler.anastasis.viewmodels.FakeRecoveryViewModel
import net.taler.anastasis.viewmodels.ReducerViewModel
import net.taler.anastasis.viewmodels.ReducerViewModelI
@@ -225,34 +224,8 @@ fun FeedbackSolvedButton() {
@Composable
fun SelectChallengeScreenPreview() {
SelectChallengeScreen(
- viewModel = FakeReducerViewModel(
- state = ReducerState.Recovery(
- recoveryState = RecoveryStates.ChallengeSelecting,
- recoveryInformation = RecoveryInformation(
- challenges = listOf(
- ChallengeInfo(
- instructions = "What is your favorite GNU package?",
- type = AuthMethod.Type.Question,
- uuid = "RNB84NQZPCM3MZWF9D5FFMSYYN07J2NAT5N8Q0DBHHT7R3GJ4AA0",
- ),
- ChallengeInfo(
- instructions = "E-mail to user@*le.com",
- type = AuthMethod.Type.Email,
- uuid = "ZA6T35B8XAR0DNKS5H100GK8PDPTA7Q8ST2FPQSYAZ4QRAA9XKK0",
- ),
- ),
- policies = listOf(
- listOf(
- RecoveryInformation.Policy(uuid = "RNB84NQZPCM3MZWF9D5FFMSYYN07J2NAT5N8Q0DBHHT7R3GJ4AA0"),
- RecoveryInformation.Policy(uuid = "ZA6T35B8XAR0DNKS5H100GK8PDPTA7Q8ST2FPQSYAZ4QRAA9XKK0")
- ),
- ),
- ),
- challengeFeedback = mapOf(
- "RNB84NQZPCM3MZWF9D5FFMSYYN07J2NAT5N8Q0DBHHT7R3GJ4AA0" to ChallengeFeedback.IncorrectAnswer,
- "ZA6T35B8XAR0DNKS5H100GK8PDPTA7Q8ST2FPQSYAZ4QRAA9XKK0" to ChallengeFeedback.Solved,
- ),
- ),
- ),
+ viewModel = FakeRecoveryViewModel(
+ recoveryState = RecoveryStates.ChallengeSelecting,
+ )
)
} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SelectSecretScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SelectSecretScreen.kt
index 71e1d5e..872886a 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SelectSecretScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SelectSecretScreen.kt
@@ -24,8 +24,13 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material.icons.filled.EditOff
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
@@ -37,6 +42,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@@ -45,16 +52,17 @@ import net.taler.anastasis.R
import net.taler.anastasis.models.AggregatedPolicyMetaInfo
import net.taler.anastasis.models.RecoveryStates
import net.taler.anastasis.models.ReducerState
-import net.taler.anastasis.models.SelectedVersionInfo
+import net.taler.anastasis.ui.common.ManageProvidersScreen
import net.taler.anastasis.ui.reusable.pages.WizardPage
import net.taler.anastasis.ui.theme.LocalSpacing
-import net.taler.anastasis.viewmodels.FakeReducerViewModel
+import net.taler.anastasis.viewmodels.FakeRecoveryViewModel
import net.taler.anastasis.viewmodels.ReducerViewModel
import net.taler.anastasis.viewmodels.ReducerViewModelI
@Composable
fun SelectSecretScreen(
viewModel: ReducerViewModelI = hiltViewModel<ReducerViewModel>(),
+ showManageProviders: Boolean = false,
) {
val state by viewModel.reducerState.collectAsState()
val reducerState = state as? ReducerState.Recovery
@@ -63,12 +71,17 @@ fun SelectSecretScreen(
val tasks by viewModel.tasks.collectAsState()
val isLoading = tasks.isBackgroundLoading
+ val authProviders = reducerState.authenticationProviders ?: emptyMap()
val versions = reducerState.discoveryState?.aggregatedPolicies ?: emptyList()
var selectedIndex by remember { mutableStateOf<Int?>(null) }
+ var manageProviders by remember { mutableStateOf(showManageProviders) }
+
WizardPage(
- title = stringResource(R.string.select_secret_title),
+ title = if (manageProviders)
+ stringResource(R.string.recovery_providers)
+ else stringResource(R.string.select_auth_methods_title),
enableNext = selectedIndex != null,
onBackClicked = { viewModel.goHome() },
onPrevClicked = { viewModel.goBack() },
@@ -77,22 +90,70 @@ fun SelectSecretScreen(
viewModel.reducerManager?.selectVersion(versions[it])
}
},
+ actions = {
+ IconButton(onClick = {
+ manageProviders = !manageProviders
+ }) {
+ if (manageProviders) {
+ Icon(
+ Icons.Default.EditOff,
+ contentDescription = stringResource(R.string.select_auth_methods_title)
+ )
+ } else {
+ Icon(
+ Icons.Default.Edit,
+ contentDescription = stringResource(R.string.manage_backup_providers),
+ )
+ }
+
+ }
+ },
isLoading = isLoading,
+ ) { scroll ->
+ if (manageProviders) {
+ ManageProvidersScreen(
+ nestedScrollConnection = scroll,
+ authProviders = authProviders,
+ onAddProvider = {
+ viewModel.reducerManager?.addProvider(it)
+ },
+ onDeleteProvider = {
+ viewModel.reducerManager?.deleteProvider(it)
+ },
+ )
+ } else {
+ SecretList(
+ scrollConnection = scroll,
+ versions = versions,
+ selectedIndex = selectedIndex,
+ onSelectIndex = { selectedIndex = it }
+ )
+ }
+ }
+}
+
+@Composable
+fun SecretList(
+ scrollConnection: NestedScrollConnection,
+ versions: List<AggregatedPolicyMetaInfo>,
+ selectedIndex: Int?,
+ onSelectIndex: (Int) -> Unit,
+) {
+ LazyColumn(
+ modifier = Modifier.nestedScroll(scrollConnection),
) {
- LazyColumn {
- items(count = versions.size) { index ->
- SecretCard(
- modifier = Modifier
- .padding(
- start = LocalSpacing.current.medium,
- end = LocalSpacing.current.medium,
- bottom = LocalSpacing.current.small,
- )
- .fillMaxWidth(),
- policy = versions[index],
- isSelected = selectedIndex == index,
- ) { selectedIndex = index }
- }
+ items(count = versions.size) { index ->
+ SecretCard(
+ modifier = Modifier
+ .padding(
+ start = LocalSpacing.current.medium,
+ end = LocalSpacing.current.medium,
+ bottom = LocalSpacing.current.small,
+ )
+ .fillMaxWidth(),
+ policy = versions[index],
+ isSelected = selectedIndex == index,
+ ) { onSelectIndex(index) }
}
}
}
@@ -146,30 +207,8 @@ fun SecretCard(
@Composable
fun SelectSecretScreenPreview() {
SelectSecretScreen(
- viewModel = FakeReducerViewModel(
- state = ReducerState.Recovery(
- recoveryState = RecoveryStates.SecretSelecting,
- discoveryState = ReducerState.Recovery.DiscoveryState(
- state = "finished",
- aggregatedPolicies = listOf(
- AggregatedPolicyMetaInfo(
- attributeMask = 0,
- policyHash = "000000000000000000000000000000000000000000000000000B28GR6691Y51HR2SAFJZFF0DCMRDZD1YQMS03A55P9NCWHQGEKW8",
- providers = listOf(
- SelectedVersionInfo.Provider(
- url = "https://v1.anastasis.taler.net/",
- version = 1,
- ),
- SelectedVersionInfo.Provider(
- url = "https://v1.anastasis.codeblau.de/",
- version = 1,
- ),
- ),
- secretName = "Secret",
- ),
- ),
- ),
- ),
- ),
+ viewModel = FakeRecoveryViewModel(
+ recoveryState = RecoveryStates.SecretSelecting,
+ )
)
} \ No newline at end of file
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
index d644385..cc0b4c6 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SolveChallengeScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/recovery/SolveChallengeScreen.kt
@@ -37,11 +37,13 @@ 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.RecoveryStates
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.FakeRecoveryViewModel
import net.taler.anastasis.viewmodels.ReducerViewModel
import net.taler.anastasis.viewmodels.ReducerViewModelI
@@ -145,5 +147,9 @@ fun SolveChallengeScreen(
@Preview
@Composable
fun SolveChallengeScreenPreview() {
- SolveChallengeScreen()
+ SolveChallengeScreen(
+ viewModel = FakeRecoveryViewModel(
+ recoveryState = RecoveryStates.ChallengeSolving,
+ )
+ )
} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/viewmodels/FakeReducerViewModel.kt b/anastasis/src/main/java/net/taler/anastasis/viewmodels/FakeReducerViewModel.kt
new file mode 100644
index 0000000..3f482e4
--- /dev/null
+++ b/anastasis/src/main/java/net/taler/anastasis/viewmodels/FakeReducerViewModel.kt
@@ -0,0 +1,274 @@
+/*
+ * 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.viewmodels
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import net.taler.anastasis.backend.TalerErrorInfo
+import net.taler.anastasis.backend.Tasks
+import net.taler.anastasis.models.AggregatedPolicyMetaInfo
+import net.taler.anastasis.models.AuthMethod
+import net.taler.anastasis.models.AuthenticationProviderStatus
+import net.taler.anastasis.models.BackupStates
+import net.taler.anastasis.models.ChallengeFeedback
+import net.taler.anastasis.models.ChallengeInfo
+import net.taler.anastasis.models.ContinentInfo
+import net.taler.anastasis.models.CoreSecret
+import net.taler.anastasis.models.CountryInfo
+import net.taler.anastasis.models.MethodSpec
+import net.taler.anastasis.models.Policy
+import net.taler.anastasis.models.RecoveryInformation
+import net.taler.anastasis.models.RecoveryInternalData
+import net.taler.anastasis.models.RecoveryStates
+import net.taler.anastasis.models.ReducerState
+import net.taler.anastasis.models.SelectedVersionInfo
+import net.taler.anastasis.models.SuccessDetail
+import net.taler.anastasis.models.UserAttributeSpec
+import net.taler.common.Amount
+import net.taler.common.Timestamp
+
+open class FakeReducerViewModel(
+ state: ReducerState,
+ error: TalerErrorInfo? = null,
+): ReducerViewModelI {
+ override val reducerManager = null
+ private val _reducerState = MutableStateFlow<ReducerState?>(state)
+ override val reducerState: StateFlow<ReducerState?> = _reducerState.asStateFlow()
+ private val _reducerError = MutableStateFlow(error)
+ override val reducerError: StateFlow<TalerErrorInfo?> = _reducerError.asStateFlow()
+ private val _loading = MutableStateFlow(Tasks())
+ override val tasks = _loading.asStateFlow()
+
+ override fun goBack(): Boolean = false
+
+ override fun goHome() {
+ _reducerState.value = null
+ }
+
+ override fun cleanError() {
+ _reducerError.value = null
+ }
+}
+
+internal val continents = listOf(
+ ContinentInfo(name = "Europe"),
+ ContinentInfo(name = "North America"),
+)
+
+internal val countries = listOf(
+ CountryInfo(
+ code = "ch",
+ name = "Switzerland",
+ continent = "Europe",
+ ),
+ CountryInfo(
+ code = "de",
+ name = "Germany",
+ continent = "Europe",
+ )
+)
+
+internal val identityAttributes = mapOf(
+ "full_name" to "Max Musterman",
+ "birthdate" to "2000-01-01",
+)
+
+internal val authenticationProviders = mapOf(
+ "http://localhost:8088/" to AuthenticationProviderStatus.Ok(
+ httpStatus = 200,
+ methods = listOf(
+ MethodSpec(type = AuthMethod.Type.Question, usageFee = "EUR:0.001"),
+ MethodSpec(type = AuthMethod.Type.Sms, usageFee = "EUR:0.55"),
+ ),
+ annualFee = "EUR:0.99",
+ truthUploadFee = "EUR:3.99",
+ liabilityLimit = "EUR:1",
+ currency = "EUR",
+ storageLimitInMegabytes = 1,
+ businessName = "Anastasis 4",
+ providerSalt = "CXAPCKSH9D3MYJTS9536RHJHCW",
+ ),
+ "http://localhost:8089/" to AuthenticationProviderStatus.Ok(
+ httpStatus = 200,
+ methods = listOf(
+ MethodSpec(type = AuthMethod.Type.Question, usageFee = "EUR:0.001"),
+ MethodSpec(type = AuthMethod.Type.Sms, usageFee = "EUR:0.55"),
+ ),
+ annualFee = "EUR:0.99",
+ truthUploadFee = "EUR:3.99",
+ liabilityLimit = "EUR:1",
+ currency = "EUR",
+ storageLimitInMegabytes = 1,
+ businessName = "Anastasis 2",
+ providerSalt = "CXAPCKSH9D3MYJTS9536RHJHCW",
+ )
+)
+
+internal val requiredAttributes = listOf(
+ UserAttributeSpec(
+ type = "string",
+ name = "full_name",
+ label = "Full name",
+ widget = "anastasis_gtk_ia_full_name",
+ uuid = "9e8f463f-575f-42cb-85f3-759559997331",
+ ),
+ UserAttributeSpec(
+ type = "date",
+ name = "birthdate",
+ label = "Birthdate",
+ widget = "anastasis_gtk_ia_birthdate",
+ uuid = "83d655c7-bdb6-484d-904e-80c1058c8854",
+ ),
+)
+
+internal const val selectedContinent = "Europe"
+internal const val selectedCountry = "ch"
+
+internal val coreSecret = CoreSecret(
+ value = "EDJP6WK5EG50",
+ mime = "text/plain",
+)
+
+class FakeBackupViewModel(
+ backupState: BackupStates,
+): FakeReducerViewModel(
+ state = ReducerState.Backup(
+ backupState = backupState,
+ continents = continents,
+ countries = countries,
+ identityAttributes = identityAttributes,
+ authenticationProviders = authenticationProviders,
+ authenticationMethods = listOf(
+ AuthMethod(
+ type = AuthMethod.Type.Question,
+ mimeType = "text/plain",
+ challenge = "E1QPPS8A",
+ instructions = "What is your favorite GNU package?",
+ ),
+ AuthMethod(
+ type = AuthMethod.Type.Email,
+ instructions = "E-mail to user@*le.com",
+ challenge = "ENSPAWJ0CNW62VBGDHJJWRVFDM50",
+ )
+ ),
+ requiredAttributes = requiredAttributes,
+ selectedContinent = selectedContinent,
+ selectedCountry = selectedCountry,
+ secretName = "_TALERWALLET_MyPinePhone",
+ policies = listOf(
+ Policy(
+ methods = listOf(
+ Policy.PolicyMethod(
+ authenticationMethod = 0,
+ provider = "http://localhost:8089/",
+ ),
+ Policy.PolicyMethod(
+ authenticationMethod = 1,
+ provider = "http://localhost:8088/",
+ ),
+ ),
+ ),
+ Policy(
+ methods = listOf(
+ Policy.PolicyMethod(
+ authenticationMethod = 0,
+ provider = "http://localhost:8089/",
+ ),
+ ),
+ ),
+ ),
+ successDetails = mapOf(
+ "http://localhost:8088/" to SuccessDetail(
+ policyVersion = 1,
+ policyExpiration = Timestamp.now(),
+ ),
+ ),
+ coreSecret = coreSecret,
+ uploadFees = listOf(
+ ReducerState.Backup.UploadFee(
+ fee = Amount("KUDOS", 42L, 0),
+ ),
+ ),
+ ),
+)
+
+class FakeRecoveryViewModel(
+ recoveryState: RecoveryStates,
+): FakeReducerViewModel(
+ state = ReducerState.Recovery(
+ recoveryState = recoveryState,
+ continents = continents,
+ countries = countries,
+ identityAttributes = identityAttributes,
+ selectedContinent = selectedContinent,
+ selectedCountry = selectedCountry,
+ requiredAttributes = requiredAttributes,
+ recoveryInformation = RecoveryInformation(
+ challenges = listOf(
+ ChallengeInfo(
+ instructions = "What is your favorite GNU package?",
+ type = AuthMethod.Type.Question,
+ uuid = "RNB84NQZPCM3MZWF9D5FFMSYYN07J2NAT5N8Q0DBHHT7R3GJ4AA0",
+ ),
+ ChallengeInfo(
+ instructions = "E-mail to user@*le.com",
+ type = AuthMethod.Type.Email,
+ uuid = "ZA6T35B8XAR0DNKS5H100GK8PDPTA7Q8ST2FPQSYAZ4QRAA9XKK0",
+ ),
+ ),
+ policies = listOf(
+ listOf(
+ RecoveryInformation.Policy(uuid = "RNB84NQZPCM3MZWF9D5FFMSYYN07J2NAT5N8Q0DBHHT7R3GJ4AA0"),
+ RecoveryInformation.Policy(uuid = "ZA6T35B8XAR0DNKS5H100GK8PDPTA7Q8ST2FPQSYAZ4QRAA9XKK0")
+ ),
+ ),
+ ),
+ recoveryDocument = RecoveryInternalData(
+ secretName = "Secret",
+ providerUrl = "http://localhost:8089",
+ version = 1,
+ ),
+ selectedChallengeUuid = "RNB84NQZPCM3MZWF9D5FFMSYYN07J2NAT5N8Q0DBHHT7R3GJ4AA0",
+ challengeFeedback = mapOf(
+ "RNB84NQZPCM3MZWF9D5FFMSYYN07J2NAT5N8Q0DBHHT7R3GJ4AA0" to ChallengeFeedback.IncorrectAnswer,
+ "ZA6T35B8XAR0DNKS5H100GK8PDPTA7Q8ST2FPQSYAZ4QRAA9XKK0" to ChallengeFeedback.Solved,
+ ),
+ coreSecret = coreSecret,
+ authenticationProviders = authenticationProviders,
+ discoveryState = ReducerState.Recovery.DiscoveryState(
+ state = "finished",
+ aggregatedPolicies = listOf(
+ AggregatedPolicyMetaInfo(
+ attributeMask = 0,
+ policyHash = "000000000000000000000000000000000000000000000000000B28GR6691Y51HR2SAFJZFF0DCMRDZD1YQMS03A55P9NCWHQGEKW8",
+ providers = listOf(
+ SelectedVersionInfo.Provider(
+ url = "https://v1.anastasis.taler.net/",
+ version = 1,
+ ),
+ SelectedVersionInfo.Provider(
+ url = "https://v1.anastasis.codeblau.de/",
+ version = 1,
+ ),
+ ),
+ secretName = "Secret",
+ ),
+ ),
+ ),
+ )
+)
diff --git a/anastasis/src/main/java/net/taler/anastasis/viewmodels/ReducerViewModel.kt b/anastasis/src/main/java/net/taler/anastasis/viewmodels/ReducerViewModel.kt
index faba94a..a1fb08c 100644
--- a/anastasis/src/main/java/net/taler/anastasis/viewmodels/ReducerViewModel.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/viewmodels/ReducerViewModel.kt
@@ -137,26 +137,3 @@ class ReducerViewModel @Inject constructor(): ViewModel(), ReducerViewModelI {
_reducerError.value = null
}
}
-
-class FakeReducerViewModel(
- state: ReducerState,
- error: TalerErrorInfo? = null,
-): ReducerViewModelI {
- override val reducerManager = null
- private val _reducerState = MutableStateFlow<ReducerState?>(state)
- override val reducerState: StateFlow<ReducerState?> = _reducerState.asStateFlow()
- private val _reducerError = MutableStateFlow(error)
- override val reducerError: StateFlow<TalerErrorInfo?> = _reducerError.asStateFlow()
- private val _loading = MutableStateFlow(Tasks())
- override val tasks = _loading.asStateFlow()
-
- override fun goBack(): Boolean = false
-
- override fun goHome() {
- _reducerState.value = null
- }
-
- override fun cleanError() {
- _reducerError.value = null
- }
-} \ No newline at end of file
diff --git a/anastasis/src/main/res/values/strings.xml b/anastasis/src/main/res/values/strings.xml
index 426ee26..d53db88 100644
--- a/anastasis/src/main/res/values/strings.xml
+++ b/anastasis/src/main/res/values/strings.xml
@@ -80,6 +80,7 @@
<string name="select_challenge_title">Select challenge</string>
<string name="solve_challenge_title">Solve challenge</string>
<string name="recovery_finished_title">Your secret was recovered</string>
+ <string name="recovery_providers">Manage recovery providers</string>
<string name="challenge_solve">Solve</string>
<string name="challenge_feedback_solved">Solved</string>
<string name="challenge_feedback_incorrect_answer">Incorrect answer</string>