commit 8e6aaa36486b30b17d586c02f15bf4fb1beb73e1
parent b292f85b3970fcd8bf9339944d075d5c62f65bd0
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date: Sun, 18 Oct 2026 18:59:52 +0200
Improve UX for alias registration flows that were completed out of band
Diffstat:
3 files changed, 115 insertions(+), 5 deletions(-)
diff --git a/wallet/src/main/java/net/taler/wallet/contacts/AliasRegistrationScreen.kt b/wallet/src/main/java/net/taler/wallet/contacts/AliasRegistrationScreen.kt
@@ -17,8 +17,10 @@
package net.taler.wallet.contacts
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
@@ -79,6 +81,12 @@ fun AliasRegistrationScreen(
},
onComplete = { alias, aliasType, taldirBaseUri, targetUri, solution ->
model.contactManager.completeTaldirRegister(alias, aliasType, taldirBaseUri, targetUri, solution)
+ },
+ onRecover = { alias, aliasType, taldirBaseUri, targetUri ->
+ model.contactManager.recoverRegistration(alias, aliasType, taldirBaseUri, targetUri)
+ },
+ onDelete = { alias, aliasType, taldirBaseUri, durationMonths ->
+ model.contactManager.taldirDeleteAlias(alias, aliasType, taldirBaseUri, durationMonths)
}
)
}
@@ -91,6 +99,8 @@ fun AliasRegistrationContent(
onNavigateBack: () -> Unit,
onRegister: (String, String, String, String, Int) -> Unit,
onComplete: (String, String, String, String, String) -> Unit,
+ onRecover: (String, String, String, String) -> Unit,
+ onDelete: (String, String, String, Int) -> Unit,
) {
var aliasType by remember { mutableStateOf("email") }
var aliasTypeExpanded by remember { mutableStateOf(false) }
@@ -213,6 +223,23 @@ fun AliasRegistrationContent(
) {
Text(stringResource(R.string.alias_registration_button))
}
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Button(
+ onClick = {
+ onDelete(
+ alias,
+ aliasType,
+ taldirBaseUri,
+ durationMonths.toIntOrNull() ?: 12
+ )
+ },
+ enabled = aliasType.isNotBlank() && alias.isNotBlank() && registerResult !is RegisterResult.Loading,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(stringResource(R.string.alias_registration_delete))
+ }
}
when (val res = registerResult) {
@@ -258,6 +285,23 @@ fun AliasRegistrationContent(
) {
Text(stringResource(R.string.ok))
}
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Button(
+ onClick = {
+ onRecover(
+ alias,
+ aliasType,
+ taldirBaseUri,
+ res.targetUri
+ )
+ },
+ enabled = alias.isNotBlank(),
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(stringResource(R.string.alias_registration_out_of_band))
+ }
}
}
is RegisterResult.CompletionLoading -> {
@@ -308,7 +352,9 @@ fun AliasRegistrationContentPreview() {
registerResult = RegisterResult.Idle,
onNavigateBack = {},
onRegister = { _, _, _, _, _ -> },
- onComplete = { _, _, _, _, _ -> }
+ onComplete = { _, _, _, _, _ -> },
+ onRecover = { _, _, _, _ -> },
+ onDelete = { _, _, _, _ -> }
)
}
}
@@ -328,10 +374,12 @@ fun AliasRegistrationContentSuccessPreview() {
),
emptyList()
),
- registerResult = RegisterResult.Success(validFor = null),
+ registerResult = RegisterResult.Success(validFor = null, targetUri = "https://mailbox.taler.net/alice"),
onNavigateBack = {},
onRegister = { _, _, _, _, _ -> },
- onComplete = { _, _, _, _, _ -> }
+ onComplete = { _, _, _, _, _ -> },
+ onRecover = { _, _, _, _ -> },
+ onDelete = { _, _, _, _ -> }
)
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/contacts/ContactManager.kt b/wallet/src/main/java/net/taler/wallet/contacts/ContactManager.kt
@@ -82,7 +82,7 @@ sealed class RegisterResult {
data object Idle : RegisterResult()
data object Loading : RegisterResult()
data class Error(val error: TalerErrorInfo) : RegisterResult()
- data class Success(val validFor: RelativeTime?) : RegisterResult()
+ data class Success(val validFor: RelativeTime?, val targetUri: String) : RegisterResult()
data object CompletionLoading : RegisterResult()
data object CompletionSuccess : RegisterResult()
}
@@ -208,7 +208,7 @@ class ContactManager(
put("duration", JSONObject(BackendManager.json.encodeToString(RelativeTime.serializer(), RelativeTime(durationUs))))
}.onSuccess {
Log.d(TAG, "Taldir registration initiated for $alias: $it")
- _registerResult.value = RegisterResult.Success(it.validFor)
+ _registerResult.value = RegisterResult.Success(it.validFor, targetUri)
}.onError {
Log.e(TAG, "Error initiating Taldir registration: $it")
_registerResult.value = RegisterResult.Error(it)
@@ -240,4 +240,64 @@ class ContactManager(
}
}
}
+
+ fun taldirDeleteAlias(
+ alias: String,
+ aliasType: String,
+ taldirBaseUri: String = "https://taldir.gnunet.org",
+ durationMonths: Int,
+ ) {
+ taldirRegister(
+ alias = alias,
+ aliasType = aliasType,
+ taldirBaseUri = taldirBaseUri,
+ targetUri = "",
+ durationMonths = durationMonths,
+ )
+ }
+
+ fun recoverRegistration(
+ alias: String,
+ aliasType: String,
+ taldirBaseUri: String = "https://taldir.gnunet.org",
+ expectedTargetUri: String,
+ ) {
+ _registerResult.value = RegisterResult.Loading
+ scope.launch {
+ api.request<LookupAliasResponse>("lookupAlias", LookupAliasResponse.serializer()) {
+ put("alias", alias)
+ put("aliasType", aliasType)
+ put("taldirBaseUrl", taldirBaseUri)
+ }.onSuccess { resp ->
+ val targetUri = resp.targetUri ?: ""
+ if (expectedTargetUri.isEmpty()) {
+ if (targetUri.isBlank()) {
+ _registerResult.value = RegisterResult.CompletionSuccess
+ } else {
+ _registerResult.value = RegisterResult.Error(TalerErrorInfo(
+ code = TalerErrorCode.UNKNOWN,
+ message = "Existing registration is not deleted (points to $targetUri)",
+ ))
+ }
+ } else {
+ if (targetUri.isNullOrBlank()) {
+ _registerResult.value = RegisterResult.Error(TalerErrorInfo(
+ code = TalerErrorCode.UNKNOWN,
+ message = "Alias not found",
+ ))
+ } else if (targetUri != expectedTargetUri) {
+ _registerResult.value = RegisterResult.Error(TalerErrorInfo(
+ code = TalerErrorCode.UNKNOWN,
+ message = "Existing registration points to a different mailbox URI",
+ ))
+ } else {
+ _registerResult.value = RegisterResult.CompletionSuccess
+ }
+ }
+ }.onError {
+ Log.e(TAG, "Error recovering registration: $it")
+ _registerResult.value = RegisterResult.Error(it)
+ }
+ }
+ }
}
diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml
@@ -511,6 +511,8 @@ GNU Taler is immune to many types of fraud such as credit card data theft, phish
<string name="alias_registration_solution">Challenge Solution</string>
<string name="alias_registration_success">Registration successful!</string>
<string name="alias_registration_title">Alias Registration</string>
+ <string name="alias_registration_delete">Delete Alias</string>
+ <string name="alias_registration_out_of_band">I have completed the registration out of band</string>
<string name="contact_add_button">Add Contact</string>
<string name="contact_add_intro">Do you want to add this contact to your wallet?</string>
<string name="contact_add_title">Add Contact</string>