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 } Component { id: swapDialog SwapDialog { onClosed: { swaphelper.destroy() destroy() } } } Component { id: swapProgressDialog SwapProgressDialog { onClosed: destroy() } } 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.accepted.connect(function() { __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.accepted.connect(function() { 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() } } function startSwap() { var swaphelper = app.swaphelper.createObject(app) var swapdialog = swapDialog.createObject(app, { swaphelper: swaphelper }) swaphelper.swapStarted.connect(function() { swapdialog.close() var progressdialog = swapProgressDialog.createObject(app, { swaphelper: swaphelper }) progressdialog.open() }) swapdialog.open() } property var _lastActive: 0 // record time of last activity property bool _lockDialogShown: false }