qml: replace Send/ScanDialog with java bases zxing qr scan activity.
This commit is contained in:
committed by
Sander van Grieken
parent
c33ee87544
commit
5c3e14d8de
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,8 +12,7 @@ electrum/locale/
|
||||
packages
|
||||
env/
|
||||
.buildozer
|
||||
.buildozer_kivy/
|
||||
.buildozer_qml/
|
||||
.buildozer_*/
|
||||
bin/
|
||||
/app.fil
|
||||
.idea
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
Normal file
29
electrum/gui/qml/android_res/layout/scanner_layout.xml
Normal file
@@ -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>
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -380,8 +380,8 @@ ApplicationWindow
|
||||
property alias scanDialog: _scanDialog
|
||||
Component {
|
||||
id: _scanDialog
|
||||
ScanDialog {
|
||||
onClosed: destroy()
|
||||
QRScanner {
|
||||
//onClosed: destroy()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
Normal file
96
electrum/gui/qml/qeqrscanner.py
Normal file
@@ -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))
|
||||
Reference in New Issue
Block a user