You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
580 lines
17 KiB
580 lines
17 KiB
import QtQuick 2.6 |
|
import QtQuick.Layouts 1.0 |
|
import QtQuick.Controls 2.3 |
|
import QtQuick.Controls.Material 2.0 |
|
import QtQuick.Controls.Material.impl 2.12 |
|
|
|
import QtQml 2.6 |
|
import QtMultimedia 5.6 |
|
import QtQuick.VirtualKeyboard 2.15 |
|
|
|
import org.electrum 1.0 |
|
|
|
import "controls" |
|
|
|
ApplicationWindow |
|
{ |
|
id: app |
|
|
|
visible: false // initial value |
|
|
|
// 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 Item constants: appconstants |
|
Constants { id: appconstants } |
|
|
|
property alias stack: mainStackView |
|
property alias inputPanel: inputPanel |
|
|
|
property variant activeDialogs: [] |
|
|
|
property bool _wantClose: false |
|
property var _exceptionDialog |
|
|
|
property QtObject appMenu: Menu { |
|
parent: Overlay.overlay |
|
dim: true |
|
modal: true |
|
Overlay.modal: Rectangle { |
|
color: "#44000000" |
|
} |
|
|
|
id: menu |
|
|
|
MenuItem { |
|
icon.color: 'transparent' |
|
action: Action { |
|
text: qsTr('Network') |
|
onTriggered: menu.openPage(Qt.resolvedUrl('NetworkOverview.qml')) |
|
icon.source: '../../icons/network.png' |
|
} |
|
} |
|
|
|
MenuItem { |
|
icon.color: 'transparent' |
|
action: Action { |
|
text: qsTr('Preferences'); |
|
onTriggered: menu.openPage(Qt.resolvedUrl('Preferences.qml')) |
|
icon.source: '../../icons/preferences.png' |
|
} |
|
} |
|
|
|
MenuItem { |
|
icon.color: 'transparent' |
|
action: Action { |
|
text: qsTr('About'); |
|
onTriggered: menu.openPage(Qt.resolvedUrl('About.qml')) |
|
icon.source: '../../icons/electrum.png' |
|
} |
|
} |
|
|
|
function openPage(url) { |
|
stack.pushOnRoot(url) |
|
currentIndex = -1 |
|
} |
|
} |
|
|
|
function openAppMenu() { |
|
appMenu.open() |
|
appMenu.x = app.width - appMenu.width |
|
appMenu.y = toolbar.height |
|
} |
|
|
|
header: ToolBar { |
|
id: toolbar |
|
|
|
background: Rectangle { |
|
implicitHeight: 48 |
|
color: Material.dialogColor |
|
|
|
layer.enabled: true |
|
layer.effect: ElevationEffect { |
|
elevation: 4 |
|
fullWidth: true |
|
} |
|
} |
|
|
|
ColumnLayout { |
|
spacing: 0 |
|
width: parent.width |
|
height: toolbar.height |
|
|
|
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 |
|
onClicked: { |
|
stack.getRoot().menu.open() // open wallet-menu |
|
stack.getRoot().menu.y = toolbar.height |
|
} |
|
} |
|
|
|
RowLayout { |
|
|
|
Item { |
|
Layout.preferredWidth: constants.paddingXLarge |
|
Layout.preferredHeight: 1 |
|
} |
|
|
|
Image { |
|
Layout.preferredWidth: constants.iconSizeSmall |
|
Layout.preferredHeight: constants.iconSizeSmall |
|
visible: Daemon.currentWallet && (!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.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 |
|
} |
|
} |
|
} |
|
|
|
Image { |
|
Layout.preferredWidth: constants.iconSizeSmall |
|
Layout.preferredHeight: constants.iconSizeSmall |
|
visible: Daemon.currentWallet && Daemon.currentWallet.isWatchOnly |
|
source: '../../icons/eye1.png' |
|
scale: 1.5 |
|
} |
|
|
|
LightningNetworkStatusIndicator {} |
|
OnchainNetworkStatusIndicator {} |
|
} |
|
} |
|
} |
|
|
|
WalletSummary { |
|
id: walletSummary |
|
Layout.preferredWidth: app.width |
|
} |
|
} |
|
} |
|
|
|
StackView { |
|
id: mainStackView |
|
// anchors.fill: parent |
|
width: parent.width |
|
height: inputPanel.y - header.height |
|
initialItem: Qt.resolvedUrl('WalletMainView.qml') |
|
|
|
function getRoot() { |
|
return mainStackView.get(0) |
|
} |
|
function pushOnRoot(item) { |
|
if (mainStackView.depth > 1) { |
|
mainStackView.replace(mainStackView.get(1), item) |
|
} else { |
|
mainStackView.push(item) |
|
} |
|
} |
|
} |
|
|
|
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 { |
|
// 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 ElDialog.resizeWithKeyboard property |
|
parent: Overlay.overlay |
|
width: parent.width |
|
height: inputPanel.y |
|
} |
|
|
|
InputPanel { |
|
id: inputPanel |
|
width: parent.width |
|
y: parent.height |
|
|
|
states: State { |
|
name: "visible" |
|
when: inputPanel.active |
|
PropertyChanges { |
|
target: inputPanel |
|
y: parent.height - height |
|
} |
|
} |
|
transitions: Transition { |
|
from: '' |
|
to: 'visible' |
|
reversible: true |
|
ParallelAnimation { |
|
NumberAnimation { |
|
properties: "y" |
|
duration: 250 |
|
easing.type: Easing.OutQuad |
|
} |
|
} |
|
} |
|
} |
|
|
|
property alias newWalletWizard: _newWalletWizard |
|
Component { |
|
id: _newWalletWizard |
|
NewWalletWizard { } |
|
} |
|
|
|
property alias serverConnectWizard: _serverConnectWizard |
|
Component { |
|
id: _serverConnectWizard |
|
ServerConnectWizard { } |
|
} |
|
|
|
property alias messageDialog: _messageDialog |
|
Component { |
|
id: _messageDialog |
|
MessageDialog { |
|
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 alias channelOpenProgressDialog: _channelOpenProgressDialog |
|
ChannelOpenProgressDialog { |
|
id: _channelOpenProgressDialog |
|
} |
|
|
|
NotificationPopup { |
|
id: notificationPopup |
|
width: parent.width |
|
} |
|
|
|
Component { |
|
id: crashDialog |
|
ExceptionDialog { |
|
z: 1000 |
|
} |
|
} |
|
|
|
property alias swaphelper: _swaphelper |
|
Component { |
|
id: _swaphelper |
|
SwapHelper { |
|
id: __swaphelper |
|
wallet: Daemon.currentWallet |
|
onConfirm: { |
|
var dialog = app.messageDialog.createObject(app, {text: message, yesno: true}) |
|
dialog.yesClicked.connect(function() { |
|
dialog.close() |
|
__swaphelper.executeSwap(true) |
|
}) |
|
dialog.open() |
|
} |
|
onAuthRequired: { |
|
app.handleAuthRequired(__swaphelper, method) |
|
} |
|
onError: { |
|
var dialog = app.messageDialog.createObject(app, { text: message }) |
|
dialog.open() |
|
} |
|
} |
|
} |
|
|
|
Component.onCompleted: { |
|
coverTimer.start() |
|
|
|
if (!Config.autoConnectDefined) { |
|
var dialog = serverConnectWizard.createObject(app) |
|
// without completed serverConnectWizard we can't start |
|
dialog.rejected.connect(function() { |
|
app.visible = false |
|
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.load_wallet(newww.path, newww.wizard_data['password']) |
|
}) |
|
newww.open() |
|
}) |
|
dialog.open() |
|
} else { |
|
Daemon.startNetwork() |
|
if (Daemon.availableWallets.rowCount() > 0) { |
|
Daemon.load_wallet() |
|
} else { |
|
var newww = app.newWalletWizard.createObject(app) |
|
newww.walletCreated.connect(function() { |
|
Daemon.availableWallets.reload() |
|
// and load the new wallet |
|
Daemon.load_wallet(newww.path, newww.wizard_data['password']) |
|
}) |
|
newww.open() |
|
} |
|
} |
|
} |
|
|
|
onClosing: { |
|
if (activeDialogs.length > 0) { |
|
var activeDialog = activeDialogs[activeDialogs.length - 1] |
|
if (activeDialog.allowClose) { |
|
activeDialog.doClose() |
|
} else { |
|
console.log('dialog disallowed close') |
|
} |
|
close.accepted = false |
|
return |
|
} |
|
if (stack.depth > 1) { |
|
close.accepted = false |
|
stack.pop() |
|
} else { |
|
// destroy most GUI components so that we don't dump so many null reference warnings on exit |
|
if (app._wantClose) { |
|
app.header.visible = false |
|
mainStackView.clear() |
|
} else { |
|
var dialog = app.messageDialog.createObject(app, { |
|
text: qsTr('Close Electrum?'), |
|
yesno: true |
|
}) |
|
dialog.yesClicked.connect(function() { |
|
dialog.close() |
|
app._wantClose = true |
|
app.close() |
|
}) |
|
dialog.open() |
|
close.accepted = false |
|
} |
|
} |
|
} |
|
|
|
Connections { |
|
target: Daemon |
|
function onWalletRequiresPassword(name, path) { |
|
console.log('wallet requires password') |
|
var dialog = openWalletDialog.createObject(app, { path: path, name: name }) |
|
dialog.open() |
|
} |
|
function onWalletOpenError(error) { |
|
console.log('wallet open error') |
|
var dialog = app.messageDialog.createObject(app, {'text': error}) |
|
dialog.open() |
|
} |
|
function onAuthRequired(method) { |
|
handleAuthRequired(Daemon, method) |
|
} |
|
function onLoadingChanged() { |
|
if (!Daemon.loading) |
|
return |
|
console.log('wallet loading') |
|
var dialog = loadingWalletDialog.createObject(app, { allowClose: false } ) |
|
dialog.open() |
|
} |
|
} |
|
|
|
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() |
|
} |
|
} |
|
|
|
Connections { |
|
target: Daemon.currentWallet |
|
function onAuthRequired(method) { |
|
handleAuthRequired(Daemon.currentWallet, method) |
|
} |
|
// 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) { |
|
handleAuthRequired(Config, method) |
|
} |
|
} |
|
|
|
function handleAuthRequired(qtobject, method) { |
|
console.log('auth using method ' + method) |
|
if (method == 'wallet') { |
|
if (Daemon.currentWallet.verify_password('')) { |
|
// wallet has no password |
|
qtobject.authProceed() |
|
} else { |
|
var dialog = app.passwordDialog.createObject(app, {'title': qsTr('Enter current password')}) |
|
dialog.accepted.connect(function() { |
|
if (Daemon.currentWallet.verify_password(dialog.password)) { |
|
qtobject.authProceed() |
|
} else { |
|
qtobject.authCancel() |
|
} |
|
}) |
|
dialog.rejected.connect(function() { |
|
qtobject.authCancel() |
|
}) |
|
dialog.open() |
|
} |
|
} else if (method == 'pin') { |
|
if (Config.pinCode == '') { |
|
// no PIN configured |
|
qtobject.authProceed() |
|
} else { |
|
var dialog = app.pinDialog.createObject(app, {mode: 'check', pincode: Config.pinCode}) |
|
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() |
|
} |
|
} |
|
|
|
property var _lastActive: 0 // record time of last activity |
|
property bool _lockDialogShown: false |
|
|
|
}
|
|
|