added qr-code decoder for android
- added color change for status and navigation bar for android
This commit is contained in:
parent
b9a13d3a32
commit
0411792ca5
13 changed files with 255 additions and 58 deletions
|
@ -202,31 +202,6 @@ QList<QString> ExportController::getQrCodes()
|
|||
}
|
||||
|
||||
void ExportController::saveFile()
|
||||
{
|
||||
QString fileExtension = ".vpn";
|
||||
QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
|
||||
QUrl fileName;
|
||||
fileName = QFileDialog::getSaveFileUrl(nullptr, tr("Save AmneziaVPN config"),
|
||||
QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), "*" + fileExtension);
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
if (!fileName.toString().endsWith(fileExtension)) {
|
||||
fileName = QUrl(fileName.toString() + fileExtension);
|
||||
}
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
QFile save(fileName.toLocalFile());
|
||||
|
||||
save.open(QIODevice::WriteOnly);
|
||||
save.write(m_config.toUtf8());
|
||||
save.close();
|
||||
|
||||
QFileInfo fi(fileName.toLocalFile());
|
||||
QDesktopServices::openUrl(fi.absoluteDir().absolutePath());
|
||||
}
|
||||
|
||||
void ExportController::shareFile()
|
||||
{
|
||||
#if defined Q_OS_IOS
|
||||
ext.replace("*", "");
|
||||
|
@ -253,6 +228,28 @@ void ExportController::shareFile()
|
|||
AndroidController::instance()->shareConfig(m_config, "amnezia_config");
|
||||
return;
|
||||
#endif
|
||||
|
||||
QString fileExtension = ".vpn";
|
||||
QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
|
||||
QUrl fileName;
|
||||
fileName = QFileDialog::getSaveFileUrl(nullptr, tr("Save AmneziaVPN config"),
|
||||
QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), "*" + fileExtension);
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
if (!fileName.toString().endsWith(fileExtension)) {
|
||||
fileName = QUrl(fileName.toString() + fileExtension);
|
||||
}
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
QFile save(fileName.toLocalFile());
|
||||
|
||||
save.open(QIODevice::WriteOnly);
|
||||
save.write(m_config.toUtf8());
|
||||
save.close();
|
||||
|
||||
QFileInfo fi(fileName.toLocalFile());
|
||||
QDesktopServices::openUrl(fi.absoluteDir().absolutePath());
|
||||
}
|
||||
|
||||
QList<QString> ExportController::generateQrCodeImageSeries(const QByteArray &data)
|
||||
|
|
|
@ -36,7 +36,6 @@ public slots:
|
|||
QList<QString> getQrCodes();
|
||||
|
||||
void saveFile();
|
||||
void shareFile();
|
||||
|
||||
signals:
|
||||
void generateConfig(int type);
|
||||
|
|
|
@ -41,6 +41,11 @@ namespace
|
|||
}
|
||||
return ConfigTypes::Amnezia;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
ImportController *mInstance = nullptr;
|
||||
constexpr auto AndroidCameraActivity = "org.amnezia.vpn.qt.CameraActivity";
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
ImportController::ImportController(const QSharedPointer<ServersModel> &serversModel,
|
||||
|
@ -49,6 +54,7 @@ ImportController::ImportController(const QSharedPointer<ServersModel> &serversMo
|
|||
: QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
mInstance = this;
|
||||
// Set security screen for Android app
|
||||
AndroidUtils::runOnAndroidThreadSync([]() {
|
||||
QJniObject activity = AndroidUtils::getActivity();
|
||||
|
@ -58,6 +64,18 @@ ImportController::ImportController(const QSharedPointer<ServersModel> &serversMo
|
|||
window.callMethod<void>("addFlags", "(I)V", FLAG_SECURE);
|
||||
}
|
||||
});
|
||||
|
||||
AndroidUtils::runOnAndroidThreadAsync([]() {
|
||||
JNINativeMethod methods[] {
|
||||
{ "passDataToDecoder", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onNewQrCodeDataChunk) },
|
||||
};
|
||||
|
||||
QJniObject javaClass(AndroidCameraActivity);
|
||||
QJniEnvironment env;
|
||||
jclass objectClass = env->GetObjectClass(javaClass.object<jobject>());
|
||||
env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0]));
|
||||
env->DeleteLocalRef(objectClass);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -93,11 +111,21 @@ void ImportController::extractConfigFromCode(QString code)
|
|||
m_configFileName = "";
|
||||
}
|
||||
|
||||
void ImportController::extractConfigFromQr()
|
||||
bool ImportController::extractConfigFromQr(const QByteArray &data)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
AndroidController::instance()->startQrReaderActivity();
|
||||
#endif
|
||||
QJsonObject dataObj = QJsonDocument::fromJson(data).object();
|
||||
if (!dataObj.isEmpty()) {
|
||||
m_config = dataObj;
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray ba_uncompressed = qUncompress(data);
|
||||
if (!ba_uncompressed.isEmpty()) {
|
||||
m_config = QJsonDocument::fromJson(ba_uncompressed).object();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString ImportController::getConfig()
|
||||
|
@ -149,21 +177,6 @@ QJsonObject ImportController::extractAmneziaConfig(QString &data)
|
|||
return QJsonDocument::fromJson(ba).object();
|
||||
}
|
||||
|
||||
// bool ImportController::importConnectionFromQr(const QByteArray &data)
|
||||
//{
|
||||
// QJsonObject dataObj = QJsonDocument::fromJson(data).object();
|
||||
// if (!dataObj.isEmpty()) {
|
||||
// return importConnection(dataObj);
|
||||
// }
|
||||
|
||||
// QByteArray ba_uncompressed = qUncompress(data);
|
||||
// if (!ba_uncompressed.isEmpty()) {
|
||||
// return importConnection(QJsonDocument::fromJson(ba_uncompressed).object());
|
||||
// }
|
||||
|
||||
// return false;
|
||||
//}
|
||||
|
||||
QJsonObject ImportController::extractOpenVpnConfig(const QString &data)
|
||||
{
|
||||
QJsonObject openVpnConfig;
|
||||
|
@ -251,3 +264,90 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
|||
|
||||
return config;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void ImportController::startDecodingQr()
|
||||
{
|
||||
AndroidController::instance()->startQrReaderActivity();
|
||||
}
|
||||
|
||||
void ImportController::stopDecodingQr()
|
||||
{
|
||||
QJniObject::callStaticMethod<void>(AndroidCameraActivity, "stopQrCodeReader", "()V");
|
||||
emit qrDecodingFinished();
|
||||
}
|
||||
|
||||
void ImportController::onNewQrCodeDataChunk(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_isQrCodeProcessed) {
|
||||
mInstance->m_qrCodeChunks.clear();
|
||||
mInstance->m_isQrCodeProcessed = true;
|
||||
mInstance->m_totalQrCodeChunksCount = 0;
|
||||
mInstance->m_receivedQrCodeChunksCount = 0;
|
||||
}
|
||||
mInstance->parseQrCodeChunk(parcelBody);
|
||||
}
|
||||
}
|
||||
|
||||
void ImportController::parseQrCodeChunk(const QString &code)
|
||||
{
|
||||
// qDebug() << code;
|
||||
if (!m_isQrCodeProcessed)
|
||||
return;
|
||||
|
||||
// check if chunk received
|
||||
QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
QDataStream s(&ba, QIODevice::ReadOnly);
|
||||
qint16 magic;
|
||||
s >> magic;
|
||||
|
||||
if (magic == amnezia::qrMagicCode) {
|
||||
quint8 chunksCount;
|
||||
s >> chunksCount;
|
||||
if (m_totalQrCodeChunksCount != chunksCount) {
|
||||
m_qrCodeChunks.clear();
|
||||
}
|
||||
|
||||
m_totalQrCodeChunksCount = chunksCount;
|
||||
|
||||
quint8 chunkId;
|
||||
s >> chunkId;
|
||||
s >> m_qrCodeChunks[chunkId];
|
||||
m_receivedQrCodeChunksCount = m_qrCodeChunks.size();
|
||||
|
||||
if (m_qrCodeChunks.size() == m_totalQrCodeChunksCount) {
|
||||
QByteArray data;
|
||||
|
||||
for (int i = 0; i < m_totalQrCodeChunksCount; ++i) {
|
||||
data.append(m_qrCodeChunks.value(i));
|
||||
}
|
||||
|
||||
bool ok = extractConfigFromQr(data);
|
||||
if (ok) {
|
||||
m_isQrCodeProcessed = false;
|
||||
stopDecodingQr();
|
||||
} else {
|
||||
m_qrCodeChunks.clear();
|
||||
m_totalQrCodeChunksCount = 0;
|
||||
m_receivedQrCodeChunksCount = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bool ok = extractConfigFromQr(ba);
|
||||
if (ok) {
|
||||
m_isQrCodeProcessed = false;
|
||||
stopDecodingQr();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
#include "core/defs.h"
|
||||
#include "ui/models/containers_model.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "jni.h"
|
||||
#endif
|
||||
|
||||
class ImportController : public QObject
|
||||
{
|
||||
|
@ -21,25 +24,44 @@ public slots:
|
|||
void extractConfigFromFile();
|
||||
void extractConfigFromData(QString &data);
|
||||
void extractConfigFromCode(QString code);
|
||||
void extractConfigFromQr();
|
||||
bool extractConfigFromQr(const QByteArray &data);
|
||||
QString getConfig();
|
||||
QString getConfigFileName();
|
||||
|
||||
#if defined Q_OS_ANDROID
|
||||
void startDecodingQr();
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void importFinished();
|
||||
void importErrorOccurred(QString errorMessage);
|
||||
|
||||
void qrDecodingFinished();
|
||||
|
||||
private:
|
||||
QJsonObject extractAmneziaConfig(QString &data);
|
||||
QJsonObject extractOpenVpnConfig(const QString &data);
|
||||
QJsonObject extractWireGuardConfig(const QString &data);
|
||||
|
||||
#if defined Q_OS_ANDROID
|
||||
void stopDecodingQr();
|
||||
static void onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring data);
|
||||
void parseQrCodeChunk(const QString &code);
|
||||
#endif
|
||||
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
QSharedPointer<ContainersModel> m_containersModel;
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
|
||||
QJsonObject m_config;
|
||||
QString m_configFileName;
|
||||
|
||||
#if defined Q_OS_ANDROID
|
||||
QMap<int, QByteArray> m_qrCodeChunks;
|
||||
bool m_isQrCodeProcessed;
|
||||
int m_totalQrCodeChunksCount;
|
||||
int m_receivedQrCodeChunksCount;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // IMPORTCONTROLLER_H
|
||||
|
|
|
@ -1,10 +1,28 @@
|
|||
#include "pageController.h"
|
||||
|
||||
#include <QApplication>
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "../../platforms/android/androidutils.h"
|
||||
#include <QJniObject>
|
||||
#endif
|
||||
|
||||
PageController::PageController(const QSharedPointer<ServersModel> &serversModel, QObject *parent)
|
||||
: QObject(parent), m_serversModel(serversModel)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
// Change color of navigation and status bar's
|
||||
auto initialPageNavigationBarColor = getInitialPageNavigationBarColor();
|
||||
AndroidUtils::runOnAndroidThreadSync([&initialPageNavigationBarColor]() {
|
||||
QJniObject activity = AndroidUtils::getActivity();
|
||||
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
|
||||
if (window.isValid()) {
|
||||
window.callMethod<void>("addFlags", "(I)V", 0x80000000);
|
||||
window.callMethod<void>("clearFlags", "(I)V", 0x04000000);
|
||||
window.callMethod<void>("setStatusBarColor", "(I)V", 0xFF0E0E11);
|
||||
window.callMethod<void>("setNavigationBarColor", "(I)V", initialPageNavigationBarColor);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
QString PageController::getInitialPage()
|
||||
|
@ -47,3 +65,26 @@ void PageController::keyPressEvent(Qt::Key key)
|
|||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int PageController::getInitialPageNavigationBarColor()
|
||||
{
|
||||
if (m_serversModel->getServersCount()) {
|
||||
return 0xFF1C1D21;
|
||||
} else {
|
||||
return 0xFF0E0E11;
|
||||
}
|
||||
}
|
||||
|
||||
void PageController::updateNavigationBarColor(const int color)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
// Change color of navigation bar
|
||||
AndroidUtils::runOnAndroidThreadSync([&color]() {
|
||||
QJniObject activity = AndroidUtils::getActivity();
|
||||
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
|
||||
if (window.isValid()) {
|
||||
window.callMethod<void>("setNavigationBarColor", "(I)V", color);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -71,6 +71,9 @@ public slots:
|
|||
void closeWindow();
|
||||
void keyPressEvent(Qt::Key key);
|
||||
|
||||
unsigned int getInitialPageNavigationBarColor();
|
||||
void updateNavigationBarColor(const int color);
|
||||
|
||||
signals:
|
||||
void goToPageHome();
|
||||
void goToPageSettings();
|
||||
|
|
|
@ -54,10 +54,10 @@ DrawerType {
|
|||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save connection code")
|
||||
text: qsTr("Share")
|
||||
|
||||
onClicked: {
|
||||
Qt.platform.os === "android" ? ExportController.shareFile() : ExportController.saveFile()
|
||||
ExportController.saveFile()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ Drawer {
|
|||
velocity: 4
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
SmoothedAnimation {
|
||||
velocity: 4
|
||||
|
@ -31,4 +32,17 @@ Drawer {
|
|||
Overlay.modal: Rectangle {
|
||||
color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
|
||||
}
|
||||
|
||||
onAboutToShow: {
|
||||
if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) {
|
||||
PageController.updateNavigationBarColor(0xFF1C1D21)
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor()
|
||||
if (initialPageNavigationBarColor !== 0xFF1C1D21) {
|
||||
PageController.updateNavigationBarColor(initialPageNavigationBarColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,14 @@ import "../Config"
|
|||
PageType {
|
||||
id: root
|
||||
|
||||
Connections {
|
||||
target: ImportController
|
||||
|
||||
function onQrDecodingFinished() {
|
||||
goToPage(PageEnum.PageSetupWizardViewConfig)
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: fl
|
||||
anchors.top: parent.top
|
||||
|
@ -77,7 +85,7 @@ It's okay if a friend passed the code.")
|
|||
leftImageSource: "qrc:/images/controls/qr-code.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
ImportController.extractConfigFromQr()
|
||||
ImportController.startDecodingQr()
|
||||
// goToPage(PageEnum.PageSetupWizardQrReader)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,8 +47,7 @@ PageType {
|
|||
} else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) {
|
||||
goToPage(PageEnum.PageSettingsServersList, false)
|
||||
} else {
|
||||
var pagePath = PageController.getPagePath(PageEnum.PageStart)
|
||||
stackView.replace(pagePath, { "objectName" : pagePath })
|
||||
PageController.replaceStartPage()
|
||||
}
|
||||
|
||||
if (isInstalledContainerFound) {
|
||||
|
|
|
@ -30,8 +30,7 @@ PageType {
|
|||
} else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) {
|
||||
goToPage(PageEnum.PageSettingsServersList, false)
|
||||
} else {
|
||||
var pagePath = PageController.getPagePath(PageEnum.PageStart)
|
||||
stackView.replace(pagePath, { "objectName" : pagePath })
|
||||
PageController.replaceStartPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Shapes
|
||||
|
||||
import PageEnum 1.0
|
||||
|
||||
|
@ -81,14 +82,27 @@ PageType {
|
|||
anchors.bottom: parent.bottom
|
||||
|
||||
topPadding: 8
|
||||
bottomPadding: 8//34
|
||||
bottomPadding: 8
|
||||
leftPadding: shareTabButton.visible ? 96 : 128
|
||||
rightPadding: shareTabButton.visible ? 96 : 128
|
||||
|
||||
background: Rectangle {
|
||||
border.width: 1
|
||||
border.color: "#2C2D30"
|
||||
color: "#1C1D21"
|
||||
background: Shape {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
ShapePath {
|
||||
startX: 0
|
||||
startY: 0
|
||||
|
||||
PathLine { x: width; y: 0 }
|
||||
PathLine { x: width; y: height - 1 }
|
||||
PathLine { x: 0; y: height - 1 }
|
||||
PathLine { x: 0; y: 0 }
|
||||
|
||||
strokeWidth: 1
|
||||
strokeColor: "#2C2D30"
|
||||
fillColor: "#1C1D21"
|
||||
}
|
||||
}
|
||||
|
||||
TabImageButtonType {
|
||||
|
|
|
@ -50,6 +50,7 @@ Window {
|
|||
while (rootStackView.depth > 1) {
|
||||
rootStackView.pop()
|
||||
}
|
||||
PageController.updateNavigationBarColor(PageController.getInitialPageNavigationBarColor())
|
||||
rootStackView.replace(pagePath, { "objectName" : pagePath })
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue