Browse Source

add initial Transaction Details page and backing qobject

master
Sander van Grieken 4 years ago
parent
commit
a6e72ae42f
  1. 2
      electrum/gui/qml/components/Constants.qml
  2. 8
      electrum/gui/qml/components/History.qml
  3. 257
      electrum/gui/qml/components/TxDetails.qml
  4. 2
      electrum/gui/qml/qeapp.py
  5. 11
      electrum/gui/qml/qetransactionlistmodel.py
  6. 143
      electrum/gui/qml/qetxdetails.py

2
electrum/gui/qml/components/Constants.qml

@ -3,6 +3,7 @@ import QtQuick.Controls.Material 2.0
Item { Item {
readonly property int paddingTiny: 4 readonly property int paddingTiny: 4
readonly property int paddingXSmall: 6
readonly property int paddingSmall: 8 readonly property int paddingSmall: 8
readonly property int paddingMedium: 12 readonly property int paddingMedium: 12
readonly property int paddingLarge: 16 readonly property int paddingLarge: 16
@ -25,4 +26,5 @@ Item {
property color colorCredit: "#ff80ff80" property color colorCredit: "#ff80ff80"
property color colorDebit: "#ffff8080" property color colorDebit: "#ffff8080"
property color mutedForeground: 'gray' //Qt.lighter(Material.background, 2) property color mutedForeground: 'gray' //Qt.lighter(Material.background, 2)
property color colorMine: "yellow"
} }

8
electrum/gui/qml/components/History.qml

@ -66,6 +66,14 @@ Pane {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: txinfo.height Layout.preferredHeight: txinfo.height
onClicked: {
var page = app.stack.push(Qt.resolvedUrl('TxDetails.qml'), {'txid': model.txid})
page.txDetailsChanged.connect(function() {
// update listmodel when details change
visualModel.model.update_tx_label(model.txid, page.label)
})
}
GridLayout { GridLayout {
id: txinfo id: txinfo
columns: 3 columns: 3

257
electrum/gui/qml/components/TxDetails.qml

@ -0,0 +1,257 @@
import QtQuick 2.6
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.3
import QtQuick.Controls.Material 2.0
import org.electrum 1.0
import "controls"
Pane {
id: root
width: parent.width
height: parent.height
property string title: qsTr("Transaction details")
property string txid
property alias label: txdetails.label
signal txDetailsChanged
property QtObject menu: Menu {
id: menu
MenuItem {
icon.color: 'transparent'
action: Action {
text: qsTr('Bump fee')
enabled: txdetails.canBump
//onTriggered:
}
}
MenuItem {
icon.color: 'transparent'
action: Action {
text: qsTr('Cancel double-spend')
enabled: txdetails.canCancel
}
}
}
Flickable {
anchors.fill: parent
contentHeight: rootLayout.height
clip: true
interactive: height < contentHeight
GridLayout {
id: rootLayout
width: parent.width
columns: 2
Label {
text: qsTr('Status')
color: Material.accentColor
}
Label {
text: txdetails.status
}
Label {
text: qsTr('Mempool depth')
color: Material.accentColor
visible: !txdetails.isMined
}
Label {
text: txdetails.mempoolDepth
visible: !txdetails.isMined
}
Label {
text: qsTr('Date')
color: Material.accentColor
}
Label {
text: txdetails.date
}
Label {
text: txdetails.amount.satsInt > 0
? qsTr('Amount received')
: qsTr('Amount sent')
color: Material.accentColor
}
RowLayout {
Label {
text: Config.formatSats(txdetails.amount)
}
Label {
text: Config.baseUnit
color: Material.accentColor
}
}
Label {
text: qsTr('Transaction fee')
color: Material.accentColor
}
RowLayout {
Label {
text: Config.formatSats(txdetails.fee)
}
Label {
text: Config.baseUnit
color: Material.accentColor
}
}
Label {
text: qsTr('Transaction ID')
Layout.columnSpan: 2
color: Material.accentColor
}
TextHighlightPane {
Layout.columnSpan: 2
Layout.fillWidth: true
padding: 0
leftPadding: constants.paddingSmall
RowLayout {
width: parent.width
Label {
text: root.txid
font.pixelSize: constants.fontSizeLarge
font.family: FixedFont
Layout.fillWidth: true
wrapMode: Text.Wrap
}
ToolButton {
icon.source: '../../icons/share.png'
icon.color: 'transparent'
onClicked: {
var dialog = share.createObject(root, { 'title': qsTr('Transaction ID'), 'text': root.txid })
dialog.open()
}
}
}
}
Label {
text: qsTr('Label')
Layout.columnSpan: 2
color: Material.accentColor
}
TextHighlightPane {
id: labelContent
property bool editmode: false
Layout.columnSpan: 2
Layout.fillWidth: true
padding: 0
leftPadding: constants.paddingSmall
RowLayout {
width: parent.width
Label {
visible: !labelContent.editmode
text: txdetails.label
wrapMode: Text.Wrap
Layout.fillWidth: true
font.pixelSize: constants.fontSizeLarge
}
ToolButton {
visible: !labelContent.editmode
icon.source: '../../icons/pen.png'
icon.color: 'transparent'
onClicked: {
labelEdit.text = txdetails.label
labelContent.editmode = true
}
}
TextField {
id: labelEdit
visible: labelContent.editmode
text: txdetails.label
font.pixelSize: constants.fontSizeLarge
Layout.fillWidth: true
}
ToolButton {
visible: labelContent.editmode
icon.source: '../../icons/confirmed.png'
icon.color: 'transparent'
onClicked: {
labelContent.editmode = false
txdetails.set_label(labelEdit.text)
}
}
ToolButton {
visible: labelContent.editmode
icon.source: '../../icons/delete.png'
icon.color: 'transparent'
onClicked: labelContent.editmode = false
}
}
}
Label {
text: qsTr('Outputs')
Layout.columnSpan: 2
color: Material.accentColor
}
Repeater {
model: txdetails.outputs
delegate: TextHighlightPane {
Layout.columnSpan: 2
Layout.fillWidth: true
padding: 0
leftPadding: constants.paddingSmall
RowLayout {
width: parent.width
Label {
text: modelData.address
Layout.fillWidth: true
wrapMode: Text.Wrap
font.pixelSize: constants.fontSizeLarge
font.family: FixedFont
color: modelData.is_mine ? constants.colorMine : Material.foreground
}
Label {
text: Config.formatSats(modelData.value)
font.pixelSize: constants.fontSizeLarge
}
Label {
text: Config.baseUnit
font.pixelSize: constants.fontSizeMedium
color: Material.accentColor
}
}
}
}
}
}
TxDetails {
id: txdetails
wallet: Daemon.currentWallet
txid: root.txid
onLabelChanged: txDetailsChanged()
}
Component {
id: share
GenericShareDialog {}
}
}

2
electrum/gui/qml/qeapp.py

@ -22,6 +22,7 @@ from .qetxfinalizer import QETxFinalizer
from .qeinvoice import QEInvoice from .qeinvoice import QEInvoice
from .qetypes import QEAmount from .qetypes import QEAmount
from .qeaddressdetails import QEAddressDetails from .qeaddressdetails import QEAddressDetails
from .qetxdetails import QETxDetails
notification = None notification = None
@ -120,6 +121,7 @@ class ElectrumQmlApplication(QGuiApplication):
qmlRegisterType(QETxFinalizer, 'org.electrum', 1, 0, 'TxFinalizer') qmlRegisterType(QETxFinalizer, 'org.electrum', 1, 0, 'TxFinalizer')
qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice') qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice')
qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails') qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails')
qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails')
qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property') qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')

11
electrum/gui/qml/qetransactionlistmodel.py

@ -117,6 +117,17 @@ class QETransactionListModel(QAbstractListModel):
return return
i = i + 1 i = i + 1
@pyqtSlot(str, str)
def update_tx_label(self, txid, label):
i = 0
for tx in self.tx_history:
if tx['txid'] == txid:
tx['label'] = label
index = self.index(i,0)
self.dataChanged.emit(index, index, [self._ROLE_RMAP['label']])
return
i = i + 1
@pyqtSlot(int) @pyqtSlot(int)
def updateBlockchainHeight(self, height): def updateBlockchainHeight(self, height):
self._logger.debug('updating height to %d' % height) self._logger.debug('updating height to %d' % height)

143
electrum/gui/qml/qetxdetails.py

@ -0,0 +1,143 @@
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
#from decimal import Decimal
from electrum.logging import get_logger
from electrum.util import format_time
from .qewallet import QEWallet
from .qetypes import QEAmount
class QETxDetails(QObject):
def __init__(self, parent=None):
super().__init__(parent)
_logger = get_logger(__name__)
_wallet = None
_txid = None
_mempool_depth = None
_date = None
detailsChanged = pyqtSignal()
walletChanged = pyqtSignal()
@pyqtProperty(QEWallet, notify=walletChanged)
def wallet(self):
return self._wallet
@wallet.setter
def wallet(self, wallet: QEWallet):
if self._wallet != wallet:
self._wallet = wallet
self.walletChanged.emit()
txidChanged = pyqtSignal()
@pyqtProperty(str, notify=txidChanged)
def txid(self):
return self._txid
@txid.setter
def txid(self, txid: str):
if self._txid != txid:
self._logger.debug('txid set -> %s' % txid)
self._txid = txid
self.txidChanged.emit()
self.update()
labelChanged = pyqtSignal()
@pyqtProperty(str, notify=labelChanged)
def label(self):
return self._label
@pyqtSlot(str)
def set_label(self, label: str):
if label != self._label:
self._wallet.wallet.set_label(self._txid, label)
self._label = label
self.labelChanged.emit()
@pyqtProperty(str, notify=detailsChanged)
def status(self):
return self._status
@pyqtProperty(str, notify=detailsChanged)
def date(self):
return self._date
@pyqtProperty(str, notify=detailsChanged)
def mempoolDepth(self):
return self._mempool_depth
@pyqtProperty(bool, notify=detailsChanged)
def isMined(self):
return self._is_mined
@pyqtProperty(bool, notify=detailsChanged)
def isLightningFundingTx(self):
return self._is_lightning_funding_tx
@pyqtProperty(bool, notify=detailsChanged)
def canBump(self):
return self._can_bump
@pyqtProperty(bool, notify=detailsChanged)
def canCancel(self):
return self._can_dscancel
@pyqtProperty(QEAmount, notify=detailsChanged)
def amount(self):
return self._amount
@pyqtProperty(QEAmount, notify=detailsChanged)
def fee(self):
return self._fee
@pyqtProperty('QVariantList', notify=detailsChanged)
def inputs(self):
return self._inputs
@pyqtProperty('QVariantList', notify=detailsChanged)
def outputs(self):
return self._outputs
def update(self):
if self._wallet is None:
self._logger.error('wallet undefined')
return
# abusing get_input_tx to get tx from txid
tx = self._wallet.wallet.get_input_tx(self._txid)
self._inputs = list(map(lambda x: x.to_json(), tx.inputs()))
self._outputs = list(map(lambda x: {
'address': x.get_ui_address_str(),
'value': QEAmount(amount_sat=x.value),
'is_mine': self._wallet.wallet.is_mine(x.get_ui_address_str())
}, tx.outputs()))
txinfo = self._wallet.wallet.get_tx_info(tx)
self._status = txinfo.status
self._label = txinfo.label
self._amount = QEAmount(amount_sat=txinfo.amount) # can be None?
self._fee = QEAmount(amount_sat=txinfo.fee)
self._is_mined = txinfo.tx_mined_status != None
if self._is_mined:
self._date = format_time(txinfo.tx_mined_status.timestamp)
else:
#TODO mempool_depth_bytes can be None?
self._mempool_depth = self._wallet.wallet.config.depth_tooltip(txinfo.mempool_depth_bytes)
self._is_lightning_funding_tx = txinfo.is_lightning_funding_tx
self._can_bump = txinfo.can_bump
self._can_dscancel = txinfo.can_dscancel
self._logger.debug(repr(txinfo.mempool_depth_bytes))
self._logger.debug(repr(txinfo.can_broadcast))
self._logger.debug(repr(txinfo.can_cpfp))
self._logger.debug(repr(txinfo.can_save_as_local))
self._logger.debug(repr(txinfo.can_remove))
self.detailsChanged.emit()
Loading…
Cancel
Save