diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 09a8775b..22c828fb 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -34,6 +34,7 @@ android:extractNativeLibs="true" android:requestLegacyExternalStorage="true" android:allowNativeHeapPointerTagging="false" + android:theme="@style/Theme.AppCompat.NoActionBar" android:icon="@drawable/icon"> + + + + + + + \ No newline at end of file diff --git a/client/android/settings.gradle b/client/android/settings.gradle index ba31e000..9256d846 100644 --- a/client/android/settings.gradle +++ b/client/android/settings.gradle @@ -1 +1,19 @@ +pluginManagement { + repositories { + google() + mavenCentral() + jcenter() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + jcenter() + } +} + include ':shadowsocks' diff --git a/client/android/shadowsocks/build.gradle b/client/android/shadowsocks/build.gradle index d35daa11..d87139fe 100644 --- a/client/android/shadowsocks/build.gradle +++ b/client/android/shadowsocks/build.gradle @@ -1,21 +1,9 @@ - -allprojects { - repositories { - google() - jcenter() - mavenCentral() - } -} - - apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' //apply plugin: 'com.novoda.bintray-release' - - android { compileSdkVersion 30 defaultConfig { diff --git a/client/android/src/org/amnezia/vpn/qt/CameraActivity.kt b/client/android/src/org/amnezia/vpn/qt/CameraActivity.kt new file mode 100644 index 00000000..8416d97c --- /dev/null +++ b/client/android/src/org/amnezia/vpn/qt/CameraActivity.kt @@ -0,0 +1,158 @@ +package org.amnezia.vpn.qt + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.core.* +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.core.content.ContextCompat +import com.google.mlkit.vision.barcode.BarcodeScannerOptions +import com.google.mlkit.vision.barcode.BarcodeScanning +import com.google.mlkit.vision.barcode.common.Barcode +import com.google.mlkit.vision.common.InputImage +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import org.amnezia.vpn.R + + +class CameraActivity : AppCompatActivity() { + + private val CAMERA_REQUEST = 100 + + private lateinit var cameraExecutor: ExecutorService + private lateinit var analyzerExecutor: ExecutorService + + 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) + + viewFinder = findViewById(R.id.viewFinder) + + cameraExecutor = Executors.newSingleThreadExecutor() + analyzerExecutor = Executors.newSingleThreadExecutor() + + instance = this + + checkPermissions() + + configureVideoPreview() + } + + private fun checkPermissions() { + if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(arrayOf(Manifest.permission.CAMERA), CAMERA_REQUEST) + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == CAMERA_REQUEST) { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, "CameraX permission granted", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, "CameraX permission denied", Toast.LENGTH_SHORT).show(); + } + } + } + + @SuppressLint("UnsafeOptInUsageError") + private fun configureVideoPreview() { + val cameraProviderFuture = ProcessCameraProvider.getInstance(this) + val imageCapture = ImageCapture.Builder().build() + + cameraProviderFuture.addListener({ + val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() + + val preview = Preview.Builder().build() + + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + + val imageAnalyzer = BarCodeAnalyzer() + + val analysisUseCase = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + + analysisUseCase.setAnalyzer(analyzerExecutor, imageAnalyzer) + + try { + preview.setSurfaceProvider(viewFinder.surfaceProvider) + cameraProvider.unbindAll() + cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, analysisUseCase) + } catch(exc: Exception) { + Log.e("WUTT", "Use case binding failed", exc) + } + }, ContextCompat.getMainExecutor(this)) + } + + override fun onDestroy() { + cameraExecutor.shutdown() + analyzerExecutor.shutdown() + + super.onDestroy() + } + + val barcodesSet = mutableSetOf() + + private inner class BarCodeAnalyzer(): ImageAnalysis.Analyzer { + + private val options = BarcodeScannerOptions.Builder() + .setBarcodeFormats(Barcode.FORMAT_QR_CODE) + .build() + + private val scanner = BarcodeScanning.getClient(options) + + @SuppressLint("UnsafeOptInUsageError") + override fun analyze(imageProxy: ImageProxy) { + val mediaImage = imageProxy.image + + if (mediaImage != null) { + val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) + + scanner.process(image) + .addOnSuccessListener { barcodes -> + if (barcodes.isNotEmpty()) { + 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 { + imageProxy.close() + } + } + } + } +} \ No newline at end of file diff --git a/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt b/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt index 673c4b59..b5e8d5fb 100644 --- a/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt +++ b/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt @@ -36,6 +36,8 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { private val TAG = "VPNActivity" private val STORAGE_PERMISSION_CODE = 42 + private val CAMERA_ACTION_CODE = 101 + companion object { private lateinit var instance: VPNActivity @@ -47,6 +49,10 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { VPNActivity.getInstance().initServiceConnection() } + @JvmStatic fun startQrCodeReader() { + VPNActivity.getInstance().startQrCodeActivity() + } + @JvmStatic fun sendToService(actionCode: Int, body: String) { VPNActivity.getInstance().dispatchParcel(actionCode, body) } @@ -62,7 +68,12 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { super.onCreate(savedInstanceState) - instance = this; + instance = this + } + + private fun startQrCodeActivity() { + val intent = Intent(this, CameraActivity::class.java) + startActivityForResult(intent, CAMERA_ACTION_CODE) } override fun getSystemService(name: String): Any? { @@ -82,6 +93,7 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { external fun onServiceMessage(actionCode: Int, body: String?) external fun qtOnServiceConnected() external fun qtOnServiceDisconnected() + external fun onActivityMessage(actionCode: Int, body: String?) private fun dispatchParcel(actionCode: Int, body: String) { if (!isBound) { @@ -286,6 +298,7 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { private val EVENT_PERMISSION_REQURED = 6 private val EVENT_DISCONNECTED = 2 + private val UI_EVENT_QR_CODE_RECEIVED = 0 fun onPermissionRequest(code: Int, data: Parcel?) { if (code != EVENT_PERMISSION_REQURED) { @@ -310,7 +323,13 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { } return } + super.onActivityResult(requestCode, resultCode, data) + + if (requestCode == CAMERA_ACTION_CODE && resultCode == RESULT_OK) { + val extra = data?.getStringExtra("result") ?: "" + onActivityMessage(UI_EVENT_QR_CODE_RECEIVED, extra) + } } override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index b25fce78..a86ab7ca 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -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]() { diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index ddaa0d79..59ecd9b3 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -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; diff --git a/client/platforms/android/androidvpnactivity.cpp b/client/platforms/android/androidvpnactivity.cpp index 0e8b9574..17639023 100644 --- a/client/platforms/android/androidvpnactivity.cpp +++ b/client/platforms/android/androidvpnactivity.cpp @@ -22,12 +22,10 @@ AndroidVPNActivity::AndroidVPNActivity() { AndroidUtils::runOnAndroidThreadAsync([]() { JNINativeMethod methods[]{ {"handleBackButton", "()Z", reinterpret_cast(handleBackButton)}, - {"onServiceMessage", "(ILjava/lang/String;)V", - reinterpret_cast(onServiceMessage)}, - {"qtOnServiceConnected", "()V", - reinterpret_cast(onServiceConnected)}, - {"qtOnServiceDisconnected", "()V", - reinterpret_cast(onServiceDisconnected)}, + {"onServiceMessage", "(ILjava/lang/String;)V", reinterpret_cast(onServiceMessage)}, + {"qtOnServiceConnected", "()V", reinterpret_cast(onServiceConnected)}, + {"qtOnServiceDisconnected", "()V", reinterpret_cast(onServiceDisconnected)}, + {"onActivityMessage", "(ILjava/lang/String;)V", reinterpret_cast(onAndroidVpnActivityMessage)} }; QJniObject javaClass(CLASSNAME); @@ -54,6 +52,11 @@ void AndroidVPNActivity::connectService() { QJniObject::callStaticMethod(CLASSNAME, "connectService", "()V"); } +void AndroidVPNActivity::startQrCodeReader() +{ + QJniObject::callStaticMethod(CLASSNAME, "startQrCodeReader", "()V"); +} + // static AndroidVPNActivity* AndroidVPNActivity::instance() { if (s_instance == nullptr) { @@ -121,6 +124,19 @@ void AndroidVPNActivity::handleServiceMessage(int code, const QString& data) { } } +void AndroidVPNActivity::handleActivityMessage(int code, const QString &data) +{ + auto mode = (UIEvents)code; + + switch (mode) { + case UIEvents::QR_CODED_DECODED: + emit eventQrCodeReceived(data); + break; + default: + Q_ASSERT(false); + } +} + void AndroidVPNActivity::onServiceConnected(JNIEnv* env, jobject thiz) { Q_UNUSED(env); Q_UNUSED(thiz); @@ -134,3 +150,19 @@ void AndroidVPNActivity::onServiceDisconnected(JNIEnv* env, jobject thiz) { emit AndroidVPNActivity::instance()->serviceDisconnected(); } + +void AndroidVPNActivity::onAndroidVpnActivityMessage(JNIEnv *env, jobject thiz, jint messageType, jstring message) +{ + Q_UNUSED(thiz); + const char* buffer = env->GetStringUTFChars(message, nullptr); + if (!buffer) { + return; + } + + QString parcelBody(buffer); + env->ReleaseStringUTFChars(message, buffer); + + AndroidUtils::dispatchToMainThread([messageType, parcelBody] { + AndroidVPNActivity::instance()->handleActivityMessage(messageType, parcelBody); + }); +} diff --git a/client/platforms/android/androidvpnactivity.h b/client/platforms/android/androidvpnactivity.h index 49d1aae5..1bc1a522 100644 --- a/client/platforms/android/androidvpnactivity.h +++ b/client/platforms/android/androidvpnactivity.h @@ -59,6 +59,11 @@ enum ServiceEvents { }; typedef enum ServiceEvents ServiceEvents; +enum UIEvents { + QR_CODED_DECODED = 0, +}; +typedef enum UIEvents UIEvents; + class AndroidVPNActivity : public QObject { Q_OBJECT @@ -69,6 +74,7 @@ public: static bool handleBackButton(JNIEnv* env, jobject thiz); static void sendToService(ServiceAction type, const QString& data); static void connectService(); + static void startQrCodeReader(); signals: void serviceConnected(); @@ -80,6 +86,7 @@ signals: void eventBackendLogs(const QString& data); void eventActivationError(const QString& data); void eventConfigImport(const QString& data); + void eventQrCodeReceived(const QString& data); private: AndroidVPNActivity(); @@ -87,7 +94,9 @@ private: static void onServiceMessage(JNIEnv* env, jobject thiz, jint messageType, jstring body); static void onServiceConnected(JNIEnv* env, jobject thiz); static void onServiceDisconnected(JNIEnv* env, jobject thiz); + static void onAndroidVpnActivityMessage(JNIEnv* env, jobject thiz, jint messageType, jstring message); void handleServiceMessage(int code, const QString& data); + void handleActivityMessage(int code, const QString& data); }; #endif // ANDROIDVPNACTIVITY_H diff --git a/client/resources.qrc b/client/resources.qrc index 4651f91e..fd26f5ad 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -81,7 +81,6 @@ ui/qml/Pages/PageSites.qml ui/qml/Pages/PageStart.qml ui/qml/Pages/PageVPN.qml - ui/qml/Pages/PageQrDecoder.qml ui/qml/Pages/PageAbout.qml ui/qml/Pages/PageQrDecoderIos.qml ui/qml/Pages/PageViewConfig.qml diff --git a/client/ui/pages_logic/QrDecoderLogic.cpp b/client/ui/pages_logic/QrDecoderLogic.cpp index 764a6e4b..e1845c77 100644 --- a/client/ui/pages_logic/QrDecoderLogic.cpp +++ b/client/ui/pages_logic/QrDecoderLogic.cpp @@ -3,15 +3,70 @@ #include "ui/uilogic.h" #include "ui/pages_logic/StartPageLogic.h" +#ifdef Q_OS_ANDROID +#include +#include +#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(onNewDataChunk)}, + }; + + QJniObject javaClass(CLASSNAME); + QJniEnvironment env; + jclass objectClass = env->GetObjectClass(javaClass.object()); + env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); + env->DeleteLocalRef(objectClass); + }); + #endif } +void QrDecoderLogic::stopDecodingQr() +{ + #if (defined(Q_OS_ANDROID)) + QJniObject::callStaticMethod(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()->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()->importConnectionFromQr(ba); if (ok) { set_detectingEnabled(false); - emit stopDecode(); + stopDecodingQr(); } } } - diff --git a/client/ui/pages_logic/QrDecoderLogic.h b/client/ui/pages_logic/QrDecoderLogic.h index 243da95b..2b24bf27 100644 --- a/client/ui/pages_logic/QrDecoderLogic.h +++ b/client/ui/pages_logic/QrDecoderLogic.h @@ -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(); diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp index 3c15199d..06febc05 100644 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ b/client/ui/pages_logic/StartPageLogic.cpp @@ -13,6 +13,7 @@ #ifdef Q_OS_ANDROID #include #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; diff --git a/client/ui/pages_logic/StartPageLogic.h b/client/ui/pages_logic/StartPageLogic.h index 183d0bd3..b3dea002 100644 --- a/client/ui/pages_logic/StartPageLogic.h +++ b/client/ui/pages_logic/StartPageLogic.h @@ -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); diff --git a/client/ui/qml/Pages/PageQrDecoder.qml b/client/ui/qml/Pages/PageQrDecoder.qml deleted file mode 100644 index 9a4b26f5..00000000 --- a/client/ui/qml/Pages/PageQrDecoder.qml +++ /dev/null @@ -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); - } - } - - - } - - } - - -} diff --git a/client/ui/qml/Pages/PageStart.qml b/client/ui/qml/Pages/PageStart.qml index d5e67e48..eacb607d 100644 --- a/client/ui/qml/Pages/PageStart.qml +++ b/client/ui/qml/Pages/PageStart.qml @@ -154,7 +154,7 @@ PageBase { if (Qt.platform.os === "ios") { UiLogic.goToPage(PageEnum.QrDecoderIos) } else { - UiLogic.goToPage(PageEnum.QrDecoder) + StartPageLogic.startQrDecoder() } } enabled: StartPageLogic.pushButtonConnectEnabled diff --git a/deploy/build_android.sh b/deploy/build_android.sh index 4357034f..d9d8f5c7 100644 --- a/deploy/build_android.sh +++ b/deploy/build_android.sh @@ -51,7 +51,7 @@ $QT_HOST_PATH/bin/androiddeployqt \ --gradle \ --release \ --input android-AmneziaVPN-deployment-settings.json \ - --android-platform android-31 + --android-platform android-33 echo "............Copy apk.................." cp $OUT_APP_DIR/android-build/build/outputs/apk/release/android-build-release-unsigned.apk \