Browse Source

qml: replace Send/ScanDialog with java bases zxing qr scan activity.

master
SomberNight 2 years ago committed by Sander van Grieken
parent
commit
5c3e14d8de
  1. 3
      .gitignore
  2. 2
      contrib/android/Dockerfile
  3. 22
      contrib/android/buildozer_qml.spec
  4. 2
      contrib/android/make_apk.sh
  5. 29
      electrum/gui/qml/android_res/layout/scanner_layout.xml
  6. 51
      electrum/gui/qml/components/ScanDialog.qml
  7. 82
      electrum/gui/qml/components/SendDialog.qml
  8. 71
      electrum/gui/qml/components/WalletMainView.qml
  9. 4
      electrum/gui/qml/components/main.qml
  10. 61
      electrum/gui/qml/java_classes/org/electrum/qr/SimpleScannerActivity.java
  11. 2
      electrum/gui/qml/qeapp.py
  12. 96
      electrum/gui/qml/qeqrscanner.py

3
.gitignore vendored

@ -12,8 +12,7 @@ electrum/locale/
packages
env/
.buildozer
.buildozer_kivy/
.buildozer_qml/
.buildozer_*/
bin/
/app.fil
.idea

2
contrib/android/Dockerfile

@ -198,7 +198,7 @@ RUN cd /opt \
&& git fetch --all \
# commit: from branch accumulator/qt6-wip (note: careful with force-pushing! see #8162) \
#
&& git checkout "3b3733dbf5f461e197ba83887ac0d3b6d0f1c396^{commit}" \
&& git checkout "f2741f0b7b6a4ac52fc6cb0dc9ac706e2287be02^{commit}" \
&& /opt/venv/bin/python3 -m pip install --no-build-isolation --no-dependencies -e .
# build env vars

22
contrib/android/buildozer_qml.spec

@ -138,9 +138,25 @@ android.add_jars = .buildozer/android/platform/*/build/libs_collections/Electrum
# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
# android.add_src = ...
# android.add_activities = ...
android.gradle_dependencies = com.android.support:support-compat:28.0.0
android.add_src = electrum/gui/qml/java_classes/
android.gradle_dependencies =
com.android.support:support-compat:28.0.0,
me.dm7.barcodescanner:zxing:1.9.8
android.add_activities = org.electrum.qr.SimpleScannerActivity
# (list) Put these files or directories in the apk res directory.
# The option may be used in three ways, the value may contain one or zero ':'
# Some examples:
# 1) A file to add to resources, legal resource names contain ['a-z','0-9','_']
# android.add_resources = my_icons/all-inclusive.png:drawable/all_inclusive.png
# 2) A directory, here 'legal_icons' must contain resources of one kind
# android.add_resources = legal_icons:drawable
# 3) A directory, here 'legal_resources' must contain one or more directories,
# each of a resource kind: drawable, xml, etc...
# android.add_resources = legal_resources
android.add_resources = electrum/gui/qml/android_res/layout:layout
# (str) python-for-android branch to use, if not master, useful to try
# not yet merged features.

2
contrib/android/make_apk.sh

@ -45,7 +45,7 @@ info "apk building phase starts."
# FIXME: changing "APP_PACKAGE_NAME" seems to require a clean rebuild of ".buildozer/",
# to avoid that, maybe change "APP_PACKAGE_DOMAIN" instead.
# So, in particular, to build a testnet apk, simply uncomment:
#export APP_PACKAGE_DOMAIN=org.electrum.testnet
export APP_PACKAGE_DOMAIN=org.electrum.testnet
if [ $CI ]; then
# override log level specified in buildozer.spec to "debug":

29
electrum/gui/qml/android_res/layout/scanner_layout.xml

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/hint"
android:layout_gravity="center|top"
android:text="Scan a QR code."
android:layout_width="wrap_content"
android:textColor="#ffffff"
android:layout_height="wrap_content" />
<Button
android:id="@+id/paste_btn"
android:layout_gravity="center|bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Paste from clipboard" />
</FrameLayout>

51
electrum/gui/qml/components/ScanDialog.qml

@ -1,51 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "controls"
ElDialog {
id: scanDialog
property string scanData
property string error
property string hint
signal found
width: parent.width
height: parent.height
padding: 0
header: null
topPadding: 0 // dialog needs topPadding override
function doClose() {
qrscan.stop()
Qt.callLater(doReject)
}
ColumnLayout {
anchors.fill: parent
spacing: 0
QRScan {
id: qrscan
Layout.fillWidth: true
Layout.fillHeight: true
hint: scanDialog.hint
onFound: {
scanDialog.scanData = scanData
scanDialog.found()
}
}
FlatButton {
id: button
Layout.fillWidth: true
text: qsTr('Cancel')
icon.source: '../../icons/closebutton.png'
onClicked: doReject()
}
}
}

82
electrum/gui/qml/components/SendDialog.qml

@ -1,82 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Controls.Material
import org.electrum 1.0
import "controls"
ElDialog {
id: dialog
property InvoiceParser invoiceParser
signal txFound(data: string)
signal channelBackupFound(data: string)
header: null
padding: 0
topPadding: 0
onAboutToHide: {
console.log('about to hide')
qrscan.stop()
}
function restart() {
qrscan.restart()
}
function dispatch(data) {
data = data.trim()
if (bitcoin.isRawTx(data)) {
txFound(data)
} else if (Daemon.currentWallet.isValidChannelBackup(data)) {
channelBackupFound(data)
} else {
invoiceParser.recipient = data
}
}
// override
function doClose() {
console.log('SendDialog doClose override') // doesn't trigger when going back??
qrscan.stop()
Qt.callLater(doReject)
}
ColumnLayout {
anchors.fill: parent
spacing: 0
QRScan {
id: qrscan
Layout.fillWidth: true
Layout.fillHeight: true
hint: qsTr('Scan an Invoice, an Address, an LNURL-pay, a PSBT or a Channel backup')
onFound: dialog.dispatch(scanData)
}
ButtonContainer {
Layout.fillWidth: true
FlatButton {
Layout.fillWidth: true
Layout.preferredWidth: 1
icon.source: '../../icons/copy_bw.png'
text: qsTr('Paste')
onClicked: {
qrscan.stop()
dialog.dispatch(AppController.clipboardToText())
}
}
}
}
Bitcoin {
id: bitcoin
}
}

71
electrum/gui/qml/components/WalletMainView.qml

@ -13,7 +13,6 @@ Item {
property string title: Daemon.currentWallet ? Daemon.currentWallet.name : qsTr('no wallet loaded')
property var _sendDialog
property string _intentUri
property string _request_amount
@ -34,21 +33,33 @@ Item {
}
function openSendDialog() {
_sendDialog = sendDialog.createObject(mainView, {invoiceParser: invoiceParser})
_sendDialog.open()
}
function closeSendDialog() {
if (_sendDialog) {
_sendDialog.doClose()
_sendDialog = null
}
var scanner = app.scanDialog.createObject(mainView, {
hint: qsTr('Scan an Invoice, an Address, an LNURL-pay, a PSBT or a Channel backup'),
})
scanner.onFound.connect(function() {
var data = scanner.scanData
data = data.trim()
if (bitcoin.isRawTx(data)) {
app.stack.push(Qt.resolvedUrl('TxDetails.qml'), { rawtx: data })
} else if (Daemon.currentWallet.isValidChannelBackup(data)) {
var dialog = app.messageDialog.createObject(app, {
title: qsTr('Import Channel backup?'),
yesno: true
})
dialog.accepted.connect(function() {
Daemon.currentWallet.importChannelBackup(data)
})
dialog.open()
} else {
invoiceParser.recipient = data
}
//scanner.destroy() // TODO
})
scanner.open()
}
function restartSendDialog() {
if (_sendDialog) {
_sendDialog.restart()
}
//openSendDialog() // note: ~infinite-loop on non-android due to directly pasting from clipboard
}
function showExport(data, helptext) {
@ -287,7 +298,6 @@ Item {
}
}
onValidationSuccess: {
closeSendDialog()
var dialog = invoiceDialog.createObject(app, {
invoice: invoiceParser,
payImmediately: invoiceParser.isLnurlPay
@ -299,7 +309,6 @@ Item {
}
onLnurlRetrieved: {
closeSendDialog()
var dialog = lnurlPayDialog.createObject(app, {
invoiceParser: invoiceParser
})
@ -314,6 +323,10 @@ Item {
}
}
Bitcoin {
id: bitcoin
}
Connections {
target: AppController
function onUriReceived(uri) {
@ -419,34 +432,6 @@ Item {
}
}
Component {
id: sendDialog
SendDialog {
width: parent.width
height: parent.height
onTxFound: {
app.stack.push(Qt.resolvedUrl('TxDetails.qml'), { rawtx: data })
close()
}
onChannelBackupFound: {
var dialog = app.messageDialog.createObject(app, {
title: qsTr('Import Channel backup?'),
yesno: true
})
dialog.accepted.connect(function() {
Daemon.currentWallet.importChannelBackup(data)
close()
})
dialog.rejected.connect(function() {
close()
})
dialog.open()
}
onClosed: destroy()
}
}
function createRequest(lightning_only, reuse_address) {
var qamt = Config.unitsToSats(_request_amount)
Daemon.currentWallet.createRequest(qamt, _request_description, _request_expiry, lightning_only, reuse_address)

4
electrum/gui/qml/components/main.qml

@ -380,8 +380,8 @@ ApplicationWindow
property alias scanDialog: _scanDialog
Component {
id: _scanDialog
ScanDialog {
onClosed: destroy()
QRScanner {
//onClosed: destroy()
}
}

61
electrum/gui/qml/java_classes/org/electrum/qr/SimpleScannerActivity.java

@ -5,7 +5,16 @@ import android.os.Bundle;
import android.util.Log;
import android.content.Intent;
import android.Manifest;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
@ -16,18 +25,53 @@ import me.dm7.barcodescanner.zxing.ZXingScannerView;
import com.google.zxing.Result;
import com.google.zxing.BarcodeFormat;
import org.electrum.testnet.electrum.R; // TODO
public class SimpleScannerActivity extends Activity implements ZXingScannerView.ResultHandler {
private static final int MY_PERMISSIONS_CAMERA = 1002;
private ZXingScannerView mScannerView = null;
final String TAG = "org.electrum.SimpleScannerActivity";
private boolean mAlreadyRequestedPermissions = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.scanner_layout);
// change top text
Intent intent = getIntent();
String text = intent.getStringExtra(intent.EXTRA_TEXT);
TextView hintTextView = (TextView) findViewById(R.id.hint);
hintTextView.setText(text);
// bind "paste" button
Button btn = (Button) findViewById(R.id.paste_btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboard.hasPrimaryClip()
&& (clipboard.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)
|| clipboard.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML))) {
ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
String clipboardText = item.getText().toString();
SimpleScannerActivity.this.setResultAndClose(clipboardText);
} else {
Toast.makeText(SimpleScannerActivity.this, "Clipboard is empty.", Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
public void onResume() {
super.onResume();
if (this.hasPermission()) {
this.startCamera();
} else {
} else if (!mAlreadyRequestedPermissions) {
mAlreadyRequestedPermissions = true;
this.requestPermission();
}
}
@ -41,18 +85,23 @@ public class SimpleScannerActivity extends Activity implements ZXingScannerView.
}
private void startCamera() {
mScannerView = new ZXingScannerView(this); // Programmatically initialize the scanner view
mScannerView = new ZXingScannerView(this);
mScannerView.setFormats(Arrays.asList(BarcodeFormat.QR_CODE));
setContentView(mScannerView); // Set the scanner view as the content view
ViewGroup contentFrame = (ViewGroup) findViewById(R.id.content_frame);
contentFrame.addView(mScannerView);
mScannerView.setResultHandler(this); // Register ourselves as a handler for scan results.
mScannerView.startCamera(); // Start camera on resume
}
@Override
public void handleResult(Result rawResult) {
//resultIntent.putExtra("format", rawResult.getBarcodeFormat().toString());
this.setResultAndClose(rawResult.getText());
}
private void setResultAndClose(String resultText) {
Intent resultIntent = new Intent();
resultIntent.putExtra("text", rawResult.getText());
resultIntent.putExtra("format", rawResult.getBarcodeFormat().toString());
resultIntent.putExtra("text", resultText);
setResult(Activity.RESULT_OK, resultIntent);
this.finish();
}
@ -80,7 +129,7 @@ public class SimpleScannerActivity extends Activity implements ZXingScannerView.
this.startCamera();
} else {
// permission denied
this.finish();
//this.finish();
}
return;
}

2
electrum/gui/qml/qeapp.py

@ -25,6 +25,7 @@ from .qedaemon import QEDaemon
from .qenetwork import QENetwork
from .qewallet import QEWallet
from .qeqr import QEQRParser, QEQRImageProvider, QEQRImageProviderHelper
from .qeqrscanner import QEQRScanner
from .qebitcoin import QEBitcoin
from .qefx import QEFX
from .qetxfinalizer import QETxFinalizer, QETxRbfFeeBumper, QETxCpfpFeeBumper, QETxCanceller
@ -353,6 +354,7 @@ class ElectrumQmlApplication(QGuiApplication):
qmlRegisterType(QEWallet, 'org.electrum', 1, 0, 'Wallet')
qmlRegisterType(QEBitcoin, 'org.electrum', 1, 0, 'Bitcoin')
qmlRegisterType(QEQRParser, 'org.electrum', 1, 0, 'QRParser')
qmlRegisterType(QEQRScanner, 'org.electrum', 1, 0, 'QRScanner')
qmlRegisterType(QEFX, 'org.electrum', 1, 0, 'FX')
qmlRegisterType(QETxFinalizer, 'org.electrum', 1, 0, 'TxFinalizer')
qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice')

96
electrum/gui/qml/qeqrscanner.py

@ -0,0 +1,96 @@
import os
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from PyQt6.QtGui import QGuiApplication
from electrum.util import send_exception_to_crash_reporter, UserFacingException
from electrum.simple_config import SimpleConfig
from electrum.logging import get_logger
from electrum.i18n import _
if 'ANDROID_DATA' in os.environ:
from jnius import autoclass, cast
from android import activity
jpythonActivity = autoclass('org.kivy.android.PythonActivity').mActivity
jString = autoclass('java.lang.String')
jIntent = autoclass('android.content.Intent')
class QEQRScanner(QObject):
_logger = get_logger(__name__)
found = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self._hint = _("Scan a QR code.")
self._scan_data = "" # decoded qr code result
@pyqtProperty(str)
def hint(self):
return self._hint
@hint.setter
def hint(self, v: str):
self._hint = v
@pyqtProperty(str)
def scanData(self):
return self._scan_data
@scanData.setter
def scanData(self, v: str):
self._scan_data = v
@pyqtSlot()
def open(self):
if 'ANDROID_DATA' not in os.environ:
self._scan_qr_non_android()
return
SimpleScannerActivity = autoclass("org.electrum.qr.SimpleScannerActivity")
intent = jIntent(jpythonActivity, SimpleScannerActivity)
intent.putExtra(jIntent.EXTRA_TEXT, jString(self._hint))
def on_qr_result(requestCode, resultCode, intent):
try:
if resultCode == -1: # RESULT_OK:
# this doesn't work due to some bug in jnius:
# contents = intent.getStringExtra("text")
contents = intent.getStringExtra(jString("text"))
#self._logger.info(f"on_qr_result. {contents=!r}")
self.scanData = contents
self.found.emit()
except Exception as e: # exc would otherwise get lost
send_exception_to_crash_reporter(e)
finally:
activity.unbind(on_activity_result=on_qr_result)
activity.bind(on_activity_result=on_qr_result)
jpythonActivity.startActivityForResult(intent, 0)
@pyqtSlot()
def close(self):
pass
def _scan_qr_non_android(self):
data = QGuiApplication.clipboard().text()
self.scanData = data
self.found.emit()
return
# from electrum import qrscanner
# from .qeapp import ElectrumQmlApplication
# daemon = ElectrumQmlApplication._daemon
# config = daemon.config # type: SimpleConfig
# try:
# video_dev = config.get_video_device()
# data = qrscanner.scan_barcode(video_dev)
# if data is not None:
# self.scanData = data
# self.found.emit()
# except UserFacingException as e:
# self._logger.warning(f'camera error: {e!r}')
# #self.show_error(e)
# except Exception as e:
# self._logger.exception('camera error')
# #self.show_error(repr(e))
Loading…
Cancel
Save