refactor: QR device transfer flow and UI tweaks

This commit is contained in:
Mitternacht822
2026-01-29 18:51:42 +04:00
parent 832fea3d50
commit dec1f51fd6
8 changed files with 312 additions and 333 deletions

View File

@@ -587,7 +587,6 @@ bool ImportController::decodeQrCode(const QString &code)
}
return mInstance->parseQrCodeChunk(code);*/
//new version for transferController Qr
QMutexLocker lock(&qrDecodeMutex);
if (!mInstance->m_isQrCodeProcessed) {
@@ -597,17 +596,13 @@ bool ImportController::decodeQrCode(const QString &code)
mInstance->m_receivedQrCodeChunksCount = 0;
}
// trying as old QR with config chunks
if (mInstance->parseQrCodeChunk(code)) {
return true;
}
//if not chinked-QR, trying transferController QR
// First, try transferController QR (plain JSON payload).
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(code.toUtf8(), &err);
if (err.error == QJsonParseError::NoError && doc.isObject()) {
const QJsonObject obj = doc.object();
if (obj.contains(QStringLiteral("gw")) && obj.contains(QStringLiteral("u"))) {
if (obj.contains(QStringLiteral("gw"))
&& (obj.contains(QStringLiteral("uuid")) || obj.contains(QStringLiteral("u")))) {
// это наш QR для передачи устройства
mInstance->m_isQrCodeProcessed = false; // reset state
emit mInstance->transferQrDecoded(code); // send string to QML
@@ -615,6 +610,10 @@ bool ImportController::decodeQrCode(const QString &code)
}
}
if (mInstance->parseQrCodeChunk(code)) {
return true;
}
return false;
}
#endif

View File

@@ -8,6 +8,7 @@
#include <QNetworkProxyQuery>
#include <QUrl>
#include "core/api/apiUtils.h"
#include "core/qrCodeUtils.h"
#include "amnezia_application.h"
#include "settings.h"
@@ -34,7 +35,6 @@ namespace {
.arg(p.hostName())
.arg(p.port());
}
qDebug() << "TransferController: system proxies for" << urlStr << ":" << proxyDesc;
}
}
@@ -44,7 +44,6 @@ TransferController::TransferController(const std::shared_ptr<Settings> &settings
QObject *parent)
: QObject(parent), m_settings(settings), m_serversModel(serversModel), m_exportController(exportController)
{
qDebug() << "TransferController created";
}
void TransferController::handleImportControllerDestroyed()
@@ -61,35 +60,36 @@ QString TransferController::buildQrPayloadJson(const QString &gatewayUrl, const
QJsonObject obj;
obj["gw"] = gatewayUrl;
obj["uuid"] = uuid;
qDebug() << "built QrPayload with GW = " << gatewayUrl
<< " uuid = " << uuid;
// Used on the sender side for human-friendly notifications (same style as "Active Devices" list).
#if defined(Q_OS_ANDROID)
obj["name"] = QStringLiteral("Android");
#elif defined(Q_OS_IOS)
obj["name"] = QStringLiteral("iOS");
#elif defined(Q_OS_WIN)
obj["name"] = QStringLiteral("Windows");
#elif defined(Q_OS_MACOS)
obj["name"] = QStringLiteral("macOS");
#elif defined(Q_OS_LINUX)
obj["name"] = QStringLiteral("Linux");
#else
obj["name"] = QStringLiteral("Device");
#endif
return QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact));
}
void TransferController::generateNewQrCode()
{
// Debug mode: keep UUID/payload generation, but disable actual QR rendering (temporary).
qDebug() << "TransferController::generateNewQrCode: generating transfer payload (QR rendering disabled)";
QString gw = m_settings->getGatewayEndpoint();
if (!gw.endsWith('/')) {
gw.append('/');
}
qDebug() << "gateway:" << gw;
m_currentUuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
qDebug() << "uuid:" << m_currentUuid;
m_currentPayload = buildQrPayloadJson(gw, m_currentUuid);
// QR generation disabled for debugging/CLI-style copy-paste flow.
// If/when QR is re-enabled, restore this block:
//
// auto qr = qrCodeUtils::generateQrCode(m_currentPayload.toUtf8());
// const QString svg = QString::fromStdString(toSvgString(qr, 1));
// m_qrCodeUrl = qrCodeUtils::svgToBase64(svg);
// emit qrCodeUpdated();
m_qrCodeUrl.clear();
auto qr = qrCodeUtils::generateQrCode(m_currentPayload.toUtf8());
const QString svg = QString::fromStdString(toSvgString(qr, 1));
m_qrCodeUrl = qrCodeUtils::svgToBase64(svg);
emit qrCodeUpdated();
emit currentUuidChanged();
emit currentPayloadChanged();
@@ -97,7 +97,6 @@ void TransferController::generateNewQrCode()
void TransferController::stopScanner()
{
qDebug() << "TransferController::stopScanner: emitting scannerShouldStop";
emit scannerShouldStop();
}
@@ -110,8 +109,6 @@ QString TransferController::getCurrentApiKey(QString *vpnKeyOut) const
const QJsonObject server = m_serversModel->getServerConfig(idx);
qDebug() << "server:" << server;
const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject();
const QJsonObject authData = server.value(QStringLiteral("auth_data")).toObject();
@@ -120,7 +117,6 @@ QString TransferController::getCurrentApiKey(QString *vpnKeyOut) const
if (vpnKeyOut) {
QString vpnKey = apiConfig.value(apiDefs::key::vpnKey).toString();
if (vpnKey.isEmpty()) {
// Fallback for older Premium V1 configs where vpn_key may be derived.
vpnKey = apiUtils::getPremiumV1VpnKey(server);
}
*vpnKeyOut = vpnKey;
@@ -131,8 +127,6 @@ QString TransferController::getCurrentApiKey(QString *vpnKeyOut) const
void TransferController::onTransferQrScanned(const QString &code)
{
qDebug() << "TransferController has scanned the Qr";
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(code.toUtf8(), &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
@@ -154,35 +148,66 @@ void TransferController::onTransferQrScanned(const QString &code)
gw.append('/');
}
int chosenServerIdx = -1;
QString apiKey;
QString vpnKey;
const QString apiKey = getCurrentApiKey(&vpnKey);
qDebug() << "scanned apiKey:" << apiKey;
if (apiKey.isEmpty()) {
qWarning() << "TransferController::onTransferQrScanned: no subscription key or config to send";
emit postFailed(QStringLiteral("No subscription key or config to send"));
return;
auto tryServerIndex = [&](int idx) -> bool {
if (!m_serversModel || idx < 0 || idx >= m_serversModel->getServersCount()) {
return false;
}
const QJsonObject server = m_serversModel->getServerConfig(idx);
const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject();
const QJsonObject authData = server.value(QStringLiteral("auth_data")).toObject();
const QString candidateApiKey = authData.value(QStringLiteral("api_key")).toString();
QString candidateVpnKey = apiConfig.value(apiDefs::key::vpnKey).toString();
if (candidateVpnKey.isEmpty()) {
// Fallback for older Premium V1 configs where vpn_key may be derived.
candidateVpnKey = apiUtils::getPremiumV1VpnKey(server);
}
const bool candidateIsPremium = apiUtils::isPremiumServer(server);
const bool candidateIsFromGatewayApi = m_serversModel->data(idx, ServersModel::IsServerFromGatewayApiRole).toBool();
if (candidateApiKey.isEmpty() || candidateVpnKey.isEmpty()) {
return false;
}
if (!candidateIsPremium && !candidateIsFromGatewayApi) {
return false;
}
chosenServerIdx = idx;
apiKey = candidateApiKey;
vpnKey = candidateVpnKey;
return true;
};
if (m_serversModel) {
tryServerIndex(m_serversModel->getProcessedServerIndex());
if (chosenServerIdx < 0) {
tryServerIndex(m_serversModel->getDefaultServerIndex());
}
if (chosenServerIdx < 0) {
const int n = m_serversModel->getServersCount();
for (int i = 0; i < n; i++) {
if (tryServerIndex(i)) {
break;
}
}
}
}
bool isPremium = m_serversModel && m_serversModel->processedServerIsPremium();
qDebug() << "isPremium: " << isPremium;
bool isFromGatewayApi = m_serversModel && m_serversModel->getProcessedServerData("isServerFromGatewayApi").toBool();
qDebug() << "is from gatewayApi: " << isFromGatewayApi;
if (!isPremium && !isFromGatewayApi) {
qWarning() << "TransferController::onTransferQrScanned: premium subscription required";
emit postFailed(QStringLiteral("Premium subscription required"));
if (chosenServerIdx < 0) {
qWarning() << "TransferController::onTransferQrScanned: no suitable subscription key/config found to send";
emit postFailed(QStringLiteral("No subscription key or config to send"));
return;
}
emit postStarted();
if (vpnKey.isEmpty()) {
qWarning() << "TransferController::onTransferQrScanned: missing vpn_key";
emit postFailed(QStringLiteral("Missing vpn_key to send"));
return;
}
// sendConfig can take longer on slower networks / debug setups, so use a bigger timeout than generic API calls.
const int sendTimeoutMs = 120000;
const int sendTimeoutMs = 60000;
GatewayController gatewayController(gw,
m_settings->isDevGatewayEnv(),
sendTimeoutMs,
@@ -236,7 +261,6 @@ void TransferController::startWaitForConfig(ImportController *importController)
}
const QString uuid = m_currentUuid;
qDebug() << "TransferController::startWaitForConfig: starting blocking wait with uuid: " << uuid;
if (uuid.isEmpty()) {
qWarning() << "TransferController::startWaitForConfig: no uuid";
@@ -252,7 +276,7 @@ void TransferController::startWaitForConfig(ImportController *importController)
Qt::UniqueConnection);
}
const int waitTimeoutMs = 300000;
const int waitTimeoutMs = 60000;
QJsonObject payload;
payload.insert(QStringLiteral("uuid"), uuid);
@@ -265,8 +289,6 @@ void TransferController::startWaitForConfig(ImportController *importController)
const QString endpoint = QStringLiteral("%1v1/waitConfig");
QByteArray responseBody;
const QString fullUrl = endpoint.arg(gw);
qDebug() << "TransferController::startWaitForConfig: POST" << fullUrl
<< "uuid:" << uuid;
logSystemProxiesForUrl(fullUrl);
const auto errorCode = gatewayController.post(endpoint, payload, responseBody);

View File

@@ -14,9 +14,18 @@ import "../Config"
PageType {
id: root
// Debug-friendly: don't auto-close on send; keep the page responsive and show status.
property bool isSending: false
function getAddedDeviceName() {
try {
var obj = JSON.parse(TransferController.pendingQrCode)
if (obj && obj.name && obj.name.length > 0) {
return obj.name
}
} catch (e) {}
return qsTr("Device")
}
function getAvailableCount() {
var max = ApiAccountInfoModel.data("maxDeviceCount")
var active = ApiAccountInfoModel.data("activeDeviceCount")
@@ -38,8 +47,6 @@ PageType {
BackButtonType {
backButtonFunction: function() {
if (root.isSending) {
// We cannot truly abort GatewayController::post() without changing it,
// but we can at least stop waiting on the other side and let the user navigate.
TransferController.stopWaitForConfig()
}
PageController.closePage()
@@ -51,8 +58,8 @@ PageType {
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Share VPN with a new device?")
descriptionText: qsTr("Your Amnezia Premium subscription can connect %1 more devices").arg(getAvailableCount())
headerText: qsTr("Add a new device to the subscription?")
descriptionText: qsTr("Devices available with Amnezia Premium: (%1)").arg(getAvailableCount())
}
BasicButtonType {
@@ -61,7 +68,7 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 16
text: qsTr("Yes, share")
text: qsTr("Add Device")
enabled: !root.isSending && root.getAvailableCount() > 0 && TransferController.pendingQrCode !== ""
clickedFunc: function() {
@@ -104,7 +111,8 @@ PageType {
function onPostSucceeded() {
root.isSending = false
PageController.showNotificationMessage(qsTr("Configuration sent successfully"))
PageController.showNotificationMessage(qsTr("%1 has been added to your subscription").arg(root.getAddedDeviceName()))
PageController.closePage()
PageController.closePage()
}

View File

@@ -4,6 +4,7 @@ import QtQuick.Layouts
import QtQuick.Dialogs
import PageEnum 1.0
import QRCodeReader 1.0
import Style 1.0
import "./"
@@ -14,159 +15,80 @@ import "../Config"
PageType {
id: root
BackButtonType {
id: backButton
anchors.left: parent.left
anchors.top: parent.top
property bool scanCompleted: false
anchors.topMargin: 20
Item {
id: cameraArea
anchors.fill: parent
}
ParagraphTextType {
id: header
Loader {
id: iosQrLoader
anchors.fill: cameraArea
active: Qt.platform.os === "ios"
property string progressString
sourceComponent: Component {
QRCodeReader {
id: qrCodeReader
anchors.left: parent.left
anchors.top: backButton.bottom
anchors.right: parent.right
function updateCameraSize() {
qrCodeReader.setCameraSize(Qt.rect(cameraArea.x,
cameraArea.y,
cameraArea.width,
cameraArea.height))
}
anchors.leftMargin: 16
anchors.rightMargin: 16
onCodeReaded: function(code) {
if (!code || code.length === 0) {
return
}
text: qsTr("Debug mode: paste payload JSON from the other device (or enter UUID). ") + progressString
}
var obj = null
try {
obj = JSON.parse(code)
} catch (e) {
obj = null
}
ProgressBarType {
id: progressBar
if (!obj || !obj.gw || !(obj.uuid || obj.u)) {
return
}
anchors.left: parent.left
anchors.top: header.bottom
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
}
// Debug: manual payload/UUID input (temporary instead of camera scanning)
ColumnLayout {
id: manualInput
anchors.left: parent.left
anchors.right: parent.right
anchors.top: progressBar.bottom
anchors.bottom: parent.bottom
anchors.leftMargin: 16
anchors.rightMargin: 16
anchors.topMargin: 24
anchors.bottomMargin: 24
spacing: 12
TextFieldWithHeaderType {
id: payloadField
Layout.fillWidth: true
headerText: qsTr("Payload JSON")
textField.placeholderText: qsTr("Paste JSON like {\"gw\":\"...\",\"uuid\":\"...\"}")
}
TextFieldWithHeaderType {
id: uuidField
Layout.fillWidth: true
headerText: qsTr("UUID")
textField.placeholderText: qsTr("Enter UUID")
}
BasicButtonType {
id: usePayloadButton
Layout.fillWidth: true
text: qsTr("Next (use payload)")
enabled: payloadField.textField.text.length > 0
clickedFunc: function() {
TransferController.setPendingQrCode(payloadField.textField.text)
var normalizedObj = { gw: obj.gw, uuid: (obj.uuid ? obj.uuid : obj.u) }
if (obj.name && obj.name.length > 0) {
normalizedObj.name = obj.name
}
var normalized = JSON.stringify(normalizedObj)
TransferController.setPendingQrCode(normalized)
qrCodeReader.stopReading()
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceConfirm)
}
}
BasicButtonType {
id: buildFromUuidButton
Layout.fillWidth: true
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.paleGray
borderColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: qsTr("Next (build from UUID)")
enabled: uuidField.textField.text.length > 0
clickedFunc: function() {
var gw = ""
if (typeof SettingsController !== "undefined") {
if (SettingsController.gatewayEndpoint !== undefined) {
gw = SettingsController.gatewayEndpoint
} else if (SettingsController.getGatewayEndpoint) {
gw = SettingsController.getGatewayEndpoint()
Component.onCompleted: {
updateCameraSize()
qrCodeReader.startReading()
}
Component.onDestruction: qrCodeReader.stopReading()
}
}
if (!gw || gw.length === 0) {
PageController.showErrorMessage(qsTr("Gateway endpoint is empty"))
return
}
if (!gw.endsWith("/")) {
gw = gw + "/"
}
}
var payload = JSON.stringify({ gw: gw, uuid: uuidField.textField.text })
TransferController.setPendingQrCode(payload)
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceConfirm)
}
onWidthChanged: {
if (iosQrLoader.item && iosQrLoader.item.updateCameraSize) {
iosQrLoader.item.updateCameraSize()
}
}
onHeightChanged: {
if (iosQrLoader.item && iosQrLoader.item.updateCameraSize) {
iosQrLoader.item.updateCameraSize()
}
}
/*Rectangle {
id: qrCodeRactangle
visible: false
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.top: progressBar.bottom
anchors.topMargin: 34
anchors.leftMargin: 16
anchors.rightMargin: 16
anchors.bottomMargin: 34
color: AmneziaStyle.color.transparent
QRCodeReader {
id: qrCodeReader
onCodeReaded: function(code) {
// scanning disabled
return
}
Component.onCompleted: { }
Component.onDestruction: qrCodeReader.stopReading()
}
}*/
Component.onCompleted: { }
/*Connections {
Connections {
target: TransferController
function onScannerShouldStop() {
if (qrCodeReader && qrCodeReader.stopReading) {
qrCodeReader.stopReading()
}
if (typeof ImportController !== "undefined"
&& ImportController.stopDecodingQr) {
ImportController.stopDecodingQr()
if (iosQrLoader.item && iosQrLoader.item.stopReading) {
iosQrLoader.item.stopReading()
}
}
}
@@ -176,15 +98,43 @@ PageType {
function onTransferQrDecoded(code) {
if (!code || code.length === 0) {
console.log("ImportController.onTransferQrDecoded: empty QR code payload")
if (typeof PageController !== "undefined" && PageController.showErrorMessage) {
PageController.showErrorMessage(qsTr("QR code not recognized. Please try again."));
}
return
}
var obj = null
try {
obj = JSON.parse(code)
} catch (e) {
obj = null
}
if (obj && obj.gw && (obj.uuid || obj.u)) {
var normalizedObj = { gw: obj.gw, uuid: (obj.uuid ? obj.uuid : obj.u) }
if (obj.name && obj.name.length > 0) {
normalizedObj.name = obj.name
}
code = JSON.stringify(normalizedObj)
}
root.scanCompleted = true
TransferController.setPendingQrCode(code)
Qt.callLater(function() {
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceConfirm)
})
}
}*/
function onQrDecodingFinished() {
if (Qt.platform.os === "android" && !root.scanCompleted) {
PageController.closePage()
}
}
}
Component.onCompleted: {
TransferController.setPendingQrCode("")
root.scanCompleted = false
if (Qt.platform.os === "android") {
ImportController.startDecodingQr()
}
}
}

View File

@@ -25,6 +25,24 @@ PageType {
return listView.count >= maxDeviceCount
}
function getConfigFilesCount() {
try {
var arr = ApiAccountInfoModel.getIssuedConfigsInfo()
if (!arr) return 0
var count = 0
for (var i = 0; i < arr.length; i++) {
var item = arr[i]
if (item && item["source_type"] === "country_config") {
++count
}
}
return count
} catch (e) {
return 0
}
}
ListViewType {
id: listView
@@ -60,7 +78,7 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 16
//visible: GC.isMobile()
visible: GC.isMobile()
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
@@ -80,16 +98,13 @@ PageType {
}
}
WarningType {
Layout.topMargin: 16
SmallTextType {
Layout.topMargin: 8
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.fillWidth: true
textString: qsTr("You can find the identifier on the Support tab or, for older versions of the app, "
+ "by tapping '+' and then the three dots at the top of the page.")
iconPath: "qrc:/images/controls/alert-circle.svg"
text: qsTr("On the other device, tap + at the bottom → Connect to Amnezia Premium")
}
}
@@ -128,6 +143,37 @@ PageType {
DividerType {}
}
footer: ColumnLayout {
width: listView.width
LabelWithButtonType {
Layout.fillWidth: true
Layout.topMargin: 6
text: qsTr("Configuration Files: {%1}").arg(root.getConfigFilesCount())
descriptionText: qsTr("Generated configuration files also count towards the device limit")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
ApiSettingsController.updateApiCountryModel()
PageController.goToPage(PageEnum.PageSettingsApiNativeConfigs)
}
}
DividerType {}
WarningType {
Layout.topMargin: 16
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 8
Layout.fillWidth: true
textString: qsTr("The Support tag is available on the Support page. In older versions: tap + in the bottom bar, then More (...) in the top-right.")
iconPath: "qrc:/images/controls/alert-circle.svg"
}
}
Connections {
target: TransferController

View File

@@ -104,7 +104,7 @@ PageType {
actionButtonImage: "qrc:/images/controls/edit-3.svg"
headerText: root.processedServer.name
headerText: root.processedServer && root.processedServer.name ? root.processedServer.name : ""
descriptionText: ApiAccountInfoModel.data("serviceDescription")
actionButtonFunction: function() {
@@ -214,9 +214,6 @@ PageType {
ApiConfigsController.prepareVpnKeyExport()
PageController.showBusyIndicator(false)
// Navigate to PageShareConnection page
//PageController.goToPage(PageEnum.PageShareConnection)
}
}
@@ -416,6 +413,6 @@ PageType {
anchors.fill: parent
expandedHeight: parent.height * 0.35
serverNameText: root.processedServer.name
serverNameText: root.processedServer && root.processedServer.name ? root.processedServer.name : ""
}
}

View File

@@ -45,7 +45,7 @@ PageType {
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Connection")
headerText: qsTr("New Connection")
actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : ""
actionButtonFunction: function() {
@@ -156,7 +156,7 @@ PageType {
Layout.leftMargin: 16
Layout.bottomMargin: 24
text: qsTr("Insert the key, add a configuration file or scan the QR-code")
text: qsTr("Enter a connection key, import a configuration file, or scan a QR code")
}
TextFieldWithHeaderType {
@@ -166,8 +166,8 @@ PageType {
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Insert key")
buttonText: qsTr("Insert")
headerText: qsTr("Connection key")
buttonText: qsTr("Paste")
clickedFunc: function() {
textField.text = ""
@@ -250,7 +250,7 @@ PageType {
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.goldenApricot
text: qsTr("Site Amnezia")
text: qsTr("Amnezia Website")
rightImageSource: "qrc:/images/controls/external-link.svg"
@@ -263,19 +263,17 @@ PageType {
property list<QtObject> variants: [
amneziaVpn,
selfHostVpn,
backupRestore,
shareViaDevice,
selfHostVpn,
fileOpen,
qrScan,
siteLink
qrScan
]
QtObject {
id: amneziaVpn
property string title: qsTr("VPN by Amnezia")
property string description: qsTr("Connect to classic paid and free VPN services from Amnezia")
property string title: qsTr("Choose a VPN from Amnezia")
property string description: qsTr("Paid and free VPN services from Amnezia")
property string imageSource: "qrc:/images/controls/amnezia.svg"
property bool isVisible: true
property var handler: function() {
@@ -288,11 +286,23 @@ PageType {
}
}
QtObject {
id: shareViaDevice
property string title: qsTr("Connect to Amnezia Premium")
property string description: qsTr("Via QR code from a phone with an active subscription")
property string imageSource: "qrc:/images/controls/monitor-with-phone.svg"
property bool isVisible: true
property var handler: function() {
PageController.goToPage(PageEnum.PageTransferConfigViaQR)
}
}
QtObject {
id: selfHostVpn
property string title: qsTr("Self-hosted VPN")
property string description: qsTr("Configure Amnezia VPN on your own server")
property string title: qsTr("Set up a Self-hosted VPN")
property string description: qsTr("On a private server")
property string imageSource: "qrc:/images/controls/server.svg"
property bool isVisible: true
property var handler: function() {
@@ -318,23 +328,11 @@ PageType {
}
}
QtObject {
id: shareViaDevice
property string title: qsTr("Set up your Amnezia Premium VPN using your phone")
property string description: qsTr("Which already has a premium subscription")
property string imageSource: "qrc:/images/controls/monitor-with-phone.svg"
property bool isVisible: true
property var handler: function() {
PageController.goToPage(PageEnum.PageTransferConfigViaQR)
}
}
QtObject {
id: fileOpen
property string title: qsTr("File with connection settings")
property string description: qsTr("")
property string title: qsTr("Use a configuration file")
property string description: qsTr("Supported formats: .conf, .vpn, .ovpn, .json")
property string imageSource: "qrc:/images/controls/folder-search-2.svg"
property bool isVisible: true
property var handler: function() {
@@ -352,8 +350,8 @@ PageType {
QtObject {
id: qrScan
property string title: qsTr("QR code")
property string description: qsTr("")
property string title: qsTr("Scan a QR code")
property string description: qsTr("To connect to a self-hosted server")
property string imageSource: "qrc:/images/controls/scan-line.svg"
property bool isVisible: SettingsController.isCameraPresent()
property var handler: function() {

View File

@@ -19,18 +19,6 @@ PageType {
z: 0
}
MouseArea {
anchors.fill: parent
z: 0
acceptedButtons: Qt.AllButtons
hoverEnabled: true
preventStealing: true
onPressed: mouse.accepted = true
onReleased: mouse.accepted = true
onClicked: mouse.accepted = true
onWheel: wheel.accepted = true
}
ColumnLayout {
z: 1
anchors.fill: parent
@@ -39,8 +27,8 @@ PageType {
BackButtonType {
Layout.topMargin: 20
Layout.leftMargin: 0
Layout.rightMargin: 0
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.alignment: Qt.AlignLeft
}
@@ -52,72 +40,60 @@ PageType {
Layout.rightMargin: 16
Layout.bottomMargin: 16
ParagraphTextType {
id: qrHeader
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
text: qsTr("Debug mode: copy the payload to the other device and send it there")
}
ColumnLayout {
id: debugPayload
anchors.left: parent.left
anchors.right: parent.right
anchors.top: qrHeader.bottom
anchors.topMargin: 12
anchors.bottom: bottomHint.top
anchors.bottomMargin: 12
spacing: 8
id: qrContent
anchors.fill: parent
spacing: 16
TextArea {
id: payloadArea
Item { Layout.fillHeight: true }
SmallTextType {
id: topHint
Layout.fillWidth: true
Layout.fillHeight: true
readOnly: true
wrapMode: TextArea.WrapAnywhere
selectByMouse: true
text: TransferController.currentPayload
placeholderText: qsTr("Payload will appear here after generating")
horizontalAlignment: Text.AlignHCenter
text: qsTr("Scan this QR code with a phone that has an active\nAmnezia Premium subscription")
}
RowLayout {
Layout.fillWidth: true
spacing: 8
Rectangle {
id: qrFrame
Layout.alignment: Qt.AlignHCenter
property real maxByHeight: qrContent.height
- topHint.implicitHeight
- bottomHint.implicitHeight
- (qrContent.spacing * 2)
property real qrSize: Math.max(180, Math.min(qrContent.width, Math.max(0, maxByHeight)))
BasicButtonType {
Layout.fillWidth: true
text: qsTr("Regenerate")
clickedFunc: function() {
TransferController.generateNewQrCode()
TransferController.startWaitForConfig(ImportController)
}
Layout.preferredWidth: qrSize
Layout.preferredHeight: qrSize
radius: 16
color: "white"
Image {
id: qrImage
anchors.fill: parent
anchors.margins: 12
fillMode: Image.PreserveAspectFit
smooth: false
sourceSize: Qt.size(Math.round(width), Math.round(height))
source: TransferController.qrCodeUrl
visible: TransferController.qrCodeUrl !== ""
}
BasicButtonType {
Layout.fillWidth: true
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.paleGray
borderColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: qsTr("Restart wait")
clickedFunc: function() {
TransferController.startWaitForConfig(ImportController)
}
BusyIndicator {
anchors.centerIn: parent
running: TransferController.qrCodeUrl === ""
visible: TransferController.qrCodeUrl === ""
}
}
}
SmallTextType {
id: bottomHint
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
text: qsTr("Copy the JSON payload above, paste it on the other device, and confirm sending there")
horizontalAlignment: Text.AlignHCenter
SmallTextType {
id: bottomHint
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: qsTr("AmneziaVPN → Amnezia Premium →\nPersonal Dashboard → Active Devices →\nAdd Device via QR Code")
}
Item { Layout.fillHeight: true }
}
}
}
@@ -125,7 +101,9 @@ PageType {
Connections {
target: TransferController
function onConfigApplied() {
PageController.showNotificationMessage(qsTr("Configuration received and applied"))
PageController.showNotificationMessage(qsTr("Device has been added to subscription"))
PageController.closePage()
PageController.goToPageHome()
}
function onWaitError(message) {
PageController.showErrorMessage(message)
@@ -145,23 +123,4 @@ PageType {
}
Component.onDestruction: TransferController.stopWaitForConfig()
}
/*CardWithIconsType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 8
headerText: qsTr("Send connection via QR")
bodyText: qsTr("Scan a QR code from another device to send your config to it")
leftImageSource: "qrc:/images/controls/scan-line.svg"
rightImageSource: "qrc:/images/controls/chevron-right.svg"
onClicked: {
showReceiveSection = false
showScanSection = true
isPosting = false
postStatusText = ""
}
}*/
}