mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
add qml QR Code
This commit is contained in:
@@ -34,7 +34,9 @@ add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
||||
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
|
||||
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
|
||||
|
||||
include(../.cache/agw_rsa_public_keys.cmake)
|
||||
if(AMNEZIA_QR_PAIRING_ALLOW)
|
||||
include(../.cache/agw_rsa_public_keys.cmake)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(PACKAGES ${PACKAGES} Widgets)
|
||||
@@ -204,8 +206,8 @@ list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
|
||||
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
||||
|
||||
if(AMNEZIA_LOCAL_GATEWAY)
|
||||
target_compile_definitions(${PROJECT} PRIVATE AMNEZIA_LOCAL_GATEWAY)
|
||||
if(AMNEZIA_QR_PAIRING_ALLOW)
|
||||
target_compile_definitions(${PROJECT} PRIVATE AMNEZIA_QR_PAIRING_ALLOW)
|
||||
endif()
|
||||
|
||||
if(AMNEZIA_QR_PAIRING_ALLOW_DUPLICATE_VPN_KEY)
|
||||
|
||||
@@ -251,6 +251,10 @@ bool AmneziaApplication::parseCommands()
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
void AmneziaApplication::startLocalServer() {
|
||||
#ifdef AMNEZIA_QR_PAIRING_ALLOW
|
||||
return;
|
||||
#endif
|
||||
|
||||
const QString serverName("AmneziaVPNInstance");
|
||||
QLocalServer::removeServer(serverName);
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef AMNEZIA_LOCAL_GATEWAY
|
||||
#ifdef AMNEZIA_QR_PAIRING_ALLOW
|
||||
{
|
||||
const QUrl gatewayUrl(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl);
|
||||
const QString host = gatewayUrl.host().toLower();
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
using namespace amnezia;
|
||||
|
||||
namespace {
|
||||
#ifdef AMNEZIA_LOCAL_GATEWAY
|
||||
#ifdef AMNEZIA_QR_PAIRING_ALLOW
|
||||
// Prefer 127.0.0.1 with local mock (tools/local_gateway listens on 0.0.0.0:8080); avoids LAN/IPv6 ambiguity in dev.
|
||||
constexpr char gatewayEndpoint[] = "http://127.0.0.1:8080/";
|
||||
#else
|
||||
|
||||
@@ -143,6 +143,15 @@ QString PairingUiController::tvStatusMessage() const
|
||||
return m_tvStatusMessage;
|
||||
}
|
||||
|
||||
int PairingUiController::tvPairingWaitWindowSeconds() const
|
||||
{
|
||||
if (!m_pairingController) {
|
||||
return 30;
|
||||
}
|
||||
const int msec = m_pairingController->pairingLongPollTimeoutMsecs();
|
||||
return qMax(1, (msec + 999) / 1000);
|
||||
}
|
||||
|
||||
bool PairingUiController::phonePairingBusy() const
|
||||
{
|
||||
return m_phonePairingBusy;
|
||||
|
||||
@@ -24,6 +24,8 @@ class PairingUiController : public QObject
|
||||
Q_PROPERTY(QString tvSessionUuid READ tvSessionUuid NOTIFY tvSessionUuidChanged)
|
||||
Q_PROPERTY(bool tvPairingBusy READ tvPairingBusy NOTIFY tvPairingBusyChanged)
|
||||
Q_PROPERTY(QString tvStatusMessage READ tvStatusMessage NOTIFY tvStatusMessageChanged)
|
||||
/** Long-poll window for generate_qr (seconds), for receive UI countdown. */
|
||||
Q_PROPERTY(int tvPairingWaitWindowSeconds READ tvPairingWaitWindowSeconds NOTIFY tvQrCodesChanged)
|
||||
|
||||
Q_PROPERTY(bool phonePairingBusy READ phonePairingBusy NOTIFY phonePairingBusyChanged)
|
||||
Q_PROPERTY(QString phoneStatusMessage READ phoneStatusMessage NOTIFY phoneStatusMessageChanged)
|
||||
@@ -41,6 +43,7 @@ public:
|
||||
QString tvSessionUuid() const;
|
||||
bool tvPairingBusy() const;
|
||||
QString tvStatusMessage() const;
|
||||
int tvPairingWaitWindowSeconds() const;
|
||||
|
||||
bool phonePairingBusy() const;
|
||||
QString phoneStatusMessage() const;
|
||||
|
||||
@@ -80,7 +80,9 @@ namespace PageLoader
|
||||
PageSetupWizardApiPremiumInfo,
|
||||
PageSetupWizardApiTrialEmail,
|
||||
|
||||
PageSettingsApiQrPairing,
|
||||
PageSettingsApiQrPairingDev,
|
||||
PageSettingsApiQrPairingSend,
|
||||
PageSetupWizardApiQrPairingReceive,
|
||||
|
||||
PageDevMenu
|
||||
};
|
||||
|
||||
@@ -90,12 +90,27 @@ PageType {
|
||||
footer: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
SwitcherType {
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
|
||||
text: qsTr("QR pairing (full dev UI)")
|
||||
descriptionText: qsTr("Receive + send on one device for local gateway / QA")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsApiQrPairingDev)
|
||||
}
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
|
||||
text: qsTr("Dev gateway environment")
|
||||
checked: SettingsController.isDevGatewayEnv
|
||||
onToggled: function() {
|
||||
|
||||
@@ -3,7 +3,6 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import QRCodeReader 1.0
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "../Controls2"
|
||||
@@ -46,13 +45,22 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
text: qsTr("QR pairing")
|
||||
text: qsTr("QR pairing (dev — single device)")
|
||||
font.pixelSize: 28
|
||||
font.bold: true
|
||||
color: AmneziaStyle.color.paleGray
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
color: AmneziaStyle.color.goldenApricot
|
||||
text: qsTr("Developer / QA: receive and send on one device (e.g. with local gateway). Not shown in production menus unless opened from Dev menu.")
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
@@ -88,8 +96,6 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: qsTr("Cancel receive")
|
||||
// Do not use defaultColor: transparent here: when enabled, BasicButtonType paints that
|
||||
// as the idle background, so midnightBlack label sits on the page — invisible until hover.
|
||||
enabled: PairingUiController.tvPairingBusy
|
||||
clickedFunc: function() {
|
||||
PairingUiController.cancelTvQrSession()
|
||||
@@ -105,7 +111,6 @@ PageType {
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// SVG QR from qrCodeUtils has a tiny viewBox (~45px); without a sized container + sourceSize it stays small.
|
||||
Item {
|
||||
id: qrBox
|
||||
Layout.fillWidth: true
|
||||
@@ -182,7 +187,6 @@ PageType {
|
||||
visible: Layout.preferredHeight > 0
|
||||
clip: true
|
||||
|
||||
// QRCodeReader is a QObject (not Item): no anchors; preview rect via setCameraSize like PageSetupWizardQrReader.
|
||||
QRCodeReader {
|
||||
id: pairingQrReader
|
||||
|
||||
@@ -246,11 +250,22 @@ PageType {
|
||||
}
|
||||
|
||||
function onTvPairingConfigReceived() {
|
||||
root.pairingCameraOpen = false
|
||||
pairingQrReader.stopReading()
|
||||
qrImage.source = ""
|
||||
PageController.showNotificationMessage(qsTr("Configuration received from gateway"))
|
||||
Qt.callLater(function() {
|
||||
PageController.closePage()
|
||||
})
|
||||
}
|
||||
|
||||
function onPhonePairingSucceeded() {
|
||||
root.pairingCameraOpen = false
|
||||
pairingQrReader.stopReading()
|
||||
PageController.showNotificationMessage(qsTr("Configuration sent"))
|
||||
Qt.callLater(function() {
|
||||
PageController.closePage()
|
||||
})
|
||||
}
|
||||
|
||||
function onPairingUuidFromScan(uuid) {
|
||||
178
client/ui/qml/Pages2/PageSettingsApiQrPairingSend.qml
Normal file
178
client/ui/qml/Pages2/PageSettingsApiQrPairingSend.qml
Normal file
@@ -0,0 +1,178 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import QRCodeReader 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool pairingCameraOpen: false
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onVisibleChanged() {
|
||||
if (!root.visible) {
|
||||
pairingQrReader.stopReading()
|
||||
root.pairingCameraOpen = false
|
||||
PairingUiController.cancelAllPairingActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
anchors.fill: parent
|
||||
contentHeight: layout.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
width: root.width
|
||||
spacing: 8
|
||||
|
||||
BackButtonType {
|
||||
Layout.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
text: qsTr("Transfer subscription (QR)")
|
||||
font.pixelSize: 28
|
||||
font.bold: true
|
||||
color: AmneziaStyle.color.paleGray
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: qsTr("Scan the session QR shown on the receiving device, then send this server’s Amnezia Premium configuration through the gateway.")
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
text: qsTr("Send from this subscription")
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: uuidField
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
headerText: qsTr("QR session UUID")
|
||||
textField.placeholderText: qsTr("Paste UUID from the other device’s QR")
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
visible: Qt.platform.os === "android" || Qt.platform.os === "ios"
|
||||
text: {
|
||||
if (Qt.platform.os === "ios" && root.pairingCameraOpen) {
|
||||
return qsTr("Hide camera")
|
||||
}
|
||||
return qsTr("Scan QR code")
|
||||
}
|
||||
enabled: !PairingUiController.phonePairingBusy
|
||||
clickedFunc: function() {
|
||||
if (Qt.platform.os === "android") {
|
||||
PairingUiController.openPairingQrScanner()
|
||||
} else {
|
||||
root.pairingCameraOpen = !root.pairingCameraOpen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: cameraSlot
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: (root.pairingCameraOpen && Qt.platform.os === "ios") ? 220 : 0
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
visible: Layout.preferredHeight > 0
|
||||
clip: true
|
||||
|
||||
QRCodeReader {
|
||||
id: pairingQrReader
|
||||
|
||||
onCodeReaded: function(code) {
|
||||
if (PairingUiController.applyScannedTextAsPairingUuid(code)) {
|
||||
pairingQrReader.stopReading()
|
||||
root.pairingCameraOpen = false
|
||||
PageController.showNotificationMessage(qsTr("Session ID filled from QR"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
pairingQrReader.stopReading()
|
||||
return
|
||||
}
|
||||
if (Qt.platform.os === "ios") {
|
||||
Qt.callLater(function() {
|
||||
var p = cameraSlot.mapToItem(root, 0, 0)
|
||||
pairingQrReader.setCameraSize(Qt.rect(p.x, p.y, cameraSlot.width, cameraSlot.height))
|
||||
pairingQrReader.startReading()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: PairingUiController.phonePairingBusy ? qsTr("Sending…") : qsTr("Send from current subscription")
|
||||
enabled: !PairingUiController.phonePairingBusy
|
||||
clickedFunc: function() {
|
||||
PairingUiController.submitPhonePairing(uuidField.textField.text, ServersUiController.getProcessedServerIndex())
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 24 + PageController.safeAreaBottomMargin
|
||||
visible: PairingUiController.phoneStatusMessage.length > 0
|
||||
text: PairingUiController.phoneStatusMessage
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PairingUiController
|
||||
|
||||
function onPhonePairingSucceeded() {
|
||||
root.pairingCameraOpen = false
|
||||
pairingQrReader.stopReading()
|
||||
PageController.showNotificationMessage(qsTr("Configuration sent"))
|
||||
Qt.callLater(function() {
|
||||
PageController.closePage()
|
||||
})
|
||||
}
|
||||
|
||||
function onPairingUuidFromScan(uuid) {
|
||||
uuidField.textField.text = uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,12 +378,12 @@ PageType {
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("QR pairing (beta)")
|
||||
descriptionText: qsTr("Transfer config via gateway using a QR code")
|
||||
text: qsTr("Transfer by QR (send)")
|
||||
descriptionText: qsTr("Scan the session QR from the receiving device and send this subscription via the gateway")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsApiQrPairing)
|
||||
PageController.goToPage(PageEnum.PageSettingsApiQrPairingSend)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
264
client/ui/qml/Pages2/PageSetupWizardApiQrPairingReceive.qml
Normal file
264
client/ui/qml/Pages2/PageSetupWizardApiQrPairingReceive.qml
Normal file
@@ -0,0 +1,264 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Style 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property int qrImageIndex: 0
|
||||
property int pairingSecondsLeft: 0
|
||||
|
||||
function formatMmSs(totalSec) {
|
||||
if (totalSec <= 0) {
|
||||
return "0:00"
|
||||
}
|
||||
const m = Math.floor(totalSec / 60)
|
||||
const s = totalSec % 60
|
||||
return m + (s < 10 ? ":0" : ":") + s
|
||||
}
|
||||
|
||||
function scrollPairingToBottom() {
|
||||
receiveScroll.contentY = Math.max(0, receiveScroll.contentHeight - receiveScroll.height)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: scrollToBottomRetryTimer
|
||||
interval: 48
|
||||
repeat: true
|
||||
property int retries: 0
|
||||
onTriggered: {
|
||||
root.scrollPairingToBottom()
|
||||
retries++
|
||||
if (retries >= 12) {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
retries = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: pairingCountdownTimer
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: PairingUiController.tvPairingBusy && PairingUiController.tvQrCodesCount > 0 && root.pairingSecondsLeft > 0
|
||||
onTriggered: {
|
||||
if (root.pairingSecondsLeft > 0) {
|
||||
root.pairingSecondsLeft--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onVisibleChanged() {
|
||||
if (!root.visible) {
|
||||
PairingUiController.cancelAllPairingActivity()
|
||||
scrollToBottomRetryTimer.stop()
|
||||
pairingCountdownTimer.stop()
|
||||
root.pairingSecondsLeft = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: receiveScroll
|
||||
anchors.fill: parent
|
||||
contentHeight: layout.implicitHeight
|
||||
|
||||
Behavior on contentY {
|
||||
NumberAnimation {
|
||||
duration: 320
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
onContentHeightChanged: {
|
||||
if (PairingUiController.tvQrCodesCount > 0) {
|
||||
Qt.callLater(root.scrollPairingToBottom)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
width: root.width
|
||||
spacing: 8
|
||||
|
||||
BackButtonType {
|
||||
Layout.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
text: qsTr("Get Premium server from mobile")
|
||||
font.pixelSize: 28
|
||||
font.bold: true
|
||||
color: AmneziaStyle.color.paleGray
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
color: AmneziaStyle.color.goldenApricot
|
||||
text: qsTr("Amnezia Premium only. Someone who already has this subscription in Amnezia on a phone or tablet must send it to you; otherwise the session expires.")
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 12
|
||||
text: qsTr("How to get the server from a mobile device")
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: qsTr("On this device (TV, tablet, or second phone):\n1) In the “Start receiving” section, tap “Start and show QR” and leave this screen open until the transfer finishes or times out.\n\nOn the mobile device that already has Amnezia Premium:\n2) Open Amnezia VPN → Settings (gear).\n3) Select your Amnezia Premium API server in the list, then open its details screen.\n4) Choose “Transfer by QR (send)”.\n5) Scan the QR code shown on this device, or paste the session ID if you copy it from this screen.\n6) Tap “Send from current subscription” and wait. When the gateway completes pairing, this device receives the configuration and adds the server.")
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
visible: PairingUiController.tvStatusMessage.length > 0
|
||||
text: PairingUiController.tvStatusMessage
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Item {
|
||||
id: qrBox
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
Layout.preferredHeight: PairingUiController.tvQrCodesCount > 0 ? width : 0
|
||||
visible: PairingUiController.tvQrCodesCount > 0
|
||||
|
||||
Image {
|
||||
id: qrImage
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize: Qt.size(2048, 2048)
|
||||
source: PairingUiController.tvQrCodesCount > 0 ? PairingUiController.tvQrCodes[root.qrImageIndex] : ""
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: PairingUiController.tvQrCodesCount > 1
|
||||
onClicked: {
|
||||
root.qrImageIndex = (root.qrImageIndex + 1) % PairingUiController.tvQrCodesCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
visible: PairingUiController.tvPairingBusy && PairingUiController.tvQrCodesCount > 0
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
text: qsTr("This QR code will refresh in %1. If the session expires, tap Start again for a new code.")
|
||||
.arg(root.formatMmSs(root.pairingSecondsLeft))
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
text: qsTr("Start receiving")
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: PairingUiController.tvPairingBusy ? qsTr("Waiting…") : qsTr("Start and show QR")
|
||||
enabled: !PairingUiController.tvPairingBusy && !PairingUiController.phonePairingBusy
|
||||
clickedFunc: function() {
|
||||
PairingUiController.startTvQrSession()
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: qsTr("Cancel receive")
|
||||
enabled: PairingUiController.tvPairingBusy
|
||||
clickedFunc: function() {
|
||||
PairingUiController.cancelTvQrSession()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 24 + PageController.safeAreaBottomMargin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PairingUiController
|
||||
|
||||
function onTvQrCodesChanged() {
|
||||
root.qrImageIndex = 0
|
||||
if (PairingUiController.tvQrCodesCount > 0) {
|
||||
root.pairingSecondsLeft = PairingUiController.tvPairingWaitWindowSeconds
|
||||
scrollToBottomRetryTimer.retries = 0
|
||||
scrollToBottomRetryTimer.start()
|
||||
Qt.callLater(function() {
|
||||
root.scrollPairingToBottom()
|
||||
})
|
||||
Qt.callLater(function() {
|
||||
Qt.callLater(function() {
|
||||
root.scrollPairingToBottom()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function onTvSessionUuidChanged() {
|
||||
root.qrImageIndex = 0
|
||||
}
|
||||
|
||||
function onTvPairingConfigReceived() {
|
||||
scrollToBottomRetryTimer.stop()
|
||||
root.pairingSecondsLeft = 0
|
||||
qrImage.source = ""
|
||||
PageController.showNotificationMessage(qsTr("Configuration received from gateway"))
|
||||
Qt.callLater(function() {
|
||||
PageController.closePage()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -269,6 +269,7 @@ PageType {
|
||||
selfHostVpn,
|
||||
backupRestore,
|
||||
fileOpen,
|
||||
gatewayQrPairingAddServer,
|
||||
qrScan,
|
||||
restorePurchases,
|
||||
siteLink
|
||||
@@ -343,6 +344,19 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: gatewayQrPairingAddServer
|
||||
|
||||
property bool featuredAmneziaConnection: false
|
||||
property string title: qsTr("Get Premium server from mobile")
|
||||
property string description: qsTr("Premium · QR transfer — steps inside")
|
||||
property string imageSource: "qrc:/images/controls/qr-code.svg"
|
||||
property bool isVisible: true
|
||||
property var handler: function() {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardApiQrPairingReceive)
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: qrScan
|
||||
|
||||
|
||||
@@ -85,7 +85,9 @@
|
||||
<file>Pages2/PageSettingsAbout.qml</file>
|
||||
<file>Pages2/PageSettingsApiAvailableCountries.qml</file>
|
||||
<file>Pages2/PageSettingsApiServerInfo.qml</file>
|
||||
<file>Pages2/PageSettingsApiQrPairing.qml</file>
|
||||
<file>Pages2/PageSettingsApiQrPairingDev.qml</file>
|
||||
<file>Pages2/PageSettingsApiQrPairingSend.qml</file>
|
||||
<file>Pages2/PageSetupWizardApiQrPairingReceive.qml</file>
|
||||
<file>Pages2/PageSettingsApplication.qml</file>
|
||||
<file>Pages2/PageSettingsAppSplitTunneling.qml</file>
|
||||
<file>Pages2/PageSettingsBackup.qml</file>
|
||||
|
||||
Reference in New Issue
Block a user