View Issue Details

IDProjectCategoryView StatusLast Update
0012141Talerwallet (iOS App)public2026-11-15 15:47
Reporterbot-cg [agent]  
PrioritynormalSeveritymajor 
Status new 
Summary0012141: taler://add-exchange link ends on "Scanning..." forever: ExchangeListView clears URLSheet's passedURL and is replaced by the loading view (patch attached)
DescriptionOpening a taler://add-exchange/ link (QR code or link from another app) does
not offer to add the payment service. The sheet comes up, shows the list of
payment services for an instant, and then shows "Loading... / Scanning..."
with a Cancel button and stays there. The "Add payment service" prompt, which
is what the link is for, never becomes usable.

Steps (iOS 27.2 simulator, fresh wallet, current master):
1. Start the wallet; it shows "Welcome to Taler Wallet!".
2. Open taler://add-exchange/<host>/ (any exchange; here a test exchange).
3. A sheet opens. Expected: the payment-service list with the "Add payment
   service" alert up and the address prefilled. Actual: "Scanning..." (see
   add-exchange-stuck-scanning.jpg, taken 90 s after the link was opened).

The app log shows the link being recognised and the list view appearing again
and again, never settling:

    saveURL: addExchange
    16:22:14.460 switching from 10 to 72 ExchangeListView.swift:211
    16:22:56.418 72 stays ExchangeListView.swift:211
    16:23:38.531 72 stays ExchangeListView.swift:211

Cause. URLSheet shows the view for a link only while both of its state
variables are set, and falls back to the "Scanning..." view otherwise:

    if let urlCommand, let passedURL {
        switch urlCommand {
            case .addExchange:
                ExchangeListView(stack: stack.push(), url: $passedURL)
        ...
    } else {
        LoadingView(... message: "Scanning...")
            .task(id: urlToOpen) { passUrlOnce() }
    }

ExchangeListView receives URLSheet's passedURL as a binding and clears it as
soon as it has read it (ExchangeListView.swift, the .task on the list):

    if let newUrl = url {
        newExchange = newUrl.trimmedString
        showAlert = true
        url = nil // <- this is URLSheet.passedURL
    }

Clearing it makes URLSheet's condition false, so URLSheet removes the
ExchangeListView it has just shown -- together with the alert it was about to
present -- and puts the "Scanning..." view back. When the sheet was opened
from onOpenURL (sheetURL), the Scanning view's task parses the link again,
the list view is created again, clears the binding again, and so on.

Fix (add-exchange-uri-stuck-scanning.diff, attached): remember in the list
view's own state that the link has been consumed, and leave the binding
alone. The Settings entry passes .constant(nil) and is unaffected.

Found by the UI fuzz harness (scenario exchange-add, 2026-11-15); the
scenario waits 120 s for the "Add payment service" prompt and never sees it.
The patch is not yet build-verified.
TagsNo tags attached.
Attached Files
add-exchange-stuck-scanning.jpg (14,594 bytes)   
add-exchange-stuck-scanning.jpg (14,594 bytes)   
12141-add-exchange-uri-stuck-scanning.diff (1,405 bytes)   
diff --git a/TalerWallet1/Views/Settings/Exchange/ExchangeListView.swift b/TalerWallet1/Views/Settings/Exchange/ExchangeListView.swift
index 2017f245..3624d6c3 100644
--- a/TalerWallet1/Views/Settings/Exchange/ExchangeListView.swift
+++ b/TalerWallet1/Views/Settings/Exchange/ExchangeListView.swift
@@ -41,6 +41,7 @@ struct ExchangeListView: View {
 #endif
 
     @State private var showAlert: Bool = false
+    @State private var urlTaken: Bool = false
     @State private var newExchange: String = EXCHANGE + DEMO        // without HTTPS
 
     func checkExchanges() {
@@ -74,10 +75,12 @@ struct ExchangeListView: View {
                         .navigationTitle(TITLE_EXCHANGES)
                         .navigationBarItems(trailing: plusButton)
                         .task {
-                            if let newUrl = url {
+                            if let newUrl = url, !urlTaken {
+                                // Do not clear `url`: it is URLSheet's passedURL, and
+                                // clearing it replaces this view by "Scanning..." for good
+                                urlTaken = true
                                 newExchange = newUrl.trimmedString
                                 showAlert = true
-                                url = nil
                             } else {
                                 checkExchanges()
                             }

Required notifications

Address or linkRemarkAdded byNotifiedActions

Activities

Issue History

Date ModifiedUsernameFieldChange
2026-11-15 15:47bot-cgNew Issue
2026-11-15 15:47bot-cgCommunicator => CM_7
2026-11-15 15:47bot-cgFile Added: add-exchange-stuck-scanning.jpg
2026-11-15 15:47bot-cgFile Added: 12141-add-exchange-uri-stuck-scanning.diff