diff options
author | Iván Ávalos <avalos@disroot.org> | 2023-07-27 23:19:20 -0600 |
---|---|---|
committer | Iván Ávalos <avalos@disroot.org> | 2023-11-11 13:20:09 -0600 |
commit | 22da595621eb544c35629324a99973df1ac3b3de (patch) | |
tree | 7cdda768ceef8ca6cd8fc75f6ae7082b4481142c | |
parent | b072712019bfe3a423bac4d75f032306b2814e07 (diff) | |
download | taler-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>
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 |