5dd3dda238
Allows to unlock the android app with the android biometric api (e.g. fingerprint). Can be enabled in the settings.
915 lines
29 KiB
QML
915 lines
29 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import QtQuick.Controls
|
|
import QtQuick.Controls.Basic
|
|
import QtQuick.Controls.Material
|
|
import QtQuick.Controls.Material.impl
|
|
import QtQuick.Window
|
|
|
|
import QtQml
|
|
import QtMultimedia
|
|
|
|
import org.electrum 1.0
|
|
|
|
import "controls"
|
|
|
|
ApplicationWindow
|
|
{
|
|
id: app
|
|
|
|
visible: false // initial value
|
|
|
|
readonly property int statusBarHeight: AppController ? AppController.getStatusBarHeight() : 0
|
|
readonly property int navigationBarHeight: AppController ? AppController.getNavigationBarHeight() : 0
|
|
|
|
// dimensions ignored on android
|
|
width: 480
|
|
height: 800
|
|
|
|
Material.theme: Material.Dark
|
|
Material.primary: Material.Indigo
|
|
Material.accent: Material.LightBlue
|
|
font.pixelSize: constants.fontSizeMedium
|
|
|
|
property QtObject constants: appconstants
|
|
Constants { id: appconstants }
|
|
|
|
property alias stack: mainStackView
|
|
property alias keyboardFreeZone: _keyboardFreeZone
|
|
property alias infobanner: _infobanner
|
|
|
|
property string pendingIntent: ""
|
|
|
|
property variant activeDialogs: []
|
|
|
|
property var _exceptionDialog
|
|
|
|
property var pluginobjects: ({})
|
|
|
|
property QtObject appMenu: Menu {
|
|
id: menu
|
|
|
|
parent: Overlay.overlay
|
|
dim: true
|
|
modal: true
|
|
Overlay.modal: Rectangle {
|
|
color: "#44000000"
|
|
}
|
|
|
|
property int implicitChildrenWidth: 64
|
|
width: implicitChildrenWidth + 60 + constants.paddingLarge
|
|
|
|
MenuItem {
|
|
icon.color: action.enabled ? 'transparent' : Material.iconDisabledColor
|
|
icon.source: '../../icons/network.png'
|
|
action: Action {
|
|
text: qsTr('Network')
|
|
onTriggered: menu.openPage(Qt.resolvedUrl('NetworkOverview.qml'))
|
|
enabled: stack.currentItem.objectName != 'NetworkOverview'
|
|
}
|
|
}
|
|
|
|
MenuItem {
|
|
icon.color: action.enabled ? 'transparent' : Material.iconDisabledColor
|
|
icon.source: '../../icons/preferences.png'
|
|
action: Action {
|
|
text: qsTr('Preferences')
|
|
onTriggered: menu.openPage(Qt.resolvedUrl('Preferences.qml'))
|
|
enabled: stack.currentItem.objectName != 'Properties'
|
|
}
|
|
}
|
|
|
|
MenuItem {
|
|
icon.color: action.enabled ? 'transparent' : Material.iconDisabledColor
|
|
icon.source: '../../icons/electrum.png'
|
|
action: Action {
|
|
text: qsTr('About');
|
|
onTriggered: menu.openPage(Qt.resolvedUrl('About.qml'))
|
|
enabled: stack.currentItem.objectName != 'About'
|
|
}
|
|
}
|
|
|
|
function openPage(url) {
|
|
stack.pushOnRoot(url)
|
|
currentIndex = -1
|
|
}
|
|
|
|
// determine widest element and store in implicitChildrenWidth
|
|
function updateImplicitWidth() {
|
|
for (let i = 0; i < menu.count; i++) {
|
|
var item = menu.itemAt(i)
|
|
var txt = item.text
|
|
var txtwidth = fontMetrics.advanceWidth(txt)
|
|
if (txtwidth > menu.implicitChildrenWidth) {
|
|
menu.implicitChildrenWidth = txtwidth
|
|
}
|
|
}
|
|
}
|
|
|
|
FontMetrics {
|
|
id: fontMetrics
|
|
font: menu.font
|
|
}
|
|
|
|
Component.onCompleted: updateImplicitWidth()
|
|
}
|
|
|
|
function openAppMenu() {
|
|
appMenu.open()
|
|
appMenu.x = app.width - appMenu.width
|
|
appMenu.y = toolbar.height
|
|
}
|
|
|
|
header: ToolBar {
|
|
id: toolbar
|
|
|
|
// Add top margin for status bar on Android when using edge-to-edge
|
|
topPadding: app.statusBarHeight
|
|
|
|
background: Rectangle {
|
|
implicitHeight: 48
|
|
color: Material.dialogColor
|
|
|
|
layer.enabled: true
|
|
layer.effect: ElevationEffect {
|
|
elevation: 4
|
|
fullWidth: true
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
spacing: 0
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
height: toolbar.availableHeight
|
|
|
|
RowLayout {
|
|
id: toolbarTopLayout
|
|
|
|
Layout.fillWidth: true
|
|
Layout.rightMargin: constants.paddingMedium
|
|
Layout.alignment: Qt.AlignVCenter
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: Math.max(implicitHeight, toolbarTopLayout.height)
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
enabled: Daemon.currentWallet &&
|
|
(!stack.currentItem || !stack.currentItem.title || stack.currentItem.title == Daemon.currentWallet.name)
|
|
onClicked: {
|
|
stack.getRoot().menu.open() // open wallet-menu
|
|
stack.getRoot().menu.y = toolbar.height
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
width: parent.width
|
|
|
|
Item {
|
|
Layout.preferredWidth: constants.paddingXLarge
|
|
Layout.preferredHeight: 1
|
|
}
|
|
|
|
Image {
|
|
Layout.preferredWidth: constants.iconSizeSmall
|
|
Layout.preferredHeight: constants.iconSizeSmall
|
|
visible: Daemon.currentWallet &&
|
|
(!stack.currentItem || !stack.currentItem.title || stack.currentItem.title == Daemon.currentWallet.name)
|
|
source: '../../icons/wallet.png'
|
|
}
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: Math.max(implicitHeight, toolbarTopLayout.height)
|
|
text: stack.currentItem && stack.currentItem.title
|
|
? stack.currentItem.title
|
|
: Daemon.currentWallet.name
|
|
elide: Label.ElideRight
|
|
verticalAlignment: Qt.AlignVCenter
|
|
font.pixelSize: constants.fontSizeMedium
|
|
font.bold: true
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
implicitHeight: 48
|
|
implicitWidth: statusIconsLayout.width
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: openAppMenu() // open global-app-menu
|
|
}
|
|
|
|
RowLayout {
|
|
id: statusIconsLayout
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
Item {
|
|
Layout.preferredWidth: constants.paddingLarge
|
|
Layout.preferredHeight: 1
|
|
}
|
|
|
|
Item {
|
|
visible: Network.isTestNet
|
|
width: column.width
|
|
height: column.height
|
|
|
|
ColumnLayout {
|
|
id: column
|
|
spacing: 0
|
|
Image {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: constants.iconSizeSmall
|
|
Layout.preferredHeight: constants.iconSizeSmall
|
|
source: "../../icons/info.png"
|
|
}
|
|
|
|
Label {
|
|
id: networkNameLabel
|
|
text: Network.networkName
|
|
color: Material.accentColor
|
|
font.pixelSize: constants.fontSizeXSmall
|
|
}
|
|
}
|
|
}
|
|
|
|
LightningNetworkStatusIndicator {
|
|
id: lnnsi
|
|
}
|
|
OnchainNetworkStatusIndicator { }
|
|
}
|
|
}
|
|
}
|
|
|
|
// hack to force relayout of toolbar
|
|
// since qt6 LightningNetworkStatusIndicator.visible doesn't trigger relayout(?)
|
|
Item {
|
|
Layout.preferredHeight: 1
|
|
Layout.topMargin: -1
|
|
Layout.preferredWidth: lnnsi.visible
|
|
? 1
|
|
: 2
|
|
}
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
width: parent.width
|
|
height: _keyboardFreeZone.height - header.height
|
|
spacing: 0
|
|
|
|
InfoBanner {
|
|
id: _infobanner
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
StackView {
|
|
id: mainStackView
|
|
Layout.fillHeight: true
|
|
Layout.fillWidth: true
|
|
|
|
initialItem: Component {
|
|
Wallets {}
|
|
}
|
|
|
|
function getRoot() {
|
|
return mainStackView.get(0)
|
|
}
|
|
function pushOnRoot(item) {
|
|
if (mainStackView.depth > 1) {
|
|
mainStackView.replace(mainStackView.get(1), item)
|
|
} else {
|
|
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
|
|
Item {
|
|
visible: app.navigationBarHeight > 0
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: app.navigationBarHeight
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: coverTimer
|
|
interval: 10
|
|
onTriggered: {
|
|
app.visible = true
|
|
cover.opacity = 0
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: cover
|
|
parent: Overlay.overlay
|
|
anchors.fill: parent
|
|
|
|
z: 1000
|
|
color: 'black'
|
|
|
|
Behavior on opacity {
|
|
enabled: AppController ? AppController.isAndroid() : false
|
|
NumberAnimation {
|
|
duration: 1000
|
|
easing.type: Easing.OutQuad;
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: _keyboardFreeZone
|
|
// Item as first child in Overlay that adjusts its size to the available
|
|
// screen space minus the virtual keyboard (e.g. to center dialogs in)
|
|
// see also ElDialog.resizeWithKeyboard property
|
|
parent: Overlay.overlay
|
|
width: parent.width
|
|
height: parent.height
|
|
|
|
states: [
|
|
State {
|
|
name: 'visible'
|
|
when: Qt.inputMethod.keyboardRectangle.y
|
|
PropertyChanges {
|
|
target: _keyboardFreeZone
|
|
height: _keyboardFreeZone.parent.height - (Screen.desktopAvailableHeight - (Qt.inputMethod.keyboardRectangle.y/Screen.devicePixelRatio))
|
|
}
|
|
}
|
|
]
|
|
|
|
transitions: [
|
|
Transition {
|
|
from: ''
|
|
to: 'visible'
|
|
NumberAnimation {
|
|
properties: 'height'
|
|
duration: 100
|
|
easing.type: Easing.OutQuad
|
|
}
|
|
},
|
|
Transition {
|
|
from: 'visible'
|
|
to: ''
|
|
SequentialAnimation {
|
|
PauseAnimation {
|
|
duration: 200
|
|
}
|
|
NumberAnimation {
|
|
properties: 'height'
|
|
duration: 50
|
|
easing.type: Easing.OutQuad
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
property alias newWalletWizard: _newWalletWizard
|
|
Component {
|
|
id: _newWalletWizard
|
|
NewWalletWizard {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
property alias termsOfUseWizard: _termsOfUseWizard
|
|
Component {
|
|
id: _termsOfUseWizard
|
|
TermsOfUseWizard {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
property alias serverConnectWizard: _serverConnectWizard
|
|
Component {
|
|
id: _serverConnectWizard
|
|
ServerConnectWizard {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
property alias messageDialog: _messageDialog
|
|
Component {
|
|
id: _messageDialog
|
|
MessageDialog {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
property alias helpDialog: _helpDialog
|
|
Component {
|
|
id: _helpDialog
|
|
HelpDialog {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
property alias passwordDialog: _passwordDialog
|
|
Component {
|
|
id: _passwordDialog
|
|
PasswordDialog {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
property alias pinDialog: _pinDialog
|
|
Component {
|
|
id: _pinDialog
|
|
Pin {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
property alias genericShareDialog: _genericShareDialog
|
|
Component {
|
|
id: _genericShareDialog
|
|
GenericShareDialog {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
property alias openWalletDialog: _openWalletDialog
|
|
Component {
|
|
id: _openWalletDialog
|
|
OpenWalletDialog {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
property alias loadingWalletDialog: _loadingWalletDialog
|
|
Component {
|
|
id: _loadingWalletDialog
|
|
LoadingWalletDialog {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
property Component scanDialog // set in Component.onCompleted
|
|
Component {
|
|
id: _scanDialog
|
|
QRScanner {
|
|
onFinished: destroy()
|
|
}
|
|
}
|
|
Component {
|
|
id: _qtScanDialog
|
|
ScanDialog {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: crashDialog
|
|
ExceptionDialog {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
property alias channelOpenProgressDialog: _channelOpenProgressDialog
|
|
ChannelOpenProgressDialog {
|
|
id: _channelOpenProgressDialog
|
|
}
|
|
|
|
property alias signVerifyMessageDialog: _signVerifyMessageDialog
|
|
Component {
|
|
id: _signVerifyMessageDialog
|
|
SignVerifyMessageDialog {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
property alias nostrSwapServersDialog: _nostrSwapServersDialog
|
|
Component {
|
|
id: _nostrSwapServersDialog
|
|
NostrSwapServersDialog {
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: swapDialog
|
|
SwapDialog {
|
|
id: _swapdialog
|
|
onClosed: destroy()
|
|
swaphelper: SwapHelper {
|
|
id: _swaphelper
|
|
wallet: Daemon.currentWallet
|
|
onAuthRequired: (method, authMessage) => {
|
|
app.handleAuthRequired(_swaphelper, method, authMessage)
|
|
}
|
|
onError: (message) => {
|
|
var dialog = app.messageDialog.createObject(app, {
|
|
title: qsTr('Error'),
|
|
iconSource: Qt.resolvedUrl('../../icons/warning.png'),
|
|
text: message
|
|
})
|
|
dialog.open()
|
|
}
|
|
onUndefinedNPub: {
|
|
var dialog = app.nostrSwapServersDialog.createObject(app, {
|
|
swaphelper: _swaphelper,
|
|
selectedPubkey: Config.swapServerNPub
|
|
})
|
|
dialog.accepted.connect(function() {
|
|
Config.swapServerNPub = dialog.selectedPubkey
|
|
_swaphelper.setReadyState()
|
|
})
|
|
dialog.rejected.connect(function() {
|
|
_swaphelper.npubSelectionCancelled()
|
|
})
|
|
dialog.open()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NotificationPopup {
|
|
id: notificationPopup
|
|
width: parent.width
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
coverTimer.start()
|
|
|
|
if (AppController.isAndroid()) {
|
|
app.scanDialog = _scanDialog
|
|
} else {
|
|
app.scanDialog = _qtScanDialog
|
|
}
|
|
|
|
function continueWithServerConnection() {
|
|
if (!Network.autoConnectDefined) {
|
|
var dialog = serverConnectWizard.createObject(app)
|
|
// without completed serverConnectWizard we can't start
|
|
dialog.rejected.connect(function() {
|
|
app.visible = false
|
|
AppController.wantClose = true
|
|
Qt.callLater(Qt.quit)
|
|
})
|
|
dialog.accepted.connect(function() {
|
|
Daemon.startNetwork()
|
|
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()
|
|
})
|
|
dialog.open()
|
|
} else {
|
|
Daemon.startNetwork()
|
|
if (Daemon.availableWallets.rowCount() > 0) {
|
|
Daemon.loadWallet()
|
|
} 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()
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Config.termsOfUseAccepted) {
|
|
var dialog = termsOfUseWizard.createObject(app)
|
|
|
|
dialog.rejected.connect(function() {
|
|
app.visible = false
|
|
AppController.wantClose = true
|
|
Qt.callLater(Qt.quit)
|
|
})
|
|
dialog.accepted.connect(function() {
|
|
Config.termsOfUseAccepted = true
|
|
continueWithServerConnection()
|
|
})
|
|
dialog.open()
|
|
} else {
|
|
continueWithServerConnection()
|
|
}
|
|
}
|
|
|
|
onClosing: (close) => {
|
|
if (AppController.wantClose) {
|
|
// destroy most GUI components so that we don't dump so many null reference warnings on exit
|
|
app.header.visible = false
|
|
mainStackView.clear()
|
|
return
|
|
}
|
|
if (activeDialogs.length > 0) {
|
|
var activeDialog = activeDialogs[activeDialogs.length - 1]
|
|
if (activeDialog.allowClose) {
|
|
console.log('main: dialog.doClose')
|
|
activeDialog.doClose()
|
|
} else {
|
|
console.log('dialog disallowed close')
|
|
}
|
|
close.accepted = false
|
|
return
|
|
}
|
|
if (stack.depth > 1) {
|
|
close.accepted = false
|
|
stack.pop()
|
|
} else {
|
|
var dialog = app.messageDialog.createObject(app, {
|
|
title: qsTr('Close Electrum?'),
|
|
yesno: true
|
|
})
|
|
dialog.accepted.connect(function() {
|
|
AppController.wantClose = true
|
|
app.close()
|
|
})
|
|
dialog.open()
|
|
close.accepted = false
|
|
}
|
|
}
|
|
|
|
property var _pendingBiometricAuth: null
|
|
property var _loadingWalletContext: null
|
|
|
|
Connections {
|
|
target: Biometrics
|
|
function onUnlockSuccess(password) {
|
|
if (_pendingBiometricAuth) {
|
|
if (_pendingBiometricAuth.action === 'load_wallet') {
|
|
_loadingWalletContext = _pendingBiometricAuth
|
|
Daemon.loadWallet(_pendingBiometricAuth.path, password)
|
|
_pendingBiometricAuth = null
|
|
return
|
|
}
|
|
|
|
var qtobject = _pendingBiometricAuth.qtobject
|
|
var method = _pendingBiometricAuth.method
|
|
|
|
if (Daemon.currentWallet.verifyPassword(password)) {
|
|
qtobject.authProceed()
|
|
} else {
|
|
console.log("Biometric password invalid falling back to manual input")
|
|
handleManualAuth(qtobject, method, _pendingBiometricAuth.authMessage)
|
|
}
|
|
_pendingBiometricAuth = null
|
|
}
|
|
}
|
|
function onUnlockError(error) {
|
|
console.log("Biometric auth failed: " + error)
|
|
// we end up here if QEBiometrics fails to give us the decrypted password. The user might
|
|
// have cancelled the biometric auth popup or the key got invalidated because a new fingerprint got registered.
|
|
if (_pendingBiometricAuth) {
|
|
// try manual auth
|
|
if (_pendingBiometricAuth.action === 'load_wallet') {
|
|
// set loadingWalletContext to disable biometric auth until the OpenWalletDialog is closed
|
|
_loadingWalletContext = _pendingBiometricAuth
|
|
showOpenWalletDialog(_pendingBiometricAuth.name, _pendingBiometricAuth.path)
|
|
} else {
|
|
handleManualAuth(_pendingBiometricAuth.qtobject, _pendingBiometricAuth.method, _pendingBiometricAuth.authMessage)
|
|
}
|
|
_pendingBiometricAuth = null
|
|
}
|
|
}
|
|
}
|
|
|
|
property var _opendialog: null
|
|
property var _opendialog_startup: true
|
|
|
|
function showOpenWalletDialog(name, path) {
|
|
if (!_opendialog) {
|
|
_opendialog = openWalletDialog.createObject(app, {
|
|
name: name,
|
|
path: path,
|
|
isStartup: _opendialog_startup,
|
|
})
|
|
_opendialog.closed.connect(function() {
|
|
_opendialog = null
|
|
_loadingWalletContext = null // dialog closed, we can allow trying biometric auth again
|
|
_opendialog_startup = false
|
|
})
|
|
_opendialog.open()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: Daemon
|
|
function onWalletRequiresPassword(name, path) {
|
|
console.log('wallet requires password')
|
|
if (Biometrics.isAvailable && Biometrics.isEnabled && !_loadingWalletContext) {
|
|
_pendingBiometricAuth = {
|
|
action: 'load_wallet',
|
|
name: name,
|
|
path: path
|
|
}
|
|
Biometrics.unlock()
|
|
} else {
|
|
showOpenWalletDialog(name, path)
|
|
}
|
|
}
|
|
function onWalletOpenError(error) {
|
|
console.log('wallet open error')
|
|
var dialog = app.messageDialog.createObject(app, {
|
|
title: qsTr('Error'),
|
|
iconSource: Qt.resolvedUrl('../../icons/warning.png'),
|
|
text: error
|
|
})
|
|
dialog.open()
|
|
}
|
|
function onAuthRequired(method, authMessage) {
|
|
handleAuthRequired(Daemon, method, authMessage)
|
|
}
|
|
function onLoadingChanged() {
|
|
if (!Daemon.loading)
|
|
return
|
|
console.log('wallet loading')
|
|
var dialog = loadingWalletDialog.createObject(app, { allowClose: false } )
|
|
dialog.open()
|
|
}
|
|
function onWalletLoaded() {
|
|
_loadingWalletContext = null // either biometric auth or manual auth was successful
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: AppController
|
|
function onUserNotify(wallet_name, message) {
|
|
notificationPopup.show(wallet_name, message)
|
|
}
|
|
function onShowException(crash_data) {
|
|
if (app._exceptionDialog)
|
|
return
|
|
app._exceptionDialog = crashDialog.createObject(app, {
|
|
crashData: crash_data
|
|
})
|
|
app._exceptionDialog.onClosed.connect(function() {
|
|
app._exceptionDialog = null
|
|
})
|
|
app._exceptionDialog.open()
|
|
}
|
|
function onPluginLoaded(name) {
|
|
console.log('plugin ' + name + ' loaded')
|
|
var loader = AppController.plugin(name).loader
|
|
if (loader == undefined)
|
|
return
|
|
var url = Qt.resolvedUrl('../../../plugins/' + name + '/qml/' + loader)
|
|
var comp = Qt.createComponent(url)
|
|
if (comp.status == Component.Error) {
|
|
console.log('Could not find/parse PluginLoader for plugin ' + name)
|
|
console.log(comp.errorString())
|
|
return
|
|
}
|
|
var obj = comp.createObject(app)
|
|
if (obj != null)
|
|
app.pluginobjects[name] = obj
|
|
}
|
|
function onUriReceived(uri) {
|
|
console.log('uri received (main): ' + uri)
|
|
app.pendingIntent = uri
|
|
}
|
|
}
|
|
|
|
function pluginsComponentsByName(comp_name) {
|
|
// return named QML components from plugins
|
|
var plugins = AppController.plugins
|
|
var result = []
|
|
for (var i=0; i < plugins.length; i++) {
|
|
if (!plugins[i].enabled)
|
|
continue
|
|
var pluginobject = app.pluginobjects[plugins[i].name]
|
|
if (!pluginobject)
|
|
continue
|
|
if (!(comp_name in pluginobject))
|
|
continue
|
|
var comp = pluginobject[comp_name]
|
|
if (!comp)
|
|
continue
|
|
|
|
result.push(comp)
|
|
}
|
|
return result
|
|
}
|
|
|
|
Connections {
|
|
target: Daemon.currentWallet
|
|
function onAuthRequired(method, authMessage) {
|
|
handleAuthRequired(Daemon.currentWallet, method, authMessage)
|
|
}
|
|
// TODO: add to notification queue instead of barging through
|
|
function onPaymentSucceeded(key) {
|
|
notificationPopup.show(Daemon.currentWallet.name, qsTr('Payment succeeded'))
|
|
}
|
|
function onPaymentFailed(key, reason) {
|
|
notificationPopup.show(Daemon.currentWallet.name, qsTr('Payment failed') + ': ' + reason)
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: Config
|
|
function onAuthRequired(method, authMessage) {
|
|
handleAuthRequired(Config, method, authMessage)
|
|
}
|
|
}
|
|
|
|
function handleAuthRequired(qtobject, method, authMessage) {
|
|
console.log('auth using method ' + method)
|
|
|
|
if (method == 'wallet_else_pin') {
|
|
// if there is a loaded wallet and all wallets use the same password, use that
|
|
// else delegate to pin auth
|
|
if (Daemon.currentWallet && Daemon.singlePasswordEnabled) {
|
|
method = 'wallet'
|
|
} else {
|
|
method = 'pin'
|
|
}
|
|
}
|
|
|
|
if (method === 'wallet') {
|
|
if (Daemon.currentWallet.verifyPassword('')) {
|
|
// wallet has no password
|
|
qtobject.authProceed()
|
|
return
|
|
}
|
|
} else if (method === 'pin') {
|
|
if (Config.pinCode === '') {
|
|
// no PIN configured
|
|
handleAuthConfirmationOnly(qtobject, authMessage)
|
|
return
|
|
}
|
|
}
|
|
|
|
if (Biometrics.isAvailable && Biometrics.isEnabled) {
|
|
_pendingBiometricAuth = { qtobject: qtobject, method: method, authMessage: authMessage }
|
|
Biometrics.unlock()
|
|
return
|
|
}
|
|
|
|
handleManualAuth(qtobject, method, authMessage)
|
|
}
|
|
|
|
function handleManualAuth(qtobject, method, authMessage) {
|
|
if (method == 'wallet') {
|
|
var dialog = app.passwordDialog.createObject(app, {'title': qsTr('Enter current password')})
|
|
dialog.accepted.connect(function() {
|
|
if (Daemon.currentWallet.verifyPassword(dialog.password)) {
|
|
qtobject.authProceed()
|
|
} else {
|
|
qtobject.authCancel()
|
|
}
|
|
})
|
|
dialog.rejected.connect(function() {
|
|
qtobject.authCancel()
|
|
})
|
|
dialog.open()
|
|
} else if (method == 'pin') {
|
|
var dialog = app.pinDialog.createObject(app, {
|
|
mode: 'check',
|
|
pincode: Config.pinCode,
|
|
authMessage: authMessage
|
|
})
|
|
dialog.accepted.connect(function() {
|
|
qtobject.authProceed()
|
|
dialog.close()
|
|
})
|
|
dialog.rejected.connect(function() {
|
|
qtobject.authCancel()
|
|
})
|
|
dialog.open()
|
|
} else {
|
|
console.log('unknown auth method ' + method)
|
|
qtobject.authCancel()
|
|
}
|
|
}
|
|
|
|
function handleAuthConfirmationOnly(qtobject, authMessage) {
|
|
if (!authMessage) {
|
|
qtobject.authProceed()
|
|
return
|
|
}
|
|
var dialog = app.messageDialog.createObject(app, {
|
|
title: authMessage,
|
|
yesno: true
|
|
})
|
|
dialog.accepted.connect(function() {
|
|
qtobject.authProceed()
|
|
})
|
|
dialog.rejected.connect(function() {
|
|
qtobject.authCancel()
|
|
})
|
|
dialog.open()
|
|
}
|
|
|
|
function startSwap() {
|
|
var swapdialog = swapDialog.createObject(app)
|
|
swapdialog.open()
|
|
}
|
|
|
|
property var _lastActive: 0 // record time of last activity
|
|
property bool _lockDialogShown: false
|
|
|
|
}
|