summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2023-07-27 23:19:20 -0600
committerIván Ávalos <avalos@disroot.org>2023-11-11 13:20:09 -0600
commit22da595621eb544c35629324a99973df1ac3b3de (patch)
tree7cdda768ceef8ca6cd8fc75f6ae7082b4481142c
parentb072712019bfe3a423bac4d75f032306b2814e07 (diff)
downloadtaler-android-22da595621eb544c35629324a99973df1ac3b3de.tar.gz
taler-android-22da595621eb544c35629324a99973df1ac3b3de.tar.bz2
taler-android-22da595621eb544c35629324a99973df1ac3b3de.zip
qtart integration + first functional screens
Signed-off-by: Iván Ávalos <avalos@disroot.org>
-rw-r--r--.idea/inspectionProfiles/Project_Default.xml37
-rw-r--r--anastasis/build.gradle.kts8
-rw-r--r--anastasis/src/main/AndroidManifest.xml1
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/AnastasisApp.kt7
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/MainActivity.kt88
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/Routes.kt11
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/backend/AnastasisReducerApi.kt86
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/backend/BackendManager.kt81
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/backend/RequestManager.kt45
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/backend/WalletResponse.kt134
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/models/ReducerState.kt283
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/reducers/ReducerManager.kt84
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupContinentScreen.kt76
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupCountryScreen.kt77
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/common/SelectContinentScreen.kt90
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/common/SelectCountryScreen.kt92
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/common/SelectUserAttributesScreen.kt (renamed from anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupUserAttributesScreen.kt)66
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/ui/home/HomeScreen.kt23
-rw-r--r--anastasis/src/main/java/net/taler/anastasis/viewmodels/ReducerViewModel.kt81
-rw-r--r--anastasis/src/main/res/values/strings.xml9
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/ApiResponse.kt (renamed from wallet/src/main/java/net/taler/wallet/backend/ApiResponse.kt)2
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/TalerErrorCode.kt (renamed from wallet/src/main/java/net/taler/wallet/backend/TalerErrorCode.kt)35
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainViewModel.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/Utils.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/BackendManager.kt3
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/RequestManager.kt1
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt3
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt1
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/IncomingComposable.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/ActionButtonComposable.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionTipFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt2
45 files changed, 990 insertions, 472 deletions
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index bf1d698..fef7018 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -1,6 +1,43 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
+ <inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
+ <option name="composableFile" value="true" />
+ <option name="previewFile" value="true" />
+ </inspection_tool>
+ <inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
+ <option name="composableFile" value="true" />
+ <option name="previewFile" value="true" />
+ </inspection_tool>
+ <inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
+ <option name="composableFile" value="true" />
+ <option name="previewFile" value="true" />
+ </inspection_tool>
+ <inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
+ <option name="composableFile" value="true" />
+ <option name="previewFile" value="true" />
+ </inspection_tool>
+ <inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
+ <option name="composableFile" value="true" />
+ <option name="previewFile" value="true" />
+ </inspection_tool>
+ <inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
+ <option name="composableFile" value="true" />
+ <option name="previewFile" value="true" />
+ </inspection_tool>
+ <inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
+ <option name="composableFile" value="true" />
+ <option name="previewFile" value="true" />
+ </inspection_tool>
+ <inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
+ <option name="composableFile" value="true" />
+ <option name="previewFile" value="true" />
+ </inspection_tool>
+ <inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
+ <option name="composableFile" value="true" />
+ <option name="previewFile" value="true" />
+ </inspection_tool>
+ <inspection_tool class="StaticImport" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryVariable" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
</profile>
</component> \ No newline at end of file
diff --git a/anastasis/build.gradle.kts b/anastasis/build.gradle.kts
index 85fb2ec..7aceb34 100644
--- a/anastasis/build.gradle.kts
+++ b/anastasis/build.gradle.kts
@@ -6,7 +6,7 @@ plugins {
id("com.google.dagger.hilt.android")
}
-val qtartVersion = "0.9.3-dev.13"
+val qtartVersion = "0.9.3-dev.14"
@Suppress("UnstableApiUsage")
android {
@@ -57,6 +57,7 @@ android {
dependencies {
implementation(project(":taler-kotlin-android"))
implementation("net.taler:qtart:$qtartVersion@aar")
+ implementation("net.java.dev.jna:jna:5.13.0@aar")
implementation("androidx.core:core-ktx:1.10.1")
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
@@ -71,7 +72,12 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
implementation("com.google.dagger:hilt-android:2.44")
+ implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
kapt("com.google.dagger:hilt-android-compiler:2.44")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
+}
+
+kapt {
+ correctErrorTypes = true
} \ No newline at end of file
diff --git a/anastasis/src/main/AndroidManifest.xml b/anastasis/src/main/AndroidManifest.xml
index 8c22a46..ce3333f 100644
--- a/anastasis/src/main/AndroidManifest.xml
+++ b/anastasis/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
<application
+ android:name=".AnastasisApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
diff --git a/anastasis/src/main/java/net/taler/anastasis/AnastasisApp.kt b/anastasis/src/main/java/net/taler/anastasis/AnastasisApp.kt
new file mode 100644
index 0000000..8fc38fa
--- /dev/null
+++ b/anastasis/src/main/java/net/taler/anastasis/AnastasisApp.kt
@@ -0,0 +1,7 @@
+package net.taler.anastasis
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class AnastasisApp: Application() \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/MainActivity.kt b/anastasis/src/main/java/net/taler/anastasis/MainActivity.kt
index 24f604a..4868357 100644
--- a/anastasis/src/main/java/net/taler/anastasis/MainActivity.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/MainActivity.kt
@@ -8,19 +8,19 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
-import net.taler.anastasis.models.ContinentInfo
-import net.taler.anastasis.models.CountryInfo
-import net.taler.anastasis.models.UserAttributeSpec
-import net.taler.anastasis.ui.backup.BackupContinentScreen
-import net.taler.anastasis.ui.backup.BackupCountryScreen
-import net.taler.anastasis.ui.backup.BackupUserAttributesScreen
+import androidx.hilt.navigation.compose.hiltViewModel
+import dagger.hilt.android.AndroidEntryPoint
+import net.taler.anastasis.ui.common.SelectContinentScreen
+import net.taler.anastasis.ui.common.SelectCountryScreen
+import net.taler.anastasis.ui.common.SelectUserAttributesScreen
import net.taler.anastasis.ui.home.HomeScreen
import net.taler.anastasis.ui.theme.AnastasisTheme
+import net.taler.anastasis.viewmodels.ReducerViewModel
+@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -38,66 +38,24 @@ class MainActivity : ComponentActivity() {
}
@Composable
-fun MainNavHost() {
- val navController = rememberNavController()
- NavHost(
- navController = navController,
- startDestination = Routes.Home.route,
- ) {
- composable(Routes.Home.route) {
- HomeScreen(navController = navController)
+fun MainNavHost(
+ viewModel: ReducerViewModel = hiltViewModel(),
+) {
+ val navRoute by viewModel.navRoute.collectAsState()
+ when (navRoute) {
+ Routes.Home.route -> {
+ HomeScreen()
}
- composable(Routes.BackupContinent.route) {
- BackupContinentScreen(
- navController = navController,
- continents = listOf(
- ContinentInfo("Europe"),
- ContinentInfo("India"),
- ContinentInfo("Asia"),
- ContinentInfo("North America")
- ),
- onSelectContinent = {},
- )
+ Routes.SelectContinent.route -> {
+ SelectContinentScreen()
}
- composable(Routes.BackupCountry.route) {
- BackupCountryScreen(
- navController = navController,
- countries = listOf(
- CountryInfo("ch", "Switzerland", "Europe"),
- CountryInfo("de", "Germany", "Europe"),
- ),
- onSelectCountry = {},
- )
+ Routes.SelectCountry.route -> {
+ SelectCountryScreen()
}
- composable(Routes.BackupUserAttributes.route) {
- BackupUserAttributesScreen(
- navController = navController,
- userAttributes = listOf(
- UserAttributeSpec(
- type = "string",
- name = "full_name",
- label = "Full name",
- widget = "anastasis_gtk_ia_full_name",
- uuid = "9e8f463f-575f-42cb-85f3-759559997331",
- validationLogic = null,
- validationRegex = null,
- ),
- UserAttributeSpec(
- type = "date",
- name = "birthdate",
- label = "Birthdate",
- uuid = "83d655c7-bdb6-484d-904e-80c1058c8854",
- widget = "anastasis_gtk_ia_birthdate",
- validationLogic = null,
- validationRegex = null,
- ),
- ),
- )
+ Routes.SelectUserAttributes.route -> {
+ SelectUserAttributesScreen()
}
- composable(Routes.RecoveryCountry.route) {
- Text("This is the recover screen!")
- }
- composable(Routes.RestoreInit.route) {
+ Routes.RestoreInit.route -> {
Text("This is the restore session screen!")
}
}
diff --git a/anastasis/src/main/java/net/taler/anastasis/Routes.kt b/anastasis/src/main/java/net/taler/anastasis/Routes.kt
index 18db321..1a0accc 100644
--- a/anastasis/src/main/java/net/taler/anastasis/Routes.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/Routes.kt
@@ -6,13 +6,10 @@ sealed class Routes(
) {
object Home: Routes("home")
- // Backup
- object BackupContinent: Routes("backup_continent")
- object BackupCountry: Routes("backup_country")
- object BackupUserAttributes: Routes("backup_user_attributes")
-
- // Recovery
- object RecoveryCountry: Routes("recovery_country")
+ // Common
+ object SelectContinent: Routes("select_continent")
+ object SelectCountry: Routes("select_country")
+ object SelectUserAttributes: Routes("select_user_attributes")
// Restore
object RestoreInit: Routes("restore")
diff --git a/anastasis/src/main/java/net/taler/anastasis/backend/AnastasisReducerApi.kt b/anastasis/src/main/java/net/taler/anastasis/backend/AnastasisReducerApi.kt
new file mode 100644
index 0000000..42ce566
--- /dev/null
+++ b/anastasis/src/main/java/net/taler/anastasis/backend/AnastasisReducerApi.kt
@@ -0,0 +1,86 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 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.backend
+
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.json.decodeFromJsonElement
+import net.taler.anastasis.models.ReducerState
+import net.taler.common.ApiResponse
+import net.taler.common.ApiResponse.*
+import net.taler.common.TalerErrorCode.NONE
+import org.json.JSONObject
+
+@OptIn(DelicateCoroutinesApi::class)
+class AnastasisReducerApi() {
+
+ private val backendManager = BackendManager()
+
+ init {
+ GlobalScope.launch(Dispatchers.IO) {
+ backendManager.run()
+ }
+ }
+
+ suspend fun sendRequest(operation: String, args: JSONObject? = null): ApiResponse {
+ return backendManager.send(operation, args)
+ }
+
+ suspend inline fun startBackup(): ReducerState = withContext(Dispatchers.Default) {
+ val json = BackendManager.json
+ when (val response = sendRequest("anastasisStartBackup")) {
+ is Response -> json.decodeFromJsonElement(response.result)
+ is Error -> error("invalid reducer response")
+ }
+ }
+
+ suspend inline fun startRecovery(): ReducerState = withContext(Dispatchers.Default) {
+ val json = BackendManager.json
+ when (val response = sendRequest("anastasisStartRecovery")) {
+ is Response -> json.decodeFromJsonElement(response.result)
+ is Error -> error("invalid reducer response")
+ }
+ }
+
+ suspend inline fun reduceAction(
+ state: ReducerState,
+ action: String,
+ noinline args: (JSONObject.() -> JSONObject)? = null,
+ ): WalletResponse<ReducerState> = withContext(Dispatchers.Default) {
+ val json = BackendManager.json
+ val body = JSONObject().apply {
+ put("state", JSONObject(json.encodeToString(ReducerState.serializer(), state)))
+ put("action", action)
+ if (args != null) put("args", args.invoke(JSONObject()))
+ }
+ try {
+ when (val response = sendRequest("anastasisReduce", body)) {
+ is Response -> {
+ val t = json.decodeFromJsonElement<ReducerState>(response.result)
+ WalletResponse.Success(t)
+ }
+ is Error -> error("invalid reducer response")
+ }
+ } catch (e: Exception) {
+ val info = TalerErrorInfo(NONE, "", e.toString())
+ WalletResponse.Error(info)
+ }
+ }
+}
diff --git a/anastasis/src/main/java/net/taler/anastasis/backend/BackendManager.kt b/anastasis/src/main/java/net/taler/anastasis/backend/BackendManager.kt
new file mode 100644
index 0000000..8ba6025
--- /dev/null
+++ b/anastasis/src/main/java/net/taler/anastasis/backend/BackendManager.kt
@@ -0,0 +1,81 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2022 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.backend
+
+import android.util.Log
+import kotlinx.serialization.json.Json
+import net.taler.common.ApiMessage
+import net.taler.common.ApiResponse
+import net.taler.qtart.TalerWalletCore
+import net.taler.anastasis.BuildConfig
+import org.json.JSONObject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+class BackendManager {
+
+ companion object {
+ private const val TAG = "BackendManager"
+ private const val TAG_CORE = "taler-wallet-embedded"
+ val json = Json {
+ ignoreUnknownKeys = true
+ }
+ }
+
+ private val walletCore = TalerWalletCore()
+ private val requestManager = RequestManager()
+
+ init {
+ walletCore.setMessageHandler { onMessageReceived(it) }
+ if (BuildConfig.DEBUG) walletCore.setStdoutHandler {
+ Log.d(TAG_CORE, it)
+ }
+ }
+
+ fun run() {
+ walletCore.run()
+ }
+
+ suspend fun send(operation: String, args: JSONObject? = null): ApiResponse =
+ suspendCoroutine { cont ->
+ requestManager.addRequest(cont) { id ->
+ val request = JSONObject().apply {
+ put("id", id)
+ put("operation", operation)
+ if (args != null) put("args", args)
+ }
+ Log.d(TAG, "sending message:\n${request.toString(2)}")
+ walletCore.sendRequest(request.toString())
+ }
+ }
+
+ private fun onMessageReceived(msg: String) {
+ Log.d(TAG, "message received: $msg")
+ when (val message = json.decodeFromString<ApiMessage>(msg)) {
+ is ApiMessage.Notification -> {}
+ is ApiResponse -> {
+ val id = message.id
+ val cont = requestManager.getAndRemoveContinuation(id)
+ if (cont == null) {
+ Log.e(TAG, "wallet returned unknown request ID ($id)")
+ } else {
+ cont.resume(message)
+ }
+ }
+ }
+ }
+}
diff --git a/anastasis/src/main/java/net/taler/anastasis/backend/RequestManager.kt b/anastasis/src/main/java/net/taler/anastasis/backend/RequestManager.kt
new file mode 100644
index 0000000..70f81b3
--- /dev/null
+++ b/anastasis/src/main/java/net/taler/anastasis/backend/RequestManager.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.backend
+
+import androidx.annotation.GuardedBy
+import net.taler.common.ApiResponse
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.coroutines.Continuation
+
+class RequestManager {
+
+ @GuardedBy("this")
+ private val contMap = ConcurrentHashMap<Int, Continuation<ApiResponse>>()
+
+ @Volatile
+ @GuardedBy("this")
+ private var currentId = 0
+
+ @Synchronized
+ fun addRequest(cont: Continuation<ApiResponse>, block: (Int) -> Unit) {
+ val id = currentId++
+ contMap[id] = cont
+ block(id)
+ }
+
+ @Synchronized
+ fun getAndRemoveContinuation(id: Int): Continuation<ApiResponse>? {
+ return contMap.remove(id)
+ }
+
+}
diff --git a/anastasis/src/main/java/net/taler/anastasis/backend/WalletResponse.kt b/anastasis/src/main/java/net/taler/anastasis/backend/WalletResponse.kt
new file mode 100644
index 0000000..29ca6b2
--- /dev/null
+++ b/anastasis/src/main/java/net/taler/anastasis/backend/WalletResponse.kt
@@ -0,0 +1,134 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 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.backend
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.builtins.MapSerializer
+import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.json.JsonDecoder
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.buildJsonObject
+import kotlinx.serialization.json.jsonObject
+import kotlinx.serialization.json.jsonPrimitive
+import net.taler.common.TalerErrorCode
+
+@Serializable
+sealed class WalletResponse<T> {
+ @Serializable
+ @SerialName("response")
+ data class Success<T>(
+ val result: T,
+ ) : WalletResponse<T>()
+
+ @Serializable
+ @SerialName("error")
+ data class Error<T>(
+ val error: TalerErrorInfo,
+ ) : WalletResponse<T>()
+
+ fun onSuccess(block: (result: T) -> Unit): WalletResponse<T> {
+ if (this is Success) block(this.result)
+ return this
+ }
+
+ fun onError(block: (result: TalerErrorInfo) -> Unit): WalletResponse<T> {
+ if (this is Error) block(this.error)
+ return this
+ }
+}
+
+@Serializable(with = TalerErrorInfoSerializer::class)
+data class TalerErrorInfo(
+ // Numeric error code defined defined in the
+ // GANA gnu-taler-error-codes registry.
+ val code: TalerErrorCode,
+
+ // English description of the error code.
+ val hint: String? = null,
+
+ // English diagnostic message that can give details
+ // for the instance of the error.
+ val message: String? = null,
+
+ // Error extra details
+ val extra: Map<String, JsonElement> = mapOf(),
+) {
+ val userFacingMsg: String
+ get() {
+ return StringBuilder().apply {
+ // If there's a hint in errorResponse, use it.
+ extra["errorResponse"]
+ ?.jsonObject
+ ?.get("hint")
+ ?.let {
+ append(it.jsonPrimitive.content)
+ } ?: {
+ // Otherwise, use the standard ones.
+ hint?.let { append(it) }
+ message?.let { append(" ").append(it) }
+ }
+ }.toString()
+ }
+
+ fun getStringExtra(key: String): String? =
+ extra[key]?.jsonPrimitive?.content
+}
+
+class TalerErrorInfoSerializer : KSerializer<TalerErrorInfo> {
+ private val stringToJsonElementSerializer = MapSerializer(String.serializer(), JsonElement.serializer())
+
+ override val descriptor: SerialDescriptor
+ get() = stringToJsonElementSerializer.descriptor
+
+ override fun deserialize(decoder: Decoder): TalerErrorInfo {
+ // Decoder -> JsonInput
+ require(decoder is JsonDecoder)
+ val json = decoder.json
+ val filtersMap = decoder.decodeSerializableValue(stringToJsonElementSerializer)
+
+ val code = filtersMap["code"]?.let {
+ json.decodeFromJsonElement(TalerErrorCode.serializer(), it)
+ } ?: TalerErrorCode.UNKNOWN
+ val hint = filtersMap["hint"]?.let {
+ json.decodeFromJsonElement(String.serializer(), it)
+ }
+ val message = filtersMap["message"]?.let {
+ json.decodeFromJsonElement(String.serializer(), it)
+ }
+
+ val knownKeys = setOf("code", "hint", "message")
+ val unknownFilters = filtersMap.filter { (key, _) -> !knownKeys.contains(key) }
+
+ return TalerErrorInfo(code, hint, message, unknownFilters)
+ }
+
+ override fun serialize(encoder: Encoder, value: TalerErrorInfo) {
+ encoder.encodeSerializableValue(JsonObject.serializer(), buildJsonObject {
+ put("code", JsonPrimitive(value.code.code))
+ put("hint", JsonPrimitive(value.hint))
+ put("message", JsonPrimitive(value.message))
+ value.extra.forEach { (key, value) -> put(key, value) }
+ })
+ }
+}
diff --git a/anastasis/src/main/java/net/taler/anastasis/models/ReducerState.kt b/anastasis/src/main/java/net/taler/anastasis/models/ReducerState.kt
index 85494f0..37f380f 100644
--- a/anastasis/src/main/java/net/taler/anastasis/models/ReducerState.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/models/ReducerState.kt
@@ -7,10 +7,110 @@ import kotlinx.serialization.json.JsonClassDiscriminator
import net.taler.common.Amount
import net.taler.common.Timestamp
-@OptIn(ExperimentalSerializationApi::class)
@Serializable
+@OptIn(ExperimentalSerializationApi::class)
@JsonClassDiscriminator("reducer_type")
-abstract class ReducerState
+sealed class ReducerState {
+ @Serializable
+ @SerialName("backup")
+ data class Backup(
+ @SerialName("backup_state")
+ val backupState: BackupStates,
+ val continents: List<ContinentInfo>? = null,
+ val countries: List<CountryInfo>? = null,
+ @SerialName("identity_attributes")
+ val identityAttributes: Map<String, String>? = null,
+ @SerialName("authentication_providers")
+ val authenticationProviders: Map<String, AuthenticationProviderStatus>? = null,
+ @SerialName("authentication_methods")
+ val authenticationMethods: List<AuthMethod>? = null,
+ @SerialName("required_attributes")
+ val requiredAttributes: List<UserAttributeSpec>? = null,
+ @SerialName("selected_continent")
+ val selectedContinent: String? = null,
+ @SerialName("selected_country")
+ val selectedCountry: String? = null,
+ @SerialName("secret_name")
+ val secretName: String? = null,
+ val policies: List<Policy>? = null,
+ @SerialName("recovery_data")
+ val recoveryData: RecoveryData? = null,
+ @SerialName("policy_providers")
+ val policyProviders: List<PolicyProvider>? = null,
+ @SerialName("success_details")
+ val successDetails: Map<String, SuccessDetail>? = null,
+ val payments: List<String>? = null,
+ @SerialName("policy_payment_requests")
+ val policyPaymentRequests: List<PolicyPaymentRequest>? = null,
+ @SerialName("core_secret")
+ val coreSecret: CoreSecret? = null,
+ val expiration: Timestamp? = null,
+ @SerialName("upload_fees")
+ val uploadFees: List<UploadFee>? = null,
+ @SerialName("truth_upload_payment_secrets")
+ val truthUploadPaymentSecrets: Map<String, String>? = null,
+ ): ReducerState() {
+ @Serializable
+ data class RecoveryData(
+ @SerialName("truth_metadata")
+ val truthMetadata: Map<String, TruthMetaData>,
+ @SerialName("recovery_document")
+ val recoveryDocument: RecoveryDocument,
+ )
+
+ @Serializable
+ data class PolicyPaymentRequest(
+ val payto: String,
+ val provider: String,
+ )
+
+ @Serializable
+ data class UploadFee(
+ val fee: Amount,
+ )
+ }
+
+ @Serializable
+ @SerialName("recovery")
+ data class Recovery(
+ @SerialName("recovery_state")
+ val recoveryState: RecoveryStates,
+ @SerialName("identity_attributes")
+ val identityAttributes: Map<String, String>? = null,
+ val continents: List<ContinentInfo>? = null,
+ val countries: List<CountryInfo>? = null,
+ @SerialName("selected_continent")
+ val selectedContinent: String? = null,
+ @SerialName("selected_country")
+ val selectedCountry: String? = null,
+ @SerialName("required_attributes")
+ val requiredAttributes: List<UserAttributeSpec>? = null,
+ @SerialName("recovery_information")
+ val recoveryInformation: RecoveryInformation? = null,
+ @SerialName("recovery_document")
+ val recoveryDocument: RecoveryInternalData? = null,
+ @SerialName("verbatim_recovery_document")
+ val verbatimRecoveryDocument: RecoveryDocument? = null,
+ @SerialName("selected_challenge_uuid")
+ val selectedChallengeUuid: String? = null,
+ @SerialName("selected_version")
+ val selectedVersion: SelectedVersionInfo? = null,
+ @SerialName("challenge_feedback")
+ val challengeFeedback: Map<String, ChallengeFeedback>? = null,
+ // TODO: recovered_key_shares
+ val coreSecret: CoreSecret? = null,
+ @SerialName("authentication_providers")
+ val authenticationProviders: Map<String, AuthenticationProviderStatus>? = null,
+ ): ReducerState()
+
+ @Serializable
+ @SerialName("error")
+ data class Error(
+ val code: Int,
+ val hint: String? = null,
+ val detail: String? = null,
+ ): ReducerState()
+}
@Serializable
data class ContinentInfo(
@@ -58,65 +158,6 @@ data class CoreSecret(
)
@Serializable
-@SerialName("backup")
-data class ReducerStateBackup(
- @SerialName("backup_state")
- val backupState: BackupStates,
- val continents: List<ContinentInfo>? = null,
- val countries: List<CountryInfo>? = null,
- @SerialName("identity_attributes")
- val identityAttributes: Map<String, String>? = null,
- @SerialName("authentication_providers")
- val authenticationProviders: Map<String, AuthenticationProviderStatus>? = null,
- @SerialName("authentication_methods")
- val authenticationMethods: List<AuthMethod>? = null,
- @SerialName("required_attributes")
- val requiredAttributes: List<UserAttributeSpec>? = null,
- @SerialName("selected_continent")
- val selectedContinent: String? = null,
- @SerialName("selected_country")
- val selectedCountry: String? = null,
- @SerialName("secret_name")
- val secretName: String? = null,
- val policies: List<Policy>? = null,
- @SerialName("recovery_data")
- val recoveryData: RecoveryData? = null,
- @SerialName("policy_providers")
- val policyProviders: List<PolicyProvider>? = null,
- @SerialName("success_details")
- val successDetails: Map<String, SuccessDetail>? = null,
- val payments: List<String>? = null,
- @SerialName("policy_payment_requests")
- val policyPaymentRequests: List<PolicyPaymentRequest>? = null,
- @SerialName("core_secret")
- val coreSecret: CoreSecret? = null,
- val expiration: Timestamp? = null,
- @SerialName("upload_fees")
- val uploadFees: List<UploadFee>? = null,
- @SerialName("truth_upload_payment_secrets")
- val truthUploadPaymentSecrets: Map<String, String>? = null,
-): ReducerState() {
- @Serializable
- data class RecoveryData(
- @SerialName("truth_metadata")
- val truthMetadata: Map<String, TruthMetaData>,
- @SerialName("recovery_document")
- val recoveryDocument: RecoveryDocument,
- )
-
- @Serializable
- data class PolicyPaymentRequest(
- val payto: String,
- val provider: String,
- )
-
- @Serializable
- data class UploadFee(
- val fee: Amount,
- )
-}
-
-@Serializable
data class AuthMethod(
val type: String,
val instructions: String,
@@ -141,9 +182,9 @@ data class UserAttributeSpec(
val widget: String,
val optional: Boolean? = null,
@SerialName("validation-regex")
- val validationRegex: String?,
+ val validationRegex: String? = null,
@SerialName("validation-logic")
- val validationLogic: String?,
+ val validationLogic: String? = null,
val autocomplete: String? = null,
)
@@ -168,38 +209,6 @@ data class RecoveryInformation(
}
@Serializable
-@SerialName("recovery")
-data class ReducerStateRecovery(
- val recoveryState: RecoveryStates,
- @SerialName("identity_attributes")
- val identityAttributes: Map<String, String>? = null,
- val continents: List<ContinentInfo>? = null,
- val countries: List<CountryInfo>? = null,
- @SerialName("selected_continent")
- val selectedContinent: String? = null,
- @SerialName("selected_country")
- val selectedCountry: String? = null,
- @SerialName("required_attributes")
- val requiredAttributes: List<UserAttributeSpec>? = null,
- @SerialName("recovery_information")
- val recoveryInformation: RecoveryInformation? = null,
- @SerialName("recovery_document")
- val recoveryDocument: RecoveryInternalData? = null,
- @SerialName("verbatim_recovery_document")
- val verbatimRecoveryDocument: RecoveryDocument? = null,
- @SerialName("selected_challenge_uuid")
- val selectedChallengeUuid: String? = null,
- @SerialName("selected_version")
- val selectedVersion: SelectedVersionInfo? = null,
- @SerialName("challenge_feedback")
- val challengeFeedback: Map<String, ChallengeFeedback>? = null,
- // TODO: recovered_key_shares
- val coreSecret: CoreSecret? = null,
- @SerialName("authentication_providers")
- val authenticationProviders: Map<String, AuthenticationProviderStatus>,
-): ReducerState()
-
-@Serializable
data class TruthMetaData(
val uuid: String,
@SerialName("key_share")
@@ -216,14 +225,6 @@ data class TruthMetaData(
)
@Serializable
-@SerialName("error")
-data class ReducerStateError(
- val code: Int,
- val hint: String? = null,
- val detail: String? = null,
-): ReducerState()
-
-@Serializable
enum class BackupStates {
@SerialName("CONTINENT_SELECTING")
ContinentSelecting,
@@ -287,48 +288,50 @@ data class MethodSpec(
val usageFee: String,
)
-@Serializable
-@SerialName("not-contacted")
-class AuthenticationProviderStatusNotContacted: AuthenticationProviderStatus()
-
-@Serializable
-@SerialName("ok")
-data class AuthenticationProviderStatusOk(
- @SerialName("annual_fee")
- val annualFee: String,
- @SerialName("business_name")
- val businessName: String,
- val currency: String,
- @SerialName("http_status")
- val httpStatus: Int,
- @SerialName("liability_limit")
- val liabilityLimit: String,
- @SerialName("provider_salt")
- val providerSalt: String,
- @SerialName("storage_limit_in_megabytes")
- val storageLimitInMegabytes: Int,
- @SerialName("truth_upload_fee")
- val truthUploadFee: String,
- val methods: List<MethodSpec>,
-): AuthenticationProviderStatus()
-
-@Serializable
-@SerialName("disabled")
-class AuthenticationProviderStatusDisabled: AuthenticationProviderStatus()
-@Serializable
-@SerialName("error")
-data class AuthenticationProviderStatusError(
- @SerialName("http_status")
- val httpStatus: Int? = null,
- val code: Int,
- val hint: String? = null,
-): AuthenticationProviderStatus()
@OptIn(ExperimentalSerializationApi::class)
@Serializable
@JsonClassDiscriminator("status")
-abstract class AuthenticationProviderStatus()
+sealed class AuthenticationProviderStatus {
+ @Serializable
+ @SerialName("not-contacted")
+ object NotContacted: AuthenticationProviderStatus()
+
+ @Serializable
+ @SerialName("ok")
+ data class Ok(
+ @SerialName("annual_fee")
+ val annualFee: String,
+ @SerialName("business_name")
+ val businessName: String,
+ val currency: String,
+ @SerialName("http_status")
+ val httpStatus: Int,
+ @SerialName("liability_limit")
+ val liabilityLimit: String,
+ @SerialName("provider_salt")
+ val providerSalt: String,
+ @SerialName("storage_limit_in_megabytes")
+ val storageLimitInMegabytes: Int,
+ @SerialName("truth_upload_fee")
+ val truthUploadFee: String,
+ val methods: List<MethodSpec>,
+ ): AuthenticationProviderStatus()
+
+ @Serializable
+ @SerialName("disabled")
+ object Disabled : AuthenticationProviderStatus()
+
+ @Serializable
+ @SerialName("error")
+ data class Error(
+ @SerialName("http_status")
+ val httpStatus: Int? = null,
+ val code: Int,
+ val hint: String? = null,
+ ): AuthenticationProviderStatus()
+}
// TODO: ReducerStateBackupUserAttributesCollecting
diff --git a/anastasis/src/main/java/net/taler/anastasis/reducers/ReducerManager.kt b/anastasis/src/main/java/net/taler/anastasis/reducers/ReducerManager.kt
new file mode 100644
index 0000000..90a5158
--- /dev/null
+++ b/anastasis/src/main/java/net/taler/anastasis/reducers/ReducerManager.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.reducers
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.encodeToJsonElement
+import net.taler.anastasis.backend.AnastasisReducerApi
+import net.taler.anastasis.models.ContinentInfo
+import net.taler.anastasis.models.CountryInfo
+import net.taler.anastasis.models.ReducerState
+
+class ReducerManager(
+ private val state: MutableStateFlow<ReducerState?>,
+ private val api: AnastasisReducerApi,
+ private val scope: CoroutineScope,
+) {
+ // TODO: error handling!
+
+ fun startBackup() = scope.launch {
+ state.value = api.startBackup()
+ }
+
+ fun startRecovery() = scope.launch {
+ state.value = api.startRecovery()
+ }
+
+ fun back() = scope.launch {
+ state.value?.let { initialState ->
+ api.reduceAction(initialState, "back")
+ .onSuccess { newState ->
+ state.value = newState
+ }
+ }
+ }
+
+ fun selectContinent(continent: ContinentInfo) = scope.launch {
+ state.value?.let { initialState ->
+ api.reduceAction(initialState, "select_continent") {
+ put("continent", continent.name)
+ }.onSuccess { newState ->
+ state.value = newState
+ }
+ }
+ }
+
+ fun selectCountry(country: CountryInfo) = scope.launch {
+ state.value?.let { initialState ->
+ api.reduceAction(initialState, "select_country") {
+ put("country_code", country.code)
+ // TODO: stop hardcoding currency!
+ put("currency", "EUR")
+ }.onSuccess { newState ->
+ state.value = newState
+ }
+ }
+ }
+
+ fun enterUserAttributes(userAttributes: Map<String, String>) = scope.launch {
+ state.value?.let { initialState ->
+ api.reduceAction(initialState, "enter_user_attributes") {
+ put("identity_attributes", Json.encodeToJsonElement(userAttributes))
+ }.onSuccess { newState ->
+ state.value = newState
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupContinentScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupContinentScreen.kt
deleted file mode 100644
index 9638ba2..0000000
--- a/anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupContinentScreen.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-package net.taler.anastasis.ui.backup
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.navigation.NavController
-import androidx.navigation.compose.rememberNavController
-import net.taler.anastasis.R
-import net.taler.anastasis.Routes
-import net.taler.anastasis.models.ContinentInfo
-import net.taler.anastasis.ui.reusable.components.Picker
-import net.taler.anastasis.ui.reusable.pages.WizardPage
-import net.taler.anastasis.ui.theme.LocalSpacing
-
-@Composable
-fun BackupContinentScreen(
- navController: NavController,
- continents: List<ContinentInfo>,
- onSelectContinent: (continent: ContinentInfo) -> Unit,
-) {
- WizardPage(
- title = stringResource(R.string.backup_country_title),
- navigationIcon = {
- IconButton(onClick = {
- navController.navigate(Routes.Home.route)
- }) {
- Icon(Icons.Default.ArrowBack, "back")
- }
- },
- showPrev = false,
- onNextClicked = {
- navController.navigate(Routes.BackupCountry.route)
- },
- ) {
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(LocalSpacing.current.medium),
- verticalArrangement = Arrangement.Top,
- ) {
- Picker(
- label = stringResource(R.string.continent),
- options = continents.map { it.name }.toSet(),
- onOptionChanged = { option ->
- continents.find { it.name == option }?.let { continent ->
- onSelectContinent(continent)
- }
- },
- )
- }
- }
-}
-
-@Composable
-@Preview
-fun BackupContinentScreenPreview() {
- val navController = rememberNavController()
- BackupContinentScreen(
- navController = navController,
- continents = listOf(
- ContinentInfo("Europe"),
- ContinentInfo("India"),
- ContinentInfo("Asia"),
- ContinentInfo("North America")),
- onSelectContinent = {},
- )
-} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupCountryScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupCountryScreen.kt
deleted file mode 100644
index dfb974f..0000000
--- a/anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupCountryScreen.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-package net.taler.anastasis.ui.backup
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.navigation.NavController
-import androidx.navigation.compose.rememberNavController
-import net.taler.anastasis.R
-import net.taler.anastasis.Routes
-import net.taler.anastasis.models.CountryInfo
-import net.taler.anastasis.ui.reusable.components.Picker
-import net.taler.anastasis.ui.reusable.pages.WizardPage
-import net.taler.anastasis.ui.theme.LocalSpacing
-
-@Composable
-fun BackupCountryScreen(
- navController: NavController,
- countries: List<CountryInfo>,
- onSelectCountry: (country: CountryInfo) -> Unit,
-) {
- WizardPage(
- title = stringResource(R.string.backup_country_title),
- navigationIcon = {
- IconButton(onClick = {
- navController.navigate(Routes.Home.route)
- }) {
- Icon(Icons.Default.ArrowBack, "back")
- }
- },
- onPrevClicked = {
- navController.navigate(Routes.BackupContinent.route)
- },
- onNextClicked = {
- navController.navigate(Routes.BackupUserAttributes.route)
- },
- ) {
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(LocalSpacing.current.medium),
- verticalArrangement = Arrangement.Top,
- ) {
- Picker(
- label = stringResource(R.string.country),
- options = countries.map { it.name }.toSet(),
- onOptionChanged = { option ->
- countries.find { it.name == option }?.let { country ->
- onSelectCountry(country)
- }
- },
- )
- }
- }
-}
-
-@Composable
-@Preview
-fun BackupCountryScreenPreview() {
- val navController = rememberNavController()
- BackupCountryScreen(
- navController = navController,
- countries = listOf(
- CountryInfo("ch", "Switzerland", "Europe"),
- CountryInfo("de", "Germany", "Europe"),
- ),
- onSelectCountry = {},
- )
-} \ 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
new file mode 100644
index 0000000..b8ec266
--- /dev/null
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectContinentScreen.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+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.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.hilt.navigation.compose.hiltViewModel
+import net.taler.anastasis.R
+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.ReducerViewModel
+
+@Composable
+fun SelectContinentScreen(
+ viewModel: ReducerViewModel = hiltViewModel(),
+) {
+ val reducerState by viewModel.reducerState.collectAsState()
+ val continents = when (val state = reducerState) {
+ is ReducerState.Backup -> state.continents
+ is ReducerState.Recovery -> state.continents
+ else -> null
+ } ?: emptyList()
+
+ var selectedContinent by remember { mutableStateOf<ContinentInfo?>(null) }
+
+ WizardPage(
+ title = stringResource(R.string.select_country_title),
+ navigationIcon = {
+ IconButton(onClick = {
+ viewModel.goHome()
+ }) {
+ Icon(Icons.Default.ArrowBack, "back")
+ }
+ },
+ showPrev = false,
+ onNextClicked = {
+ selectedContinent?.let {
+ viewModel.reducerManager.selectContinent(it)
+ }
+ },
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(LocalSpacing.current.medium),
+ verticalArrangement = Arrangement.Top,
+ ) {
+ Picker(
+ label = stringResource(R.string.continent),
+ options = continents.map { it.name }.toSet(),
+ onOptionChanged = { option ->
+ continents.find { it.name == option }?.let { continent ->
+ selectedContinent = continent
+ }
+ },
+ )
+ }
+ }
+} \ 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
new file mode 100644
index 0000000..1c4fa49
--- /dev/null
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectCountryScreen.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+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.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.hilt.navigation.compose.hiltViewModel
+import net.taler.anastasis.R
+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.ReducerViewModel
+
+@Composable
+fun SelectCountryScreen(
+ viewModel: ReducerViewModel = hiltViewModel(),
+) {
+ val reducerState by viewModel.reducerState.collectAsState()
+ val countries = when (val state = reducerState) {
+ is ReducerState.Backup -> state.countries
+ is ReducerState.Recovery -> state.countries
+ else -> null
+ } ?: emptyList()
+
+ var selectedCountry by remember { mutableStateOf<CountryInfo?>(null) }
+
+ WizardPage(
+ title = stringResource(R.string.select_country_title),
+ navigationIcon = {
+ IconButton(onClick = {
+ viewModel.goHome()
+ }) {
+ Icon(Icons.Default.ArrowBack, "back")
+ }
+ },
+ onPrevClicked = {
+ viewModel.reducerManager.back()
+ },
+ onNextClicked = {
+ selectedCountry?.let {
+ viewModel.reducerManager.selectCountry(it)
+ }
+ },
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(LocalSpacing.current.medium),
+ verticalArrangement = Arrangement.Top,
+ ) {
+ Picker(
+ label = stringResource(R.string.country),
+ options = countries.map { it.name }.toSet(),
+ onOptionChanged = { option ->
+ countries.find { it.name == option }?.let { country ->
+ selectedCountry = country
+ }
+ },
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupUserAttributesScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectUserAttributesScreen.kt
index bbd0147..6fa9f97 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/backup/BackupUserAttributesScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/common/SelectUserAttributesScreen.kt
@@ -14,7 +14,7 @@
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-package net.taler.anastasis.ui.backup
+package net.taler.anastasis.ui.common
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Spacer
@@ -32,6 +32,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
@@ -39,38 +40,44 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.navigation.NavController
-import androidx.navigation.compose.rememberNavController
+import androidx.hilt.navigation.compose.hiltViewModel
import net.taler.anastasis.R
-import net.taler.anastasis.Routes
-import net.taler.anastasis.models.UserAttributeSpec
+import net.taler.anastasis.models.ReducerState
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.ReducerViewModel
import java.util.Calendar
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun BackupUserAttributesScreen(
- navController: NavController,
- userAttributes: List<UserAttributeSpec>,
+fun SelectUserAttributesScreen(
+ viewModel: ReducerViewModel = hiltViewModel(),
) {
+ val reducerState by viewModel.reducerState.collectAsState()
+ val userAttributes = when (val state = reducerState) {
+ is ReducerState.Backup -> state.requiredAttributes
+ is ReducerState.Recovery -> state.requiredAttributes
+ else -> null
+ } ?: emptyList()
+
val values = remember { mutableStateMapOf<String, String>() }
WizardPage(
- title = stringResource(R.string.backup_user_attributes_title),
+ title = stringResource(R.string.select_user_attributes_title),
navigationIcon = {
IconButton(onClick = {
- navController.navigate(Routes.Home.route)
+ viewModel.goHome()
}) {
Icon(Icons.Default.ArrowBack, "back")
}
},
onPrevClicked = {
- navController.navigate(Routes.BackupCountry.route)
+ viewModel.reducerManager.back()
+ },
+ onNextClicked = {
+ viewModel.reducerManager.enterUserAttributes(values)
},
- onNextClicked = {},
) {
LazyColumn(
modifier = Modifier
@@ -82,8 +89,8 @@ fun BackupUserAttributesScreen(
when (attr.type) {
"string" -> OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
- value = values[attr.uuid] ?: "",
- onValueChange = { values[attr.uuid] = it },
+ value = values[attr.name] ?: "",
+ onValueChange = { values[attr.name] = it },
label = { Text(attr.label) },
)
"date" -> @Composable {
@@ -109,33 +116,4 @@ fun BackupUserAttributesScreen(
}
}
}
-}
-
-@Preview
-@Composable
-fun BackupUserAttributesScreenPreview() {
- val navController = rememberNavController()
- BackupUserAttributesScreen(
- navController = navController,
- userAttributes = listOf(
- UserAttributeSpec(
- type = "string",
- name = "full_name",
- label = "Full name",
- widget = "anastasis_gtk_ia_full_name",
- uuid = "9e8f463f-575f-42cb-85f3-759559997331",
- validationLogic = null,
- validationRegex = null,
- ),
- UserAttributeSpec(
- type = "date",
- name = "birthdate",
- label = "Birthdate",
- uuid = "83d655c7-bdb6-484d-904e-80c1058c8854",
- widget = "anastasis_gtk_ia_birthdate",
- validationLogic = null,
- validationRegex = null,
- ),
- ),
- )
} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/ui/home/HomeScreen.kt b/anastasis/src/main/java/net/taler/anastasis/ui/home/HomeScreen.kt
index 98a70b4..f79c3a2 100644
--- a/anastasis/src/main/java/net/taler/anastasis/ui/home/HomeScreen.kt
+++ b/anastasis/src/main/java/net/taler/anastasis/ui/home/HomeScreen.kt
@@ -16,18 +16,16 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.navigation.NavController
-import androidx.navigation.compose.rememberNavController
+import androidx.hilt.navigation.compose.hiltViewModel
import net.taler.anastasis.R
-import net.taler.anastasis.Routes
import net.taler.anastasis.ui.reusable.components.ActionCard
+import net.taler.anastasis.viewmodels.ReducerViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(
- navController: NavController,
+ viewModel: ReducerViewModel = hiltViewModel(),
) {
Scaffold(
topBar = {
@@ -53,7 +51,7 @@ fun HomeScreen(
icon = { Icon(Icons.Outlined.Upload, null) },
headline = stringResource(R.string.backup_secret),
onClick = {
- navController.navigate(Routes.BackupContinent.route)
+ viewModel.reducerManager.startBackup()
},
)
@@ -66,7 +64,7 @@ fun HomeScreen(
icon = { Icon(Icons.Outlined.Download, null) },
headline = stringResource(R.string.recover_secret),
onClick = {
- navController.navigate(Routes.RecoveryCountry.route)
+ viewModel.reducerManager.startRecovery()
},
)
@@ -78,17 +76,8 @@ fun HomeScreen(
.fillMaxWidth(),
icon = { Icon(Icons.Outlined.Restore, null) },
headline = stringResource(R.string.restore_session),
- onClick = {
- navController.navigate(Routes.RestoreInit.route)
- },
+ onClick = {},
)
}
}
-}
-
-@Composable
-@Preview
-fun HomeScreenPreview() {
- val navController = rememberNavController()
- HomeScreen(navController = navController)
} \ No newline at end of file
diff --git a/anastasis/src/main/java/net/taler/anastasis/viewmodels/ReducerViewModel.kt b/anastasis/src/main/java/net/taler/anastasis/viewmodels/ReducerViewModel.kt
new file mode 100644
index 0000000..3f5671b
--- /dev/null
+++ b/anastasis/src/main/java/net/taler/anastasis/viewmodels/ReducerViewModel.kt
@@ -0,0 +1,81 @@
+/*
+ * 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 android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import net.taler.anastasis.Routes
+import net.taler.anastasis.backend.AnastasisReducerApi
+import net.taler.anastasis.models.BackupStates
+import net.taler.anastasis.models.RecoveryStates
+import net.taler.anastasis.models.ReducerState
+import net.taler.anastasis.reducers.ReducerManager
+import javax.inject.Inject
+
+@HiltViewModel
+class ReducerViewModel @Inject constructor(): ViewModel() {
+ private val api = AnastasisReducerApi()
+ val reducerManager: ReducerManager
+
+ private val _reducerState = MutableStateFlow<ReducerState?>(null)
+ val reducerState = _reducerState.asStateFlow()
+ private val _navRoute = MutableStateFlow(Routes.Home.route)
+ val navRoute = _navRoute.asStateFlow()
+
+ init {
+ reducerManager = ReducerManager(_reducerState, api, viewModelScope)
+ viewModelScope.launch {
+ _reducerState.collect {
+ Log.d("ReducerViewModel", it?.toString() ?: "nothing")
+ _navRoute.value = when (it) {
+ is ReducerState.Backup -> when (it.backupState) {
+ BackupStates.ContinentSelecting -> Routes.SelectContinent.route
+ BackupStates.CountrySelecting -> Routes.SelectCountry.route
+ BackupStates.UserAttributesCollecting -> Routes.SelectUserAttributes.route
+ BackupStates.AuthenticationsEditing -> TODO()
+ BackupStates.PoliciesReviewing -> TODO()
+ BackupStates.SecretEditing -> TODO()
+ BackupStates.TruthsPaying -> TODO()
+ BackupStates.PoliciesPaying -> TODO()
+ BackupStates.BackupFinished -> TODO()
+ }
+ is ReducerState.Recovery -> when (it.recoveryState) {
+ RecoveryStates.ContinentSelecting -> Routes.SelectContinent.route
+ RecoveryStates.CountrySelecting -> Routes.SelectCountry.route
+ RecoveryStates.UserAttributesCollecting -> Routes.SelectUserAttributes.route
+ RecoveryStates.SecretSelecting -> TODO()
+ RecoveryStates.ChallengeSelecting -> TODO()
+ RecoveryStates.ChallengePaying -> TODO()
+ RecoveryStates.ChallengeSolving -> TODO()
+ RecoveryStates.RecoveryFinished -> TODO()
+ }
+ is ReducerState.Error -> TODO()
+ else -> Routes.Home.route
+ }
+ }
+ }
+ }
+
+ fun goHome() {
+ _reducerState.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 9e974f2..de788ce 100644
--- a/anastasis/src/main/res/values/strings.xml
+++ b/anastasis/src/main/res/values/strings.xml
@@ -7,9 +7,12 @@
<string name="recover_secret">Recover a secret</string>
<string name="restore_session">Restore session</string>
- <string name="backup_country_title">Where do you live?</string>
+ <!-- Shared -->
<string name="country">Country</string>
<string name="continent">Continent</string>
- <string name="backup_user_attributes_title">Who are you?</string>
- <string name="backup_auth_methods_title">Authentication methods</string>
+
+ <!-- Common -->
+ <string name="select_country_title">Where do you live?</string>
+ <string name="select_user_attributes_title">Who are you?</string>
+ <string name="select_auth_methods_title">Authentication methods</string>
</resources> \ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/backend/ApiResponse.kt b/taler-kotlin-android/src/main/java/net/taler/common/ApiResponse.kt
index 46eb2f0..d51d2c0 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/ApiResponse.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/ApiResponse.kt
@@ -14,7 +14,7 @@
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-package net.taler.wallet.backend
+package net.taler.common
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
diff --git a/wallet/src/main/java/net/taler/wallet/backend/TalerErrorCode.kt b/taler-kotlin-android/src/main/java/net/taler/common/TalerErrorCode.kt
index 2242e33..dbe59a4 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/TalerErrorCode.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/TalerErrorCode.kt
@@ -1,26 +1,19 @@
/*
- This file is part of GNU Taler
- Copyright (C) 2012-2020 Taler Systems SA
-
- GNU Taler is free software: you can redistribute it and/or modify it
- under the terms of the GNU Lesser General Public License as published
- by the Free Software Foundation, either version 3 of the License,
- 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
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- SPDX-License-Identifier: LGPL3.0-or-later
-
- Note: the LGPL does not apply to all components of GNU Taler,
- but it does apply to this file.
+ * 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.wallet.backend
+package net.taler.common
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 15fe7e4..083d1be 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -32,7 +32,7 @@ import net.taler.common.AmountParserException
import net.taler.common.Event
import net.taler.common.toEvent
import net.taler.wallet.accounts.AccountManager
-import net.taler.wallet.backend.NotificationPayload
+import net.taler.common.NotificationPayload
import net.taler.wallet.backend.NotificationReceiver
import net.taler.wallet.backend.VersionReceiver
import net.taler.wallet.backend.WalletBackendApi
diff --git a/wallet/src/main/java/net/taler/wallet/Utils.kt b/wallet/src/main/java/net/taler/wallet/Utils.kt
index d1cdf8c..96b88d1 100644
--- a/wallet/src/main/java/net/taler/wallet/Utils.kt
+++ b/wallet/src/main/java/net/taler/wallet/Utils.kt
@@ -40,7 +40,7 @@ import net.taler.common.Amount
import net.taler.common.AmountParserException
import net.taler.common.showError
import net.taler.common.startActivitySafe
-import net.taler.wallet.backend.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED
+import net.taler.common.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.transactions.Transaction
diff --git a/wallet/src/main/java/net/taler/wallet/backend/BackendManager.kt b/wallet/src/main/java/net/taler/wallet/backend/BackendManager.kt
index b2f1f10..23594ae 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/BackendManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/BackendManager.kt
@@ -19,6 +19,9 @@ package net.taler.wallet.backend
import android.util.Log
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
+import net.taler.common.ApiMessage
+import net.taler.common.ApiResponse
+import net.taler.common.NotificationPayload
import net.taler.qtart.TalerWalletCore
import net.taler.wallet.BuildConfig
import org.json.JSONObject
diff --git a/wallet/src/main/java/net/taler/wallet/backend/RequestManager.kt b/wallet/src/main/java/net/taler/wallet/backend/RequestManager.kt
index 041656e..5e7ce40 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/RequestManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/RequestManager.kt
@@ -17,6 +17,7 @@
package net.taler.wallet.backend
import androidx.annotation.GuardedBy
+import net.taler.common.ApiResponse
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.Continuation
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
index bf6f371..f68c8b6 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -24,7 +24,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.decodeFromJsonElement
-import net.taler.wallet.backend.TalerErrorCode.NONE
+import net.taler.common.ApiResponse
+import net.taler.common.TalerErrorCode.NONE
import org.json.JSONObject
const val WALLET_DB = "talerwalletdb.sql"
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
index 8ef135f..2ece261 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
@@ -31,6 +31,7 @@ import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
+import net.taler.common.TalerErrorCode
@Serializable
sealed class WalletResponse<T> {
diff --git a/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt
index 1d5c91e..f575081 100644
--- a/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt
@@ -35,7 +35,7 @@ import net.taler.common.Amount
import net.taler.common.Timestamp
import net.taler.common.toAbsoluteTime
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
+import net.taler.common.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.transactions.AmountType
import net.taler.wallet.transactions.ErrorTransactionButton
diff --git a/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt
index c08bc76..8a3c4b5 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt
@@ -35,7 +35,7 @@ import net.taler.common.ContractMerchant
import net.taler.common.Timestamp
import net.taler.common.toAbsoluteTime
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode
+import net.taler.common.TalerErrorCode
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.compose.TalerSurface
import net.taler.wallet.transactions.AmountType
diff --git a/wallet/src/main/java/net/taler/wallet/peer/IncomingComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/IncomingComposable.kt
index 0c118e6..1f9ed72 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/IncomingComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/IncomingComposable.kt
@@ -48,7 +48,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import net.taler.common.Amount
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED
+import net.taler.common.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED
import net.taler.wallet.backend.TalerErrorInfo
data class IncomingData(
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt
index de62cda..edcd61c 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullResultComposable.kt
@@ -38,7 +38,7 @@ import androidx.compose.ui.unit.dp
import kotlinx.serialization.json.JsonPrimitive
import net.taler.common.QrCodeManager
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED
+import net.taler.common.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.compose.QrCodeUriComposable
import net.taler.wallet.compose.TalerSurface
diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt
index 0a4ee70..0d51ca9 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushResultComposable.kt
@@ -38,7 +38,7 @@ import androidx.compose.ui.unit.dp
import kotlinx.serialization.json.JsonPrimitive
import net.taler.common.QrCodeManager
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED
+import net.taler.common.TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.compose.QrCodeUriComposable
import net.taler.wallet.compose.TalerSurface
diff --git a/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt b/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
index 6e65e0b..6191d58 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/PeerManager.kt
@@ -28,7 +28,7 @@ import net.taler.common.Amount
import net.taler.common.QrCodeManager
import net.taler.common.Timestamp
import net.taler.wallet.TAG
-import net.taler.wallet.backend.TalerErrorCode.UNKNOWN
+import net.taler.common.TalerErrorCode.UNKNOWN
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.backend.WalletBackendApi
import net.taler.wallet.exchanges.ExchangeItem
diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt
index b04a756..7749f10 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt
@@ -29,7 +29,7 @@ import androidx.compose.ui.unit.dp
import net.taler.common.Amount
import net.taler.common.Timestamp
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
+import net.taler.common.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.compose.QrCodeUriComposable
import net.taler.wallet.transactions.AmountType
diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt
index aa12a8e..3240d3d 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt
@@ -23,7 +23,7 @@ import androidx.compose.ui.tooling.preview.Preview
import net.taler.common.Amount
import net.taler.common.Timestamp
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
+import net.taler.common.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.transactions.AmountType
import net.taler.wallet.transactions.PeerInfoShort
diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt
index 2c1c24c..75346c5 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt
@@ -23,7 +23,7 @@ import androidx.compose.ui.tooling.preview.Preview
import net.taler.common.Amount
import net.taler.common.Timestamp
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
+import net.taler.common.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.transactions.AmountType
import net.taler.wallet.transactions.PeerInfoShort
diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt
index 2587ea9..dcc9811 100644
--- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt
+++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt
@@ -29,7 +29,7 @@ import androidx.compose.ui.unit.dp
import net.taler.common.Amount
import net.taler.common.Timestamp
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
+import net.taler.common.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.compose.QrCodeUriComposable
import net.taler.wallet.transactions.AmountType
diff --git a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
index f5079f6..03c27da 100644
--- a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
@@ -22,7 +22,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.serialization.json.jsonArray
import net.taler.wallet.TAG
-import net.taler.wallet.backend.ApiResponse
+import net.taler.common.ApiResponse
import net.taler.wallet.backend.WalletBackendApi
import org.json.JSONObject
diff --git a/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt b/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt
index ac5e2da..1580502 100644
--- a/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt
@@ -34,7 +34,7 @@ import net.taler.common.Amount
import net.taler.common.Timestamp
import net.taler.common.toAbsoluteTime
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode
+import net.taler.common.TalerErrorCode
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.compose.TalerSurface
import net.taler.wallet.transactions.AmountType
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/ActionButtonComposable.kt b/wallet/src/main/java/net/taler/wallet/transactions/ActionButtonComposable.kt
index d4c12aa..cd43c7b 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/ActionButtonComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/ActionButtonComposable.kt
@@ -28,7 +28,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode
+import net.taler.common.TalerErrorCode
import net.taler.wallet.transactions.WithdrawalDetails.ManualTransfer
import net.taler.wallet.transactions.WithdrawalDetails.TalerBankIntegrationApi
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt
index da4b14d..22ef503 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt
@@ -41,7 +41,7 @@ import net.taler.common.Amount
import net.taler.common.Timestamp
import net.taler.common.toAbsoluteTime
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode
+import net.taler.common.TalerErrorCode
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.compose.TalerSurface
import net.taler.wallet.transactions.TransactionAction.Abort
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionTipFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionTipFragment.kt
index c15f931..29793e7 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionTipFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionTipFragment.kt
@@ -41,7 +41,7 @@ import net.taler.common.Amount
import net.taler.common.Timestamp
import net.taler.common.toAbsoluteTime
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
+import net.taler.common.TalerErrorCode.EXCHANGE_GENERIC_KYC_REQUIRED
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.compose.TalerSurface
import net.taler.wallet.transactions.TransactionAction.Abort
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
index 6b1d135..91daf07 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
@@ -39,7 +39,7 @@ import net.taler.common.ContractProduct
import net.taler.common.Timestamp
import net.taler.wallet.R
import net.taler.wallet.TAG
-import net.taler.wallet.backend.TalerErrorCode
+import net.taler.common.TalerErrorCode
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.cleanExchange
import net.taler.wallet.refund.RefundPaymentInfo
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt b/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt
index 79cfc5e..1ba5e90 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt
@@ -35,7 +35,7 @@ import net.taler.common.Amount
import net.taler.common.Timestamp
import net.taler.common.toAbsoluteTime
import net.taler.wallet.R
-import net.taler.wallet.backend.TalerErrorCode
+import net.taler.common.TalerErrorCode
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.cleanExchange
import net.taler.wallet.transactions.ActionButton