Support of multiple-code config
This commit is contained in:
parent
a86e8659f7
commit
b870306c5d
11 changed files with 126 additions and 204 deletions
|
@ -34,6 +34,7 @@
|
|||
android:extractNativeLibs="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:allowNativeHeapPointerTagging="false"
|
||||
android:theme="@style/Theme.AppCompat.NoActionBar"
|
||||
android:icon="@drawable/icon">
|
||||
|
||||
<activity
|
||||
|
@ -115,7 +116,7 @@
|
|||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".CameraActivity"
|
||||
android:name=".qt.CameraActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
|
|
|
@ -31,6 +31,20 @@ class CameraActivity : AppCompatActivity() {
|
|||
|
||||
private lateinit var viewFinder: PreviewView
|
||||
|
||||
companion object {
|
||||
private lateinit var instance: CameraActivity
|
||||
|
||||
@JvmStatic fun getInstance(): CameraActivity {
|
||||
return instance
|
||||
}
|
||||
|
||||
@JvmStatic fun stopQrCodeReader() {
|
||||
CameraActivity.getInstance().finish()
|
||||
}
|
||||
}
|
||||
|
||||
external fun passDataToDecoder(data: String)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_camera)
|
||||
|
@ -40,6 +54,8 @@ class CameraActivity : AppCompatActivity() {
|
|||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
analyzerExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
instance = this
|
||||
|
||||
checkPermissions()
|
||||
|
||||
configureVideoPreview()
|
||||
|
@ -74,20 +90,7 @@ class CameraActivity : AppCompatActivity() {
|
|||
|
||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||
|
||||
val listener = object : OnBarcodeActivityResult {
|
||||
override fun onSuccess(result: String) {
|
||||
val resultIntent = Intent()
|
||||
resultIntent.putExtra("result", result)
|
||||
setResult(Activity.RESULT_OK, resultIntent)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onFailure(result: Exception) {
|
||||
Log.d("WUTT", "exception: $result")
|
||||
}
|
||||
}
|
||||
|
||||
val imageAnalyzer = BarCodeAnalyzer(listener)
|
||||
val imageAnalyzer = BarCodeAnalyzer()
|
||||
|
||||
val analysisUseCase = ImageAnalysis.Builder()
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
|
@ -112,7 +115,9 @@ class CameraActivity : AppCompatActivity() {
|
|||
super.onDestroy()
|
||||
}
|
||||
|
||||
private class BarCodeAnalyzer(val callback: OnBarcodeActivityResult): ImageAnalysis.Analyzer {
|
||||
val barcodesSet = mutableSetOf<String>()
|
||||
|
||||
private inner class BarCodeAnalyzer(): ImageAnalysis.Analyzer {
|
||||
|
||||
private val options = BarcodeScannerOptions.Builder()
|
||||
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
|
||||
|
@ -130,21 +135,24 @@ class CameraActivity : AppCompatActivity() {
|
|||
scanner.process(image)
|
||||
.addOnSuccessListener { barcodes ->
|
||||
if (barcodes.isNotEmpty()) {
|
||||
callback.onSuccess(barcodes[0]?.displayValue ?: "empty?")
|
||||
val barcode = barcodes[0]
|
||||
if (barcode != null) {
|
||||
val str = barcode?.displayValue ?: ""
|
||||
if (str.isNotEmpty()) {
|
||||
val isAdded = barcodesSet.add(str)
|
||||
if (isAdded) {
|
||||
passDataToDecoder(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
imageProxy.close()
|
||||
}
|
||||
.addOnFailureListener {
|
||||
callback.onFailure(it)
|
||||
imageProxy.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface OnBarcodeActivityResult {
|
||||
fun onSuccess(result: String)
|
||||
fun onFailure(result: Exception)
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@
|
|||
#include "private/qandroidextras_p.h"
|
||||
#include "ui/pages_logic/StartPageLogic.h"
|
||||
|
||||
#include "androidvpnactivity.h"
|
||||
#include "androidutils.h"
|
||||
|
||||
namespace {
|
||||
|
@ -262,6 +261,11 @@ void AndroidController::setVpnConfig(const QJsonObject &newVpnConfig)
|
|||
m_vpnConfig = newVpnConfig;
|
||||
}
|
||||
|
||||
void AndroidController::startQrReaderActivity()
|
||||
{
|
||||
AndroidVPNActivity::instance()->startQrCodeReader();
|
||||
}
|
||||
|
||||
void AndroidController::scheduleStatusCheckSlot()
|
||||
{
|
||||
QTimer::singleShot(1000, [this]() {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "ui/pages_logic/StartPageLogic.h"
|
||||
|
||||
#include "protocols/vpnprotocol.h"
|
||||
#include "androidvpnactivity.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
|
@ -42,6 +43,8 @@ public:
|
|||
const QJsonObject &vpnConfig() const;
|
||||
void setVpnConfig(const QJsonObject &newVpnConfig);
|
||||
|
||||
void startQrReaderActivity();
|
||||
|
||||
signals:
|
||||
void connectionStateChanged(VpnProtocol::VpnConnectionState state);
|
||||
|
||||
|
@ -50,8 +53,7 @@ signals:
|
|||
// to true and the "connectionDate" should be set to the activation date if
|
||||
// known.
|
||||
// If "status" is set to false, the backend service is considered unavailable.
|
||||
void initialized(bool status, bool connected,
|
||||
const QDateTime& connectionDate);
|
||||
void initialized(bool status, bool connected, const QDateTime& connectionDate);
|
||||
|
||||
void statusUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4);
|
||||
void scheduleStatusCheckSignal();
|
||||
|
@ -59,9 +61,6 @@ signals:
|
|||
protected slots:
|
||||
void scheduleStatusCheckSlot();
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
private:
|
||||
bool m_init = false;
|
||||
|
||||
|
|
|
@ -81,7 +81,6 @@
|
|||
<file>ui/qml/Pages/PageSites.qml</file>
|
||||
<file>ui/qml/Pages/PageStart.qml</file>
|
||||
<file>ui/qml/Pages/PageVPN.qml</file>
|
||||
<file>ui/qml/Pages/PageQrDecoder.qml</file>
|
||||
<file>ui/qml/Pages/PageAbout.qml</file>
|
||||
<file>ui/qml/Pages/PageQrDecoderIos.qml</file>
|
||||
<file>ui/qml/Pages/PageViewConfig.qml</file>
|
||||
|
|
|
@ -3,15 +3,70 @@
|
|||
#include "ui/uilogic.h"
|
||||
#include "ui/pages_logic/StartPageLogic.h"
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include <QJniEnvironment>
|
||||
#include <QJniObject>
|
||||
#include "../../platforms/android/androidutils.h"
|
||||
#endif
|
||||
|
||||
using namespace amnezia;
|
||||
using namespace PageEnumNS;
|
||||
|
||||
namespace {
|
||||
QrDecoderLogic* mInstance = nullptr;
|
||||
constexpr auto CLASSNAME = "org.amnezia.vpn.qt.CameraActivity";
|
||||
}
|
||||
|
||||
QrDecoderLogic::QrDecoderLogic(UiLogic *logic, QObject *parent):
|
||||
PageLogicBase(logic, parent)
|
||||
{
|
||||
mInstance = this;
|
||||
|
||||
#if (defined(Q_OS_ANDROID))
|
||||
AndroidUtils::runOnAndroidThreadAsync([]() {
|
||||
JNINativeMethod methods[]{
|
||||
{"passDataToDecoder", "(Ljava/lang/String;)V", reinterpret_cast<void*>(onNewDataChunk)},
|
||||
};
|
||||
|
||||
QJniObject javaClass(CLASSNAME);
|
||||
QJniEnvironment env;
|
||||
jclass objectClass = env->GetObjectClass(javaClass.object<jobject>());
|
||||
env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0]));
|
||||
env->DeleteLocalRef(objectClass);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void QrDecoderLogic::stopDecodingQr()
|
||||
{
|
||||
#if (defined(Q_OS_ANDROID))
|
||||
QJniObject::callStaticMethod<void>(CLASSNAME, "stopQrCodeReader", "()V");
|
||||
#endif
|
||||
|
||||
emit stopDecode();
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void QrDecoderLogic::onNewDataChunk(JNIEnv *env, jobject thiz, jstring data)
|
||||
{
|
||||
Q_UNUSED(thiz);
|
||||
const char* buffer = env->GetStringUTFChars(data, nullptr);
|
||||
if (!buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString parcelBody(buffer);
|
||||
env->ReleaseStringUTFChars(data, buffer);
|
||||
|
||||
if (mInstance != nullptr) {
|
||||
if (!mInstance->m_detectingEnabled) {
|
||||
mInstance->onUpdatePage();
|
||||
}
|
||||
mInstance->onDetectedQrCode(parcelBody);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void QrDecoderLogic::onUpdatePage()
|
||||
{
|
||||
m_chunks.clear();
|
||||
|
@ -24,7 +79,6 @@ void QrDecoderLogic::onUpdatePage()
|
|||
void QrDecoderLogic::onDetectedQrCode(const QString &code)
|
||||
{
|
||||
//qDebug() << code;
|
||||
|
||||
if (!detectingEnabled()) return;
|
||||
|
||||
// check if chunk received
|
||||
|
@ -32,12 +86,12 @@ void QrDecoderLogic::onDetectedQrCode(const QString &code)
|
|||
QDataStream s(&ba, QIODevice::ReadOnly);
|
||||
qint16 magic; s >> magic;
|
||||
|
||||
|
||||
if (magic == amnezia::qrMagicCode) {
|
||||
quint8 chunksCount; s >> chunksCount;
|
||||
if (totalChunksCount() != chunksCount) {
|
||||
m_chunks.clear();
|
||||
}
|
||||
|
||||
set_totalChunksCount(chunksCount);
|
||||
|
||||
quint8 chunkId; s >> chunkId;
|
||||
|
@ -46,6 +100,7 @@ void QrDecoderLogic::onDetectedQrCode(const QString &code)
|
|||
|
||||
if (m_chunks.size() == totalChunksCount()) {
|
||||
QByteArray data;
|
||||
|
||||
for (int i = 0; i < totalChunksCount(); ++i) {
|
||||
data.append(m_chunks.value(i));
|
||||
}
|
||||
|
@ -53,21 +108,18 @@ void QrDecoderLogic::onDetectedQrCode(const QString &code)
|
|||
bool ok = uiLogic()->pageLogic<StartPageLogic>()->importConnectionFromQr(data);
|
||||
if (ok) {
|
||||
set_detectingEnabled(false);
|
||||
emit stopDecode();
|
||||
}
|
||||
else {
|
||||
stopDecodingQr();
|
||||
} else {
|
||||
m_chunks.clear();
|
||||
set_totalChunksCount(0);
|
||||
set_receivedChunksCount(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
bool ok = uiLogic()->pageLogic<StartPageLogic>()->importConnectionFromQr(ba);
|
||||
if (ok) {
|
||||
set_detectingEnabled(false);
|
||||
emit stopDecode();
|
||||
stopDecodingQr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
#include "PageLogicBase.h"
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "jni.h"
|
||||
#endif
|
||||
|
||||
class UiLogic;
|
||||
|
||||
class QrDecoderLogic : public PageLogicBase
|
||||
|
@ -16,10 +20,17 @@ public:
|
|||
Q_INVOKABLE void onUpdatePage() override;
|
||||
Q_INVOKABLE void onDetectedQrCode(const QString &code);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
static void onNewDataChunk(JNIEnv *env, jobject thiz, jstring data);
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit QrDecoderLogic(UiLogic *uiLogic, QObject *parent = nullptr);
|
||||
~QrDecoderLogic() = default;
|
||||
|
||||
private:
|
||||
void stopDecodingQr();
|
||||
|
||||
signals:
|
||||
void startDecode();
|
||||
void stopDecode();
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#ifdef Q_OS_ANDROID
|
||||
#include <QJniObject>
|
||||
#include "../../platforms/android/androidutils.h"
|
||||
#include "../../platforms/android/android_controller.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
@ -184,6 +185,13 @@ void StartPageLogic::onPushButtonImportOpenFile()
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void StartPageLogic::startQrDecoder()
|
||||
{
|
||||
AndroidController::instance()->startQrReaderActivity();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool StartPageLogic::importConnection(const QJsonObject &profile)
|
||||
{
|
||||
ServerCredentials credentials;
|
||||
|
|
|
@ -31,6 +31,10 @@ public:
|
|||
Q_INVOKABLE void onPushButtonImport();
|
||||
Q_INVOKABLE void onPushButtonImportOpenFile();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
Q_INVOKABLE void startQrDecoder();
|
||||
#endif
|
||||
|
||||
bool importConnection(const QJsonObject &profile);
|
||||
bool importConnectionFromCode(QString code);
|
||||
bool importConnectionFromQr(const QByteArray &data);
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtMultimedia
|
||||
import PageEnum 1.0
|
||||
import QZXing 3.2
|
||||
|
||||
import "./"
|
||||
import "../Controls"
|
||||
import "../Config"
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
page: PageEnum.QrDecoder
|
||||
logic: QrDecoderLogic
|
||||
|
||||
onDeactivated: {
|
||||
console.debug("Stopping QR decoder")
|
||||
loader.sourceComponent = undefined
|
||||
}
|
||||
|
||||
BackButton {
|
||||
}
|
||||
Caption {
|
||||
id: caption
|
||||
text: qsTr("Import configuration")
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Qt.platform.os != "ios" ? QrDecoderLogic : null
|
||||
function onStartDecode() {
|
||||
console.debug("Starting QR decoder")
|
||||
loader.sourceComponent = component
|
||||
}
|
||||
function onStopDecode() {
|
||||
console.debug("Stopping QR decoder")
|
||||
loader.sourceComponent = undefined
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
|
||||
anchors.top: caption.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
Component {
|
||||
id: component
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Camera
|
||||
{
|
||||
id:camera
|
||||
focus {
|
||||
focusMode: CameraFocus.FocusContinuous
|
||||
focusPointMode: CameraFocus.FocusPointAuto
|
||||
}
|
||||
}
|
||||
|
||||
VideoOutput
|
||||
{
|
||||
id: videoOutput
|
||||
source: camera
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
autoOrientation: true
|
||||
fillMode: VideoOutput.PreserveAspectFit
|
||||
// filters: [ zxingFilter ]
|
||||
|
||||
|
||||
Rectangle {
|
||||
color: "black"
|
||||
opacity: 0.5
|
||||
width: videoOutput.contentRect.width * 0.15
|
||||
height: videoOutput.contentRect.height
|
||||
x: (videoOutput.width - videoOutput.contentRect.width)/2
|
||||
anchors.verticalCenter: videoOutput.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: "black"
|
||||
opacity: 0.5
|
||||
width: videoOutput.contentRect.width * 0.15
|
||||
height: videoOutput.contentRect.height
|
||||
x: videoOutput.width/2 + videoOutput.contentRect.width/2 - videoOutput.contentRect.width * 0.15
|
||||
anchors.verticalCenter: videoOutput.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: "black"
|
||||
opacity: 0.5
|
||||
width: videoOutput.contentRect.width * 0.7
|
||||
height: videoOutput.contentRect.height * 0.15
|
||||
x: (videoOutput.width - videoOutput.contentRect.width)/2 + videoOutput.contentRect.width * 0.15
|
||||
y: (videoOutput.height - videoOutput.contentRect.height)/2
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: "black"
|
||||
opacity: 0.5
|
||||
width: videoOutput.contentRect.width * 0.7
|
||||
height: videoOutput.contentRect.height * 0.15
|
||||
x: (videoOutput.width - videoOutput.contentRect.width)/2 + videoOutput.contentRect.width * 0.15
|
||||
y: videoOutput.height/2 + videoOutput.contentRect.height/2 - videoOutput.contentRect.height * 0.15
|
||||
}
|
||||
|
||||
LabelType {
|
||||
width: parent.width
|
||||
text: qsTr("Decoded QR chunks " + QrDecoderLogic.receivedChunksCount + "/" + QrDecoderLogic.totalChunksCount)
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
visible: QrDecoderLogic.totalChunksCount > 0
|
||||
anchors.horizontalCenter: videoOutput.horizontalCenter
|
||||
y: videoOutput.height/2 + videoOutput.contentRect.height/2
|
||||
}
|
||||
}
|
||||
|
||||
QZXingFilter
|
||||
{
|
||||
id: zxingFilter
|
||||
orientation: videoOutput.orientation
|
||||
captureRect: {
|
||||
// setup bindings
|
||||
videoOutput.contentRect;
|
||||
videoOutput.sourceRect;
|
||||
return videoOutput.mapRectToSource(videoOutput.mapNormalizedRectToItem(Qt.rect(
|
||||
0.15, 0.15, 0.7, 0.7 //0, 0, 1.0, 1.0
|
||||
)));
|
||||
}
|
||||
|
||||
decoder {
|
||||
enabledDecoders: QZXing.DecoderFormat_QR_CODE
|
||||
|
||||
onTagFound: {
|
||||
QrDecoderLogic.onDetectedQrCode(tag)
|
||||
}
|
||||
|
||||
tryHarder: true
|
||||
}
|
||||
|
||||
property int framesDecoded: 0
|
||||
property real timePerFrameDecode: 0
|
||||
|
||||
onDecodingFinished:
|
||||
{
|
||||
timePerFrameDecode = (decodeTime + framesDecoded * timePerFrameDecode) / (framesDecoded + 1);
|
||||
framesDecoded++;
|
||||
if(succeeded)
|
||||
console.log("frame finished: " + succeeded, decodeTime, timePerFrameDecode, framesDecoded);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -154,7 +154,7 @@ PageBase {
|
|||
if (Qt.platform.os === "ios") {
|
||||
UiLogic.goToPage(PageEnum.QrDecoderIos)
|
||||
} else {
|
||||
UiLogic.goToPage(PageEnum.QrDecoder)
|
||||
StartPageLogic.startQrDecoder()
|
||||
}
|
||||
}
|
||||
enabled: StartPageLogic.pushButtonConnectEnabled
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue