qml: show wallet list as root if no wallet is loaded
What changed, and why it matters
This commit is a user-interface cleanup for Electrum's mobile/QML app. It changes the app so that when no wallet is open, the user sees a wallet list screen instead of a 'no wallet loaded' placeholder inside the main wallet view. It also moves handling of incoming payment links (URI intents) from the wallet view to the top-level app so the link is not lost if a wallet isn't open yet. There is no security fix here.
No security action needed; review as normal UI refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors QML navigation: main.qml now starts at Wallets.qml instead of WalletMainView.qml, and WalletMainView.qml assumes Daemon.currentWallet is always non-null when shown. It removes the no-wallet placeholder from WalletMainView, adds a replaceRoot helper, and routes pending BIP21/payment URIs through a new app.pendingIntent property so they survive the wallet-selection flow. The only backend change is a slightly more informative FileNotFoundError message in qedaemon.py. No vulnerability is addressed.
Changed components
electrum/gui/qml/components/WalletMainView.qmlelectrum/gui/qml/components/Wallets.qmlelectrum/gui/qml/components/main.qmlelectrum/gui/qml/qedaemon.pyInspect captured patch +39 / −69
diff --git a/electrum/gui/qml/components/WalletMainView.qml b/electrum/gui/qml/components/WalletMainView.qml
index a9755ac..c39cda5 100644
--- a/electrum/gui/qml/components/WalletMainView.qml
+++ b/electrum/gui/qml/components/WalletMainView.qml
@@ -11,10 +11,9 @@ import "controls"
Item {
id: mainView
- property string title: Daemon.currentWallet ? Daemon.currentWallet.name : qsTr('no wallet loaded')
+ property string title: Daemon.currentWallet.name
property var _sendDialog
- property string _intentUri
property string _request_amount
property string _request_description
@@ -188,7 +187,7 @@ Item {
icon.source: '../../icons/wallet.png'
action: Action {
text: qsTr('Wallet details')
- enabled: Daemon.currentWallet && app.stack.currentItem.objectName != 'WalletDetails'
+ enabled: app.stack.currentItem.objectName != 'WalletDetails'
onTriggered: menu.openPage(Qt.resolvedUrl('WalletDetails.qml'))
}
}
@@ -198,7 +197,7 @@ Item {
action: Action {
text: qsTr('Addresses/Coins');
onTriggered: menu.openPage(Qt.resolvedUrl('Addresses.qml'));
- enabled: Daemon.currentWallet && app.stack.currentItem.objectName != 'Addresses'
+ enabled: app.stack.currentItem.objectName != 'Addresses'
}
}
MenuItem {
@@ -206,7 +205,7 @@ Item {
icon.source: '../../icons/lightning.png'
action: Action {
text: qsTr('Channels');
- enabled: Daemon.currentWallet && Daemon.currentWallet.isLightning && app.stack.currentItem.objectName != 'Channels'
+ enabled: Daemon.currentWallet.isLightning && app.stack.currentItem.objectName != 'Channels'
onTriggered: menu.openPage(Qt.resolvedUrl('Channels.qml'))
}
}
@@ -285,62 +284,16 @@ Item {
History {
id: history
- visible: Daemon.currentWallet
Layout.fillWidth: true
Layout.fillHeight: true
}
- ColumnLayout {
- Layout.alignment: Qt.AlignHCenter
- Layout.fillHeight: true
- spacing: 2*constants.paddingXLarge
- visible: !Daemon.currentWallet
-
- Item {
- Layout.fillHeight: true
- }
- Label {
- Layout.alignment: Qt.AlignHCenter
- text: qsTr('No wallet loaded')
- font.pixelSize: constants.fontSizeXXLarge
- }
-
- Pane {
- Layout.alignment: Qt.AlignHCenter
- padding: 0
- background: Rectangle {
- color: Material.dialogColor
- }
- FlatButton {
- text: qsTr('Open/Create Wallet')
- icon.source: '../../icons/wallet.png'
- onClicked: {
- if (Daemon.availableWallets.rowCount() > 0) {
- stack.push(Qt.resolvedUrl('Wallets.qml'))
- } else {
- var newww = app.newWalletWizard.createObject(app)
- newww.walletCreated.connect(function() {
- Daemon.availableWallets.reload()
- // and load the new wallet
- Daemon.loadWallet(newww.path, newww.wizard_data['password'])
- })
- newww.open()
- }
- }
- }
- }
- Item {
- Layout.fillHeight: true
- }
- }
-
ButtonContainer {
id: buttonContainer
Layout.fillWidth: true
FlatButton {
id: receiveButton
- visible: Daemon.currentWallet
Layout.fillWidth: true
Layout.preferredWidth: 1
icon.source: '../../icons/tab_receive.png'
@@ -357,7 +310,6 @@ Item {
}
}
FlatButton {
- visible: Daemon.currentWallet
Layout.fillWidth: true
Layout.preferredWidth: 1
icon.source: '../../icons/tab_send.png'
@@ -489,25 +441,22 @@ Item {
}
Connections {
- target: AppController
- function onUriReceived(uri) {
- console.log('uri received: ' + uri)
- if (!Daemon.currentWallet) {
- console.log('No wallet open, deferring')
- _intentUri = uri
+ target: Daemon
+ function onWalletLoaded() {
+ if (!Daemon.currentWallet) { // wallet got deleted
+ app.stack.replaceRoot('Wallets.qml')
return
}
- piResolver.recipient = uri
+ infobanner.hide() // start hidden when switching wallets
}
}
Connections {
- target: Daemon
- function onWalletLoaded() {
- infobanner.hide() // start hidden when switching wallets
- if (_intentUri) {
- piResolver.recipient = _intentUri
- _intentUri = ''
+ target: app
+ function onPendingIntentChanged() {
+ if (app.pendingIntent) {
+ piResolver.recipient = app.pendingIntent
+ app.pendingIntent = ""
}
}
}
@@ -819,5 +768,12 @@ Item {
}
}
+ Component.onCompleted: {
+ console.log("WalletMainView completed: ", Daemon.currentWallet.name)
+ if (app.pendingIntent) {
+ piResolver.recipient = app.pendingIntent
+ app.pendingIntent = ""
+ }
+ }
}
diff --git a/electrum/gui/qml/components/Wallets.qml b/electrum/gui/qml/components/Wallets.qml
index f9c9f84..b38fdf7 100644
--- a/electrum/gui/qml/components/Wallets.qml
+++ b/electrum/gui/qml/components/Wallets.qml
@@ -143,7 +143,11 @@ Pane {
target: Daemon
function onWalletLoaded() {
if (app.stack.currentItem.objectName == 'Wallets')
- app.stack.pop()
+ if (app.stack.getRoot().objectName == 'Wallets') {
+ app.stack.replaceRoot('WalletMainView.qml')
+ } else {
+ app.stack.pop()
+ }
}
}
diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml
index cfdbf4e..bf0b0c9 100644
--- a/electrum/gui/qml/components/main.qml
+++ b/electrum/gui/qml/components/main.qml
@@ -38,6 +38,8 @@ ApplicationWindow
property alias keyboardFreeZone: _keyboardFreeZone
property alias infobanner: _infobanner
+ property string pendingIntent: ""
+
property variant activeDialogs: []
property var _exceptionDialog
@@ -120,7 +122,7 @@ ApplicationWindow
header: ToolBar {
id: toolbar
-
+
// Add top margin for status bar on Android when using edge-to-edge
topPadding: app.statusBarHeight
@@ -269,7 +271,7 @@ ApplicationWindow
Layout.fillWidth: true
initialItem: Component {
- WalletMainView {}
+ Wallets {}
}
function getRoot() {
@@ -282,6 +284,10 @@ ApplicationWindow
mainStackView.push(item)
}
}
+ function replaceRoot(item_url) {
+ mainStackView.clear()
+ mainStackView.push(Qt.resolvedUrl(item_url))
+ }
}
// Add bottom padding for navigation bar on Android when UI is edge-to-edge
@@ -698,6 +704,10 @@ ApplicationWindow
if (obj != null)
app.pluginobjects[name] = obj
}
+ function onUriReceived(uri) {
+ console.log('uri received (main): ' + uri)
+ app.pendingIntent = uri
+ }
}
function pluginsComponentsByName(comp_name) {
diff --git a/electrum/gui/qml/qedaemon.py b/electrum/gui/qml/qedaemon.py
index 72e618b..acbe1a7 100644
--- a/electrum/gui/qml/qedaemon.py
+++ b/electrum/gui/qml/qedaemon.py
@@ -219,7 +219,7 @@ class QEDaemon(AuthMixin, QObject):
except InvalidPassword:
self.walletRequiresPassword.emit(self._name, self._path)
except FileNotFoundError:
- self.walletOpenError.emit(_('File not found'))
+ self.walletOpenError.emit(_('File not found') + f":\n{self._path}")
except StorageReadWriteError:
self.walletOpenError.emit(_('Could not read/write file'))
except WalletFileException as e:
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.