Browse Source

wip lightning

patch-4
Sander van Grieken 3 years ago
parent
commit
c55aa7bb48
  1. 132
      electrum/gui/qml/components/Channels.qml
  2. 145
      electrum/gui/qml/components/OpenChannel.qml
  3. 15
      electrum/gui/qml/components/Scan.qml
  4. 10
      electrum/gui/qml/components/WalletMainView.qml
  5. 4
      electrum/gui/qml/qeapp.py
  6. 53
      electrum/gui/qml/qechannellistmodel.py
  7. 160
      electrum/gui/qml/qechannelopener.py
  8. 9
      electrum/gui/qml/qewallet.py

132
electrum/gui/qml/components/Channels.qml

@ -0,0 +1,132 @@
import QtQuick 2.6
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.0
import QtQuick.Controls.Material 2.0
import org.electrum 1.0
import "controls"
Pane {
property string title: qsTr("Lightning Channels")
ColumnLayout {
id: layout
width: parent.width
height: parent.height
GridLayout {
id: summaryLayout
Layout.preferredWidth: parent.width
columns: 2
Label {
Layout.columnSpan: 2
text: ''
}
Label {
text: qsTr('You can send:')
color: Material.accentColor
}
Label {
text: ''
}
Label {
text: qsTr('You can receive:')
color: Material.accentColor
}
Label {
text: ''
}
RowLayout {
Layout.columnSpan: 2
Button {
text: qsTr('Open Channel')
onClicked: app.stack.push(Qt.resolvedUrl('OpenChannel.qml'))
}
}
}
Frame {
id: channelsFrame
Layout.preferredWidth: parent.width
Layout.fillHeight: true
verticalPadding: 0
horizontalPadding: 0
background: PaneInsetBackground {}
ColumnLayout {
spacing: 0
anchors.fill: parent
Item {
Layout.preferredHeight: hitem.height
Layout.preferredWidth: parent.width
Rectangle {
anchors.fill: parent
color: Qt.lighter(Material.background, 1.25)
}
RowLayout {
id: hitem
width: parent.width
Label {
text: qsTr('Channels')
font.pixelSize: constants.fontSizeLarge
color: Material.accentColor
}
}
}
ListView {
id: listview
Layout.preferredWidth: parent.width
Layout.fillHeight: true
clip: true
model: 3 //Daemon.currentWallet.channelsModel
delegate: ItemDelegate {
width: ListView.view.width
height: row.height
highlighted: ListView.isCurrentItem
font.pixelSize: constants.fontSizeMedium // set default font size for child controls
RowLayout {
id: row
spacing: 10
x: constants.paddingSmall
width: parent.width - 2 * constants.paddingSmall
Image {
id: walleticon
source: "../../icons/lightning.png"
fillMode: Image.PreserveAspectFit
Layout.preferredWidth: constants.iconSizeLarge
Layout.preferredHeight: constants.iconSizeLarge
}
Label {
font.pixelSize: constants.fontSizeLarge
text: index
Layout.fillWidth: true
}
}
}
ScrollIndicator.vertical: ScrollIndicator { }
}
}
}
}
Component.onCompleted: Daemon.currentWallet.channelModel.init_model()
}

145
electrum/gui/qml/components/OpenChannel.qml

@ -0,0 +1,145 @@
import QtQuick 2.6
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.0
import QtQuick.Controls.Material 2.0
import org.electrum 1.0
import "controls"
Pane {
id: root
property string title: qsTr("Open Lightning Channel")
GridLayout {
id: form
width: parent.width
rowSpacing: constants.paddingSmall
columnSpacing: constants.paddingSmall
columns: 4
Label {
text: qsTr('Node')
}
TextArea {
id: node
Layout.columnSpan: 2
Layout.fillWidth: true
font.family: FixedFont
wrapMode: Text.Wrap
placeholderText: qsTr('Paste or scan node uri/pubkey')
onActiveFocusChanged: {
if (!activeFocus)
channelopener.nodeid = text
}
}
RowLayout {
spacing: 0
ToolButton {
icon.source: '../../icons/paste.png'
icon.height: constants.iconSizeMedium
icon.width: constants.iconSizeMedium
onClicked: {
channelopener.nodeid = AppController.clipboardToText()
node.text = channelopener.nodeid
}
}
ToolButton {
icon.source: '../../icons/qrcode.png'
icon.height: constants.iconSizeMedium
icon.width: constants.iconSizeMedium
scale: 1.2
onClicked: {
var page = app.stack.push(Qt.resolvedUrl('Scan.qml'))
page.onFound.connect(function() {
channelopener.nodeid = page.scanData
node.text = channelopener.nodeid
})
}
}
}
Label {
text: qsTr('Amount')
}
BtcField {
id: amount
fiatfield: amountFiat
Layout.preferredWidth: parent.width /2
onTextChanged: channelopener.amount = Config.unitsToSats(amount.text)
enabled: !is_max.checked
}
RowLayout {
Layout.columnSpan: 2
Layout.fillWidth: true
Label {
text: Config.baseUnit
color: Material.accentColor
}
Switch {
id: is_max
text: qsTr('Max')
onCheckedChanged: {
if (checked) {
channelopener.amount = MAX
}
}
}
}
Item { width: 1; height: 1; visible: Daemon.fx.enabled }
FiatField {
id: amountFiat
btcfield: amount
visible: Daemon.fx.enabled
Layout.preferredWidth: parent.width /2
enabled: !is_max.checked
}
Label {
visible: Daemon.fx.enabled
text: Daemon.fx.fiatCurrency
color: Material.accentColor
Layout.fillWidth: true
}
Item { visible: Daemon.fx.enabled ; height: 1; width: 1 }
RowLayout {
Layout.columnSpan: 4
Layout.alignment: Qt.AlignHCenter
Button {
text: qsTr('Open Channel')
enabled: channelopener.valid
onClicked: channelopener.open_channel()
}
}
}
ChannelOpener {
id: channelopener
wallet: Daemon.currentWallet
onValidationError: {
if (code == 'invalid_nodeid') {
var dialog = app.messageDialog.createObject(root, { 'text': message })
dialog.open()
}
}
onConflictingBackup: {
var dialog = app.messageDialog.createObject(root, { 'text': message })
dialog.open()
dialog.yesClicked.connect(function() {
channelopener.open_channel(true)
})
}
}
}

15
electrum/gui/qml/components/Scan.qml

@ -12,7 +12,6 @@ Item {
property bool toolbar: false
property string scanData
property var invoiceData: undefined
property string error
signal found
@ -24,16 +23,6 @@ Item {
onFound: {
scanPage.scanData = scanData
var invoice = bitcoin.parse_uri(scanData)
if (invoice['error']) {
error = invoice['error']
console.log(error)
app.stack.pop()
return
}
invoiceData = invoice
console.log(invoiceData['address'])
scanPage.found()
app.stack.pop()
}
@ -46,8 +35,4 @@ Item {
text: 'Cancel'
onClicked: app.stack.pop()
}
Bitcoin {
id: bitcoin
}
}

10
electrum/gui/qml/components/WalletMainView.qml

@ -35,6 +35,16 @@ Item {
icon.source: '../../icons/network.png'
}
}
MenuItem {
icon.color: 'transparent'
action: Action {
text: qsTr('Channels');
enabled: Daemon.currentWallet.isLightning
onTriggered: menu.openPage(Qt.resolvedUrl('Channels.qml'))
icon.source: '../../icons/lightning.png'
}
}
MenuItem {
icon.color: 'transparent'
action: Action {

4
electrum/gui/qml/qeapp.py

@ -23,6 +23,7 @@ from .qeinvoice import QEInvoice
from .qetypes import QEAmount
from .qeaddressdetails import QEAddressDetails
from .qetxdetails import QETxDetails
from .qechannelopener import QEChannelOpener
notification = None
@ -143,6 +144,7 @@ class ElectrumQmlApplication(QGuiApplication):
qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice')
qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails')
qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails')
qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener')
qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')
@ -165,11 +167,13 @@ class ElectrumQmlApplication(QGuiApplication):
self._qenetwork = QENetwork(daemon.network)
self._qedaemon = QEDaemon(daemon)
self._appController = QEAppController(self._qedaemon)
self._maxAmount = QEAmount(is_max=True)
self.context.setContextProperty('AppController', self._appController)
self.context.setContextProperty('Config', self._qeconfig)
self.context.setContextProperty('Network', self._qenetwork)
self.context.setContextProperty('Daemon', self._qedaemon)
self.context.setContextProperty('FixedFont', self.fixedFont)
self.context.setContextProperty('MAX', self._maxAmount)
self.context.setContextProperty('BUILD', {
'electrum_version': version.ELECTRUM_VERSION,
'apk_version': version.APK_VERSION,

53
electrum/gui/qml/qechannellistmodel.py

@ -0,0 +1,53 @@
from datetime import datetime, timedelta
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
from electrum.logging import get_logger
from electrum.util import Satoshis, TxMinedInfo
from .qetypes import QEAmount
class QEChannelListModel(QAbstractListModel):
def __init__(self, wallet, parent=None):
super().__init__(parent)
self.wallet = wallet
self.channels = []
_logger = get_logger(__name__)
# define listmodel rolemap
_ROLE_NAMES=('cid','state','initiator','capacity','can_send','can_receive',
'l_csv_delat','r_csv_delay','send_frozen','receive_frozen',
'type','node_id','funding_tx')
_ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES))
_ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
_ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS))
def rowCount(self, index):
return len(self.tx_history)
def roleNames(self):
return self._ROLE_MAP
def data(self, index, role):
tx = self.tx_history[index.row()]
role_index = role - Qt.UserRole
value = tx[self._ROLE_NAMES[role_index]]
if isinstance(value, (bool, list, int, str, QEAmount)) or value is None:
return value
if isinstance(value, Satoshis):
return value.value
if isinstance(value, QEAmount):
return value
return str(value)
@pyqtSlot()
def init_model(self):
if not self.wallet.lnworker:
self._logger.warning('lnworker should be defined')
return
channels = self.wallet.lnworker.channels
self._logger.debug(repr(channels))
#channels = list(lnworker.channels.values()) if lnworker else []

160
electrum/gui/qml/qechannelopener.py

@ -0,0 +1,160 @@
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from electrum.logging import get_logger
from electrum.util import format_time
from electrum.lnutil import extract_nodeid, ConnStringFormatError
from electrum.gui import messages
from .qewallet import QEWallet
from .qetypes import QEAmount
class QEChannelOpener(QObject):
def __init__(self, parent=None):
super().__init__(parent)
_logger = get_logger(__name__)
_wallet = None
_nodeid = None
_amount = QEAmount()
_valid = False
_opentx = None
validationError = pyqtSignal([str,str], arguments=['code','message'])
conflictingBackup = pyqtSignal([str], arguments=['message'])
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()
nodeidChanged = pyqtSignal()
@pyqtProperty(str, notify=nodeidChanged)
def nodeid(self):
return self._nodeid
@nodeid.setter
def nodeid(self, nodeid: str):
if self._nodeid != nodeid:
self._logger.debug('nodeid set -> %s' % nodeid)
self._nodeid = nodeid
self.nodeidChanged.emit()
self.validate()
amountChanged = pyqtSignal()
@pyqtProperty(QEAmount, notify=amountChanged)
def amount(self):
return self._amount
@amount.setter
def amount(self, amount: QEAmount):
if self._amount != amount:
self._amount = amount
self.amountChanged.emit()
self.validate()
validChanged = pyqtSignal()
@pyqtProperty(bool, notify=validChanged)
def valid(self):
return self._valid
openTxChanged = pyqtSignal()
@pyqtProperty(bool, notify=openTxChanged)
def openTx(self):
return self._opentx
def validate(self):
nodeid_valid = False
if self._nodeid:
try:
self._node_pubkey, self._host_port = extract_nodeid(self._nodeid)
nodeid_valid = True
except ConnStringFormatError as e:
self.validationError.emit('invalid_nodeid', repr(e))
if not nodeid_valid:
self._valid = False
self.validChanged.emit()
return
self._logger.debug('amount=%s' % str(self._amount))
if not self._amount or not (self._amount.satsInt > 0 or self._amount.isMax):
self._valid = False
self.validChanged.emit()
return
self._valid = True
self.validChanged.emit()
# FIXME "max" button in amount_dialog should enforce LN_MAX_FUNDING_SAT
@pyqtSlot()
@pyqtSlot(bool)
def open_channel(self, confirm_backup_conflict=False):
if not self.valid:
return
#if self.use_gossip:
#conn_str = self.pubkey
#if self.ipport:
#conn_str += '@' + self.ipport.strip()
#else:
#conn_str = str(self.trampolines[self.pubkey])
amount = '!' if self._amount.isMax else self._amount.satsInt
lnworker = self._wallet.wallet.lnworker
if lnworker.has_conflicting_backup_with(node_pubkey) and not confirm_backup_conflict:
self.conflictingBackup.emit(messages.MGS_CONFLICTING_BACKUP_INSTANCE)
return
coins = self._wallet.wallet.get_spendable_coins(None, nonlocal_only=True)
#node_id, rest = extract_nodeid(conn_str)
make_tx = lambda rbf: lnworker.mktx_for_open_channel(
coins=coins,
funding_sat=amount,
node_id=self._node_pubkey,
fee_est=None)
#on_pay = lambda tx: self.app.protected('Create a new channel?', self.do_open_channel, (tx, conn_str))
#d = ConfirmTxDialog(
#self.app,
#amount = amount,
#make_tx=make_tx,
#on_pay=on_pay,
#show_final=False)
#d.open()
#def do_open_channel(self, funding_tx, conn_str, password):
## read funding_sat from tx; converts '!' to int value
#funding_sat = funding_tx.output_value_for_address(ln_dummy_address())
#lnworker = self.app.wallet.lnworker
#try:
#chan, funding_tx = lnworker.open_channel(
#connect_str=conn_str,
#funding_tx=funding_tx,
#funding_sat=funding_sat,
#push_amt_sat=0,
#password=password)
#except Exception as e:
#self.app.logger.exception("Problem opening channel")
#self.app.show_error(_('Problem opening channel: ') + '\n' + repr(e))
#return
## TODO: it would be nice to show this before broadcasting
#if chan.has_onchain_backup():
#self.maybe_show_funding_tx(chan, funding_tx)
#else:
#title = _('Save backup')
#help_text = _(messages.MSG_CREATED_NON_RECOVERABLE_CHANNEL)
#data = lnworker.export_channel_backup(chan.channel_id)
#popup = QRDialog(
#title, data,
#show_text=False,
#text_for_clipboard=data,
#help_text=help_text,
#close_button_text=_('OK'),
#on_close=lambda: self.maybe_show_funding_tx(chan, funding_tx))
#popup.open()

9
electrum/gui/qml/qewallet.py

@ -17,6 +17,7 @@ from electrum.invoices import (Invoice, InvoiceError,
from .qeinvoicelistmodel import QEInvoiceListModel, QERequestListModel
from .qetransactionlistmodel import QETransactionListModel
from .qeaddresslistmodel import QEAddressListModel
from .qechannellistmodel import QEChannelListModel
from .qetypes import QEAmount
class QEWallet(QObject):
@ -60,6 +61,7 @@ class QEWallet(QObject):
self._addressModel = QEAddressListModel(wallet)
self._requestModel = QERequestListModel(wallet)
self._invoiceModel = QEInvoiceListModel(wallet)
self._channelModel = None
self._historyModel.init_model()
self._requestModel.init_model()
@ -192,6 +194,13 @@ class QEWallet(QObject):
def invoiceModel(self):
return self._invoiceModel
channelModelChanged = pyqtSignal()
@pyqtProperty(QEChannelListModel, notify=channelModelChanged)
def channelModel(self):
if self._channelModel is None:
self._channelModel = QEChannelListModel(self.wallet)
return self._channelModel
nameChanged = pyqtSignal()
@pyqtProperty('QString', notify=nameChanged)
def name(self):

Loading…
Cancel
Save