mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
remove dead code
This commit is contained in:
@@ -211,10 +211,6 @@ if(AMNEZIA_QR_PAIRING_ALLOW)
|
|||||||
target_compile_definitions(${PROJECT} PRIVATE AMNEZIA_QR_PAIRING_ALLOW)
|
target_compile_definitions(${PROJECT} PRIVATE AMNEZIA_QR_PAIRING_ALLOW)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(AMNEZIA_QR_PAIRING_ALLOW_DUPLICATE_VPN_KEY)
|
|
||||||
target_compile_definitions(${PROJECT} PRIVATE AMNEZIA_QR_PAIRING_ALLOW_DUPLICATE_VPN_KEY)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||||
|
|
||||||
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
|
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
|
||||||
|
|||||||
@@ -254,10 +254,6 @@ bool AmneziaApplication::parseCommands()
|
|||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
void AmneziaApplication::startLocalServer() {
|
void AmneziaApplication::startLocalServer() {
|
||||||
#ifdef AMNEZIA_QR_PAIRING_ALLOW
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const QString serverName("AmneziaVPNInstance");
|
const QString serverName("AmneziaVPNInstance");
|
||||||
QLocalServer::removeServer(serverName);
|
QLocalServer::removeServer(serverName);
|
||||||
|
|
||||||
|
|||||||
@@ -7,40 +7,7 @@ import kotlin.math.floor
|
|||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
|
||||||
* Same proportions as [PageSettingsApiQrPairingSend.qml] (iOS embedded scan): sq = 0.72 * min(w,h),
|
|
||||||
* vertical bias -0.06 * height (square shifted up slightly).
|
|
||||||
*/
|
|
||||||
object PairingQrScanGeometry {
|
object PairingQrScanGeometry {
|
||||||
const val SQ_FRACTION = 0.72f
|
|
||||||
const val VERTICAL_BIAS = 0.06f
|
|
||||||
|
|
||||||
fun holeRectF(viewW: Int, viewH: Int): RectF {
|
|
||||||
val w = viewW.toFloat()
|
|
||||||
val h = viewH.toFloat()
|
|
||||||
val side = min(w, h) * SQ_FRACTION
|
|
||||||
val left = (w - side) / 2f
|
|
||||||
var top = (h - side) / 2f - h * VERTICAL_BIAS
|
|
||||||
top = max(0f, top)
|
|
||||||
var bottom = top + side
|
|
||||||
if (bottom > h) {
|
|
||||||
bottom = h
|
|
||||||
}
|
|
||||||
val adjSide = bottom - top
|
|
||||||
return RectF(left, top, left + adjSide, bottom)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ML Kit [Barcode] box is in [InputImage] pixel space (same as analysis frame WxH). */
|
|
||||||
fun barcodeCenterInPairingHole(imageW: Int, imageH: Int, barcode: Barcode): Boolean {
|
|
||||||
val box = barcode.boundingBox ?: return true
|
|
||||||
val r = holeRectF(imageW, imageH)
|
|
||||||
return r.contains(box.centerX().toFloat(), box.centerY().toFloat())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a rectangle in [PreviewView] / overlay pixel space to [InputImage] pixel space,
|
|
||||||
* assuming the preview uses default FILL_CENTER scaling (same as typical CameraX [PreviewView]).
|
|
||||||
*/
|
|
||||||
fun viewRectToInputImageRectFillCenter(
|
fun viewRectToInputImageRectFillCenter(
|
||||||
viewW: Int,
|
viewW: Int,
|
||||||
viewH: Int,
|
viewH: Int,
|
||||||
@@ -59,16 +26,6 @@ object PairingQrScanGeometry {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Pairing hole (same geometry as overlay) expressed in ML Kit / [InputImage] coordinates. */
|
|
||||||
fun pairingHoleInImageCoords(viewW: Int, viewH: Int, imageW: Int, imageH: Int): RectF {
|
|
||||||
val holeView = holeRectF(viewW, viewH)
|
|
||||||
return viewRectToInputImageRectFillCenter(viewW, viewH, imageW, imageH, holeView)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rounded scan window corner radius — same formula as iOS `layoutScanOverlayGeometry` (`holeR`).
|
|
||||||
* [sidePx] is the scan square side in pixels.
|
|
||||||
*/
|
|
||||||
fun pairingIosStyleHoleCornerRadiusPx(sidePx: Float, density: Float): Float {
|
fun pairingIosStyleHoleCornerRadiusPx(sidePx: Float, density: Float): Float {
|
||||||
val d = density
|
val d = density
|
||||||
var holeR = min(28f * d, max(10f * d, sidePx * 0.056f))
|
var holeR = min(28f * d, max(10f * d, sidePx * 0.056f))
|
||||||
@@ -77,7 +34,6 @@ object PairingQrScanGeometry {
|
|||||||
return max(holeR, 1f)
|
return max(holeR, 1f)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Area(roi ∩ box) / area(box); 0 if disjoint. */
|
|
||||||
fun barcodeBoxOverlapFraction(roi: RectF, box: Rect): Float {
|
fun barcodeBoxOverlapFraction(roi: RectF, box: Rect): Float {
|
||||||
val bf = RectF(box)
|
val bf = RectF(box)
|
||||||
val inter = RectF(roi)
|
val inter = RectF(roi)
|
||||||
@@ -87,40 +43,6 @@ object PairingQrScanGeometry {
|
|||||||
return if (boxArea <= 0f) 0f else interArea / boxArea
|
return if (boxArea <= 0f) 0f else interArea / boxArea
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept only codes whose bounding box overlaps the on-screen pairing square by at least
|
|
||||||
* [minOverlapFraction] when that square is mapped into image space (preview FILL_CENTER model).
|
|
||||||
*/
|
|
||||||
fun barcodeMostlyInsidePairingHole(
|
|
||||||
viewW: Int,
|
|
||||||
viewH: Int,
|
|
||||||
imageW: Int,
|
|
||||||
imageH: Int,
|
|
||||||
barcode: Barcode,
|
|
||||||
minOverlapFraction: Float = 0.82f
|
|
||||||
): Boolean {
|
|
||||||
val box = barcode.boundingBox ?: return true
|
|
||||||
if (viewW <= 0 || viewH <= 0 || imageW <= 0 || imageH <= 0) {
|
|
||||||
return barcodeCenterInPairingHole(imageW, imageH, barcode)
|
|
||||||
}
|
|
||||||
val roi = pairingHoleInImageCoords(viewW, viewH, imageW, imageH)
|
|
||||||
val inset = 0.02f * min(imageW, imageH)
|
|
||||||
roi.inset(inset, inset)
|
|
||||||
if (roi.width() <= 0f || roi.height() <= 0f) {
|
|
||||||
return barcodeCenterInPairingHole(imageW, imageH, barcode)
|
|
||||||
}
|
|
||||||
return barcodeBoxOverlapFraction(roi, box) >= minOverlapFraction
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pairing send: accept only if the QR lies fully inside the on-screen square.
|
|
||||||
* [roiInImageSpace] is [holeRectF] in [PreviewView] coords mapped into the same space as ML Kit
|
|
||||||
* geometry ([CoordinateTransform] in [CameraActivity]).
|
|
||||||
*
|
|
||||||
* When [Barcode.getCornerPoints] is present (typical for QR), all corners must lie inside the ROI —
|
|
||||||
* tighter than [BoundingBox], which is often padded.
|
|
||||||
* Otherwise falls back to bbox center inside ROI plus [minOverlapFraction] of bbox area inside ROI.
|
|
||||||
*/
|
|
||||||
fun barcodeMatchesPairingHole(
|
fun barcodeMatchesPairingHole(
|
||||||
roiInImageSpace: RectF,
|
roiInImageSpace: RectF,
|
||||||
imageW: Int,
|
imageW: Int,
|
||||||
@@ -161,14 +83,8 @@ object PairingQrScanGeometry {
|
|||||||
return barcodeBoxOverlapFraction(roi, box) >= minOverlapFraction
|
return barcodeBoxOverlapFraction(roi, box) >= minOverlapFraction
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Bbox-only fallback when corner points are missing (unusual for QR). */
|
|
||||||
private const val PAIRING_SEND_MIN_OVERLAP_BBOX_FALLBACK = 0.72f
|
private const val PAIRING_SEND_MIN_OVERLAP_BBOX_FALLBACK = 0.72f
|
||||||
|
|
||||||
/**
|
|
||||||
* Native pairing scan hole — same rules as iOS `layoutScanOverlayGeometry` in
|
|
||||||
* `iosPairingQrOverlayWindow.mm` (0.72 × min side, header / bottom band clamps).
|
|
||||||
* [headerBottomPx] is the bottom edge of the chrome row in this view’s coordinate system.
|
|
||||||
*/
|
|
||||||
fun pairingIosStyleHoleRectF(
|
fun pairingIosStyleHoleRectF(
|
||||||
viewW: Int,
|
viewW: Int,
|
||||||
viewH: Int,
|
viewH: Int,
|
||||||
@@ -201,9 +117,6 @@ object PairingQrScanGeometry {
|
|||||||
return RectF(sqX, sqY, sqX + sqSz, sqY + sqSz)
|
return RectF(sqX, sqY, sqX + sqSz, sqY + sqSz)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Vertical center of the torch control in px (same math as iOS `torchCenterYConstraint` update).
|
|
||||||
*/
|
|
||||||
fun pairingIosStyleTorchCenterYPx(
|
fun pairingIosStyleTorchCenterYPx(
|
||||||
holeBottomPx: Float,
|
holeBottomPx: Float,
|
||||||
bandBottomPx: Float,
|
bandBottomPx: Float,
|
||||||
@@ -224,7 +137,6 @@ object PairingQrScanGeometry {
|
|||||||
return max(torchCy, hdr)
|
return max(torchCy, hdr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** [pairingIosStyleHoleRectF] mapped with the legacy FILL_CENTER preview model (transform fallback). */
|
|
||||||
fun pairingIosStyleHoleInImageCoords(
|
fun pairingIosStyleHoleInImageCoords(
|
||||||
viewW: Int,
|
viewW: Int,
|
||||||
viewH: Int,
|
viewH: Int,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QSysInfo>
|
#include <QSysInfo>
|
||||||
#include "core/controllers/gatewayController.h"
|
|
||||||
#include "core/repositories/secureAppSettingsRepository.h"
|
#include "core/repositories/secureAppSettingsRepository.h"
|
||||||
#include "core/utils/api/apiUtils.h"
|
#include "core/utils/api/apiUtils.h"
|
||||||
#include "core/utils/constants/apiConstants.h"
|
#include "core/utils/constants/apiConstants.h"
|
||||||
@@ -13,8 +12,6 @@ using namespace amnezia;
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr auto kGenerateQrEndpoint = "%1api/v1/generate_qr";
|
|
||||||
constexpr auto kScanQrEndpoint = "%1api/v1/scan_qr";
|
|
||||||
constexpr qsizetype kPairingMaxQrUuidChars = 128;
|
constexpr qsizetype kPairingMaxQrUuidChars = 128;
|
||||||
constexpr qsizetype kPairingMaxVpnConfigChars = 256 * 1024;
|
constexpr qsizetype kPairingMaxVpnConfigChars = 256 * 1024;
|
||||||
constexpr qsizetype kPairingMaxApiKeyChars = 8192;
|
constexpr qsizetype kPairingMaxApiKeyChars = 8192;
|
||||||
@@ -186,50 +183,3 @@ QJsonObject PairingController::buildScanQrPayload(const QString &qrUuid, const Q
|
|||||||
o[apiDefs::key::osVersion] = QSysInfo::productType();
|
o[apiDefs::key::osVersion] = QSysInfo::productType();
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode PairingController::startPairing(const QString &qrUuid, QrPairingConfigPayload &outPayload)
|
|
||||||
{
|
|
||||||
outPayload = QrPairingConfigPayload {};
|
|
||||||
if (qrUuid.isEmpty()) {
|
|
||||||
return ErrorCode::ApiConfigEmptyError;
|
|
||||||
}
|
|
||||||
|
|
||||||
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(),
|
|
||||||
pairingLongPollTimeoutMsecs(), m_appSettingsRepository->isStrictKillSwitchEnabled());
|
|
||||||
|
|
||||||
QByteArray responseBody;
|
|
||||||
const ErrorCode transportError = gatewayController.post(QString::fromLatin1(kGenerateQrEndpoint), buildGenerateQrPayload(qrUuid), responseBody);
|
|
||||||
if (transportError != ErrorCode::NoError) {
|
|
||||||
return transportError;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QJsonObject obj = QJsonDocument::fromJson(responseBody).object();
|
|
||||||
return interpretGenerateQrJson(obj, outPayload);
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode PairingController::completePairing(const QString &qrUuid, const QString &vpnConfig, const QJsonObject &serviceInfo,
|
|
||||||
const QJsonArray &supportedProtocols, const QString &apiKey)
|
|
||||||
{
|
|
||||||
if (qrUuid.isEmpty() || vpnConfig.isEmpty() || apiKey.isEmpty()) {
|
|
||||||
return ErrorCode::ApiConfigEmptyError;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ErrorCode fieldErr = validatePairingScanFields(qrUuid, vpnConfig, apiKey);
|
|
||||||
if (fieldErr != ErrorCode::NoError) {
|
|
||||||
return fieldErr;
|
|
||||||
}
|
|
||||||
|
|
||||||
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(),
|
|
||||||
apiDefs::requestTimeoutMsecs, m_appSettingsRepository->isStrictKillSwitchEnabled());
|
|
||||||
|
|
||||||
QByteArray responseBody;
|
|
||||||
const ErrorCode transportError =
|
|
||||||
gatewayController.post(QString::fromLatin1(kScanQrEndpoint),
|
|
||||||
buildScanQrPayload(qrUuid, vpnConfig, serviceInfo, supportedProtocols, apiKey), responseBody);
|
|
||||||
if (transportError != ErrorCode::NoError) {
|
|
||||||
return transportError;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QJsonObject obj = QJsonDocument::fromJson(responseBody).object();
|
|
||||||
return interpretScanQrJson(obj);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -37,10 +37,6 @@ public:
|
|||||||
/** Length bounds before `scan_qr` (avoids huge JSON / abuse). */
|
/** Length bounds before `scan_qr` (avoids huge JSON / abuse). */
|
||||||
static amnezia::ErrorCode validatePairingScanFields(const QString &qrUuid, const QString &vpnConfig, const QString &apiKey);
|
static amnezia::ErrorCode validatePairingScanFields(const QString &qrUuid, const QString &vpnConfig, const QString &apiKey);
|
||||||
|
|
||||||
amnezia::ErrorCode startPairing(const QString &qrUuid, QrPairingConfigPayload &outPayload);
|
|
||||||
amnezia::ErrorCode completePairing(const QString &qrUuid, const QString &vpnConfig, const QJsonObject &serviceInfo,
|
|
||||||
const QJsonArray &supportedProtocols, const QString &apiKey);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SecureAppSettingsRepository *m_appSettingsRepository;
|
SecureAppSettingsRepository *m_appSettingsRepository;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ int pairingRetryDelayMs(int zeroBasedAttempt)
|
|||||||
return baseMs * (1 << zeroBasedAttempt);
|
return baseMs * (1 << zeroBasedAttempt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Legacy TV QR: generateQrCodeImageSeries base64url-wrapped QDataStream chunk (see qrCodeUtils.cpp). */
|
|
||||||
bool tryDecodeLegacyChunkedPairingQrPayload(const QString &t, QString *outUuid)
|
bool tryDecodeLegacyChunkedPairingQrPayload(const QString &t, QString *outUuid)
|
||||||
{
|
{
|
||||||
static const QRegularExpression binUrlSafe(QStringLiteral("^[A-Za-z0-9_-]+$"));
|
static const QRegularExpression binUrlSafe(QStringLiteral("^[A-Za-z0-9_-]+$"));
|
||||||
@@ -99,10 +98,6 @@ bool tryDecodeLegacyChunkedPairingQrPayload(const QString &t, QString *outUuid)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract a pairing session UUID from raw QR text without touching QObject / signals.
|
|
||||||
* Safe from CameraX / JNI threads while AmneziaActivity is stopped (Qt event loop may not run).
|
|
||||||
*/
|
|
||||||
QString extractPairingSessionUuidFromScanText(const QString &raw)
|
QString extractPairingSessionUuidFromScanText(const QString &raw)
|
||||||
{
|
{
|
||||||
const QString t = raw.trimmed();
|
const QString t = raw.trimmed();
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ class PairingUiController : public QObject
|
|||||||
Q_PROPERTY(QString tvSessionUuid READ tvSessionUuid NOTIFY tvSessionUuidChanged)
|
Q_PROPERTY(QString tvSessionUuid READ tvSessionUuid NOTIFY tvSessionUuidChanged)
|
||||||
Q_PROPERTY(bool tvPairingBusy READ tvPairingBusy NOTIFY tvPairingBusyChanged)
|
Q_PROPERTY(bool tvPairingBusy READ tvPairingBusy NOTIFY tvPairingBusyChanged)
|
||||||
Q_PROPERTY(QString tvStatusMessage READ tvStatusMessage NOTIFY tvStatusMessageChanged)
|
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(int tvPairingWaitWindowSeconds READ tvPairingWaitWindowSeconds NOTIFY tvQrCodesChanged)
|
||||||
|
|
||||||
Q_PROPERTY(bool phonePairingBusy READ phonePairingBusy NOTIFY phonePairingBusyChanged)
|
Q_PROPERTY(bool phonePairingBusy READ phonePairingBusy NOTIFY phonePairingBusyChanged)
|
||||||
@@ -33,19 +32,11 @@ class PairingUiController : public QObject
|
|||||||
pendingPhonePairingUuidChanged)
|
pendingPhonePairingUuidChanged)
|
||||||
Q_PROPERTY(QString lastSuccessfulPhonePairingDisplayName READ lastSuccessfulPhonePairingDisplayName NOTIFY
|
Q_PROPERTY(QString lastSuccessfulPhonePairingDisplayName READ lastSuccessfulPhonePairingDisplayName NOTIFY
|
||||||
lastSuccessfulPhonePairingDisplayNameChanged)
|
lastSuccessfulPhonePairingDisplayNameChanged)
|
||||||
/** TV flow for QA: 0=idle, 1=waitingForPeer, 2=error, 3=sessionExpired */
|
|
||||||
Q_PROPERTY(int tvPairingUiPhase READ tvPairingUiPhase NOTIFY tvPairingUiPhaseChanged)
|
Q_PROPERTY(int tvPairingUiPhase READ tvPairingUiPhase NOTIFY tvPairingUiPhaseChanged)
|
||||||
/** Full-screen pairing QR camera under QML (mobile); drives translucent main window. */
|
|
||||||
Q_PROPERTY(bool embeddedPairingQrCameraActive READ embeddedPairingQrCameraActive WRITE setEmbeddedPairingQrCameraActive NOTIFY
|
Q_PROPERTY(bool embeddedPairingQrCameraActive READ embeddedPairingQrCameraActive WRITE setEmbeddedPairingQrCameraActive NOTIFY
|
||||||
embeddedPairingQrCameraActiveChanged)
|
embeddedPairingQrCameraActiveChanged)
|
||||||
/** True only on iOS builds: use native UIWindow QR overlay (not Qt.platform.os, which can differ). */
|
|
||||||
Q_PROPERTY(bool iosNativePairingQrOverlayBuild READ iosNativePairingQrOverlayBuild CONSTANT)
|
Q_PROPERTY(bool iosNativePairingQrOverlayBuild READ iosNativePairingQrOverlayBuild CONSTANT)
|
||||||
/** True only on Android builds: full-screen CameraActivity pairing scanner; QML hides duplicate scan chrome. */
|
|
||||||
Q_PROPERTY(bool androidNativePairingQrOverlayBuild READ androidNativePairingQrOverlayBuild CONSTANT)
|
Q_PROPERTY(bool androidNativePairingQrOverlayBuild READ androidNativePairingQrOverlayBuild CONSTANT)
|
||||||
/**
|
|
||||||
* Epoch ms until which QML should not call openPairingQrScanner again (after native CameraActivity closes).
|
|
||||||
* Android pairing flow only; always 0 on other platforms.
|
|
||||||
*/
|
|
||||||
Q_PROPERTY(qint64 androidPairingReaderCooldownUntilEpochMs READ androidPairingReaderCooldownUntilEpochMs NOTIFY
|
Q_PROPERTY(qint64 androidPairingReaderCooldownUntilEpochMs READ androidPairingReaderCooldownUntilEpochMs NOTIFY
|
||||||
androidPairingReaderCooldownUntilEpochMsChanged)
|
androidPairingReaderCooldownUntilEpochMsChanged)
|
||||||
|
|
||||||
@@ -72,23 +63,12 @@ public:
|
|||||||
bool iosNativePairingQrOverlayBuild() const;
|
bool iosNativePairingQrOverlayBuild() const;
|
||||||
bool androidNativePairingQrOverlayBuild() const;
|
bool androidNativePairingQrOverlayBuild() const;
|
||||||
Q_INVOKABLE void setEmbeddedPairingQrCameraActive(bool active);
|
Q_INVOKABLE void setEmbeddedPairingQrCameraActive(bool active);
|
||||||
/** iOS: native dim strip height uses safe bottom + extraPt (see PageSettingsApiQrPairingSend scanDimBleedBottom). No-op elsewhere. */
|
|
||||||
Q_INVOKABLE void syncIosEmbeddedPairingQrNativeBottomExtra(int extraPt);
|
Q_INVOKABLE void syncIosEmbeddedPairingQrNativeBottomExtra(int extraPt);
|
||||||
/**
|
|
||||||
* iOS: reapply UIView transparency + safe-area dim strips when embedded pairing is already active.
|
|
||||||
* Needed after multitask resume: setEmbeddedPairingQrCameraActive(true) is a no-op if the flag stayed true,
|
|
||||||
* but QUIMetalView / hierarchy may have been rebuilt opaque so the camera only shows in the status bar band.
|
|
||||||
*/
|
|
||||||
Q_INVOKABLE void refreshIosEmbeddedPairingQrChrome();
|
Q_INVOKABLE void refreshIosEmbeddedPairingQrChrome();
|
||||||
|
|
||||||
qint64 androidPairingReaderCooldownUntilEpochMs() const { return m_androidPairingReaderCooldownUntilEpochMs; }
|
qint64 androidPairingReaderCooldownUntilEpochMs() const { return m_androidPairingReaderCooldownUntilEpochMs; }
|
||||||
/** Lengthens androidPairingReaderCooldownUntilEpochMs to at least now + ms (Android pairing; no-op elsewhere). */
|
|
||||||
Q_INVOKABLE void suppressAndroidNativePairingReaderStarts(int ms);
|
Q_INVOKABLE void suppressAndroidNativePairingReaderStarts(int ms);
|
||||||
|
|
||||||
/**
|
|
||||||
* iOS: UIKit UIWindow QR scanner (see iosPairingQrOverlayWindow). Pass translated title/subtitle for native chrome.
|
|
||||||
* No-op on other platforms.
|
|
||||||
*/
|
|
||||||
Q_INVOKABLE void presentIosPairingQrNativeOverlayScanner(const QString &title = QString(),
|
Q_INVOKABLE void presentIosPairingQrNativeOverlayScanner(const QString &title = QString(),
|
||||||
const QString &subtitle = QString());
|
const QString &subtitle = QString());
|
||||||
Q_INVOKABLE void dismissIosPairingQrNativeOverlayScanner();
|
Q_INVOKABLE void dismissIosPairingQrNativeOverlayScanner();
|
||||||
@@ -96,36 +76,25 @@ public:
|
|||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
static bool tryConsumeAndroidQrScan(const QString &code);
|
static bool tryConsumeAndroidQrScan(const QString &code);
|
||||||
/** JNI from CameraActivity onDestroy: avoid reopening native reader while camera HAL is still releasing. */
|
|
||||||
static void notifyAndroidPairingQrCameraClosed();
|
static void notifyAndroidPairingQrCameraClosed();
|
||||||
/** JNI before CameraActivity finish when user pressed back — Qt should reopen native scan (QML shell has no preview). */
|
|
||||||
static void notifyAndroidPairingQrCameraUserDismissed();
|
static void notifyAndroidPairingQrCameraUserDismissed();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/** Fast preflight before opening receive QR page; emits errorOccurred on failure. */
|
|
||||||
bool canOpenTvQrPairingPage();
|
bool canOpenTvQrPairingPage();
|
||||||
void startTvQrSession();
|
void startTvQrSession();
|
||||||
void cancelTvQrSession();
|
void cancelTvQrSession();
|
||||||
/** TV receive + phone send: call when leaving QR pairing (back / pop) so long-poll state does not stick. */
|
|
||||||
void cancelAllPairingActivity();
|
void cancelAllPairingActivity();
|
||||||
|
|
||||||
/** Sends the current premium/free API config from \a serverIndex to the gateway for the given \a qrUuid. */
|
|
||||||
void submitPhonePairing(const QString &qrUuid, int serverIndex);
|
void submitPhonePairing(const QString &qrUuid, int serverIndex);
|
||||||
|
|
||||||
/** Android: system camera activity. iOS: toggle camera from QML. */
|
|
||||||
void openPairingQrScanner();
|
void openPairingQrScanner();
|
||||||
|
|
||||||
/** Mobile: whether the app may use the camera for QR pairing (OS permission). Desktop: true. */
|
|
||||||
Q_INVOKABLE bool isPairingCameraAccessGranted() const;
|
Q_INVOKABLE bool isPairingCameraAccessGranted() const;
|
||||||
/** Mobile: show rationale / system camera permission UI; emits pairingCameraAccessFinished. Desktop: emits granted. */
|
|
||||||
Q_INVOKABLE void requestPairingCameraAccess();
|
Q_INVOKABLE void requestPairingCameraAccess();
|
||||||
/** Open system settings for this app (camera can be enabled there). No-op on desktop. */
|
|
||||||
Q_INVOKABLE void openPairingCameraAppSettings();
|
Q_INVOKABLE void openPairingCameraAppSettings();
|
||||||
/** Android: torch for embedded pairing camera. No-op elsewhere. */
|
|
||||||
Q_INVOKABLE void setPairingQrTorchEnabled(bool enabled);
|
Q_INVOKABLE void setPairingQrTorchEnabled(bool enabled);
|
||||||
|
|
||||||
/** If \a raw contains a session UUID (not vpn://), emits pairingUuidFromScan and returns true. */
|
|
||||||
bool applyScannedTextAsPairingUuid(const QString &raw);
|
bool applyScannedTextAsPairingUuid(const QString &raw);
|
||||||
|
|
||||||
Q_INVOKABLE void clearPendingPhonePairingUuid();
|
Q_INVOKABLE void clearPendingPhonePairingUuid();
|
||||||
@@ -146,15 +115,11 @@ signals:
|
|||||||
|
|
||||||
void pairingUuidFromScan(const QString &uuid);
|
void pairingUuidFromScan(const QString &uuid);
|
||||||
void tvPairingUiPhaseChanged();
|
void tvPairingUiPhaseChanged();
|
||||||
/** After requestPairingCameraAccess(): true if OS granted camera access. */
|
|
||||||
void pairingCameraAccessFinished(bool granted);
|
void pairingCameraAccessFinished(bool granted);
|
||||||
void embeddedPairingQrCameraActiveChanged();
|
void embeddedPairingQrCameraActiveChanged();
|
||||||
void androidPairingReaderCooldownUntilEpochMsChanged();
|
void androidPairingReaderCooldownUntilEpochMsChanged();
|
||||||
/** iOS native overlay scanner: payload was not a pairing session UUID (toast in QML). */
|
|
||||||
void pairingSendQrScanRejectedInvalidPayload();
|
void pairingSendQrScanRejectedInvalidPayload();
|
||||||
/** Native overlay back chevron tapped — dismiss scanner and close page from QML. */
|
|
||||||
void pairingIosNativeQrOverlayBackRequested();
|
void pairingIosNativeQrOverlayBackRequested();
|
||||||
/** Android CameraActivity: user pressed back — QML should exit pairing send (e.g. closePage), not reopen camera. */
|
|
||||||
void pairingAndroidNativeQrScannerUserDismissed();
|
void pairingAndroidNativeQrScannerUserDismissed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -16,31 +16,24 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
/** Loud dim colors when true (red/blue/cyan/orange regions). Sync with PageStart.pairingQrChromeDebug. */
|
|
||||||
property bool pairingQrChromeDebug: false
|
property bool pairingQrChromeDebug: false
|
||||||
|
|
||||||
/** iOS (and any non-Android mobile): native QRCodeReader; Qt may not always report os === "ios". */
|
|
||||||
readonly property bool useIosStyleNativeQrReader: GC.isMobile() && Qt.platform.os !== "android"
|
readonly property bool useIosStyleNativeQrReader: GC.isMobile() && Qt.platform.os !== "android"
|
||||||
|
|
||||||
/** iOS-only: full-screen UIKit UIWindow scanner (PairingUiController.iosNativePairingQrOverlayBuild — not Qt.platform.os). */
|
|
||||||
readonly property bool useIosNativePairingQrOverlay: PairingUiController.iosNativePairingQrOverlayBuild
|
readonly property bool useIosNativePairingQrOverlay: PairingUiController.iosNativePairingQrOverlayBuild
|
||||||
|
|
||||||
/** Android: full-screen CameraActivity — Qt cannot reliably composite CameraX under QML on some OEMs (e.g. Samsung). */
|
|
||||||
readonly property bool useAndroidNativePairingQrScanner: GC.isMobile() && Qt.platform.os === "android"
|
readonly property bool useAndroidNativePairingQrScanner: GC.isMobile() && Qt.platform.os === "android"
|
||||||
/** Android: iOS-like flow — titles and camera preview only in CameraActivity; QML hides duplicate scan chrome. */
|
|
||||||
readonly property bool useAndroidNativePairingQrOverlay: PairingUiController.androidNativePairingQrOverlayBuild
|
readonly property bool useAndroidNativePairingQrOverlay: PairingUiController.androidNativePairingQrOverlayBuild
|
||||||
&& GC.isMobile()
|
&& GC.isMobile()
|
||||||
&& Qt.platform.os === "android"
|
&& Qt.platform.os === "android"
|
||||||
|
|
||||||
/** Let dimming draw into window chrome (status bar + tab bar) when camera underlay is active. */
|
|
||||||
readonly property bool extendScanDimToScreenEdges: GC.isMobile() && pairingWizardStep === 0
|
readonly property bool extendScanDimToScreenEdges: GC.isMobile() && pairingWizardStep === 0
|
||||||
&& PairingUiController.embeddedPairingQrCameraActive
|
&& PairingUiController.embeddedPairingQrCameraActive
|
||||||
&& !root.useAndroidNativePairingQrOverlay
|
&& !root.useAndroidNativePairingQrOverlay
|
||||||
clip: !extendScanDimToScreenEdges
|
clip: !extendScanDimToScreenEdges
|
||||||
|
|
||||||
/** QQuickWindow (not Item); do not type as Item — breaks binding on Qt 6. */
|
/** QQuickWindow as var — typing as Item breaks bindings on Qt 6. */
|
||||||
readonly property var appWindow: Window.window
|
readonly property var appWindow: Window.window
|
||||||
/** Pixels of window above this page (status bar / safe area gap). */
|
|
||||||
readonly property real scanDimBleedTop: {
|
readonly property real scanDimBleedTop: {
|
||||||
if (!extendScanDimToScreenEdges || !appWindow || !appWindow.contentItem)
|
if (!extendScanDimToScreenEdges || !appWindow || !appWindow.contentItem)
|
||||||
return 0
|
return 0
|
||||||
@@ -49,7 +42,6 @@ PageType {
|
|||||||
bleed = Math.max(bleed, PageController.safeAreaTopMargin)
|
bleed = Math.max(bleed, PageController.safeAreaTopMargin)
|
||||||
return bleed
|
return bleed
|
||||||
}
|
}
|
||||||
/** Pixels of window below this page (tab bar + home indicator). */
|
|
||||||
readonly property real scanDimBleedBottom: {
|
readonly property real scanDimBleedBottom: {
|
||||||
if (!extendScanDimToScreenEdges || !appWindow || !appWindow.contentItem)
|
if (!extendScanDimToScreenEdges || !appWindow || !appWindow.contentItem)
|
||||||
return 0
|
return 0
|
||||||
@@ -63,15 +55,9 @@ PageType {
|
|||||||
return bleed
|
return bleed
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Bottom bleed for dimLayer only. On iOS embedded native QR, keep dim inside the page — semi-opaque dim
|
|
||||||
* extended into the tab stack seam composites badly with opaque tab chrome (persistent hairline).
|
|
||||||
* Native bottom mask still uses scanDimBleedBottom via pushIosNativeBottomBleedSync().
|
|
||||||
*/
|
|
||||||
readonly property real scanDimBleedBottomForDimLayer: (root.useIosStyleNativeQrReader
|
readonly property real scanDimBleedBottomForDimLayer: (root.useIosStyleNativeQrReader
|
||||||
&& PairingUiController.embeddedPairingQrCameraActive) ? 0 : scanDimBleedBottom
|
&& PairingUiController.embeddedPairingQrCameraActive) ? 0 : scanDimBleedBottom
|
||||||
|
|
||||||
/** iOS: extend UIKit bottom dim under QML tab bar (see iosPairingCameraAccess + PairingUiController). */
|
|
||||||
function pushIosNativeBottomBleedSync() {
|
function pushIosNativeBottomBleedSync() {
|
||||||
if (!root.useIosStyleNativeQrReader || !PairingUiController.embeddedPairingQrCameraActive) {
|
if (!root.useIosStyleNativeQrReader || !PairingUiController.embeddedPairingQrCameraActive) {
|
||||||
return
|
return
|
||||||
@@ -85,61 +71,16 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: pairingScanLayoutLogTimer
|
|
||||||
interval: 50
|
|
||||||
repeat: false
|
|
||||||
onTriggered: root.logPairingScanLayout("timer")
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: PairingUiController
|
target: PairingUiController
|
||||||
|
|
||||||
function onEmbeddedPairingQrCameraActiveChanged() {
|
function onEmbeddedPairingQrCameraActiveChanged() {
|
||||||
if (PairingUiController.embeddedPairingQrCameraActive) {
|
if (PairingUiController.embeddedPairingQrCameraActive) {
|
||||||
pairingScanLayoutLogTimer.restart()
|
|
||||||
Qt.callLater(root.pushIosNativeBottomBleedSync)
|
Qt.callLater(root.pushIosNativeBottomBleedSync)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function logPairingScanLayout(tag) {
|
|
||||||
const w = Window.window
|
|
||||||
const ci = w && w.contentItem ? w.contentItem : null
|
|
||||||
let m00 = null
|
|
||||||
let m0h = null
|
|
||||||
let scanBot = null
|
|
||||||
let dimTL = null
|
|
||||||
let dimBR = null
|
|
||||||
let dimHoleB = null
|
|
||||||
if (ci) {
|
|
||||||
m00 = root.mapToItem(ci, 0, 0)
|
|
||||||
m0h = root.mapToItem(ci, 0, root.height)
|
|
||||||
scanBot = scanStep.mapToItem(ci, 0, scanStep.height)
|
|
||||||
dimTL = dimLayer.mapToItem(ci, 0, 0)
|
|
||||||
dimBR = dimLayer.mapToItem(ci, dimLayer.width, dimLayer.height)
|
|
||||||
dimHoleB = dimLayer.holeBottom
|
|
||||||
}
|
|
||||||
console.warn("[PairingQrLayout]", tag,
|
|
||||||
"extend=", extendScanDimToScreenEdges,
|
|
||||||
"clip=", clip,
|
|
||||||
"root=", root.width, "x", root.height,
|
|
||||||
"win=", w ? w.width : -1, "x", w ? w.height : -1,
|
|
||||||
"contentItem=", ci ? ci.width : -1, "x", ci ? ci.height : -1,
|
|
||||||
"bleedT/B=", scanDimBleedTop, scanDimBleedBottom,
|
|
||||||
"dimLayerBleedB=", root.scanDimBleedBottomForDimLayer,
|
|
||||||
"safeT/B=", PageController.safeAreaTopMargin, PageController.safeAreaBottomMargin,
|
|
||||||
"map00=", m00 ? m00.x + "," + m00.y : "n/a",
|
|
||||||
"map0h=", m0h ? m0h.x + "," + m0h.y : "n/a",
|
|
||||||
"ci.scanStepBot=", scanBot ? scanBot.x.toFixed(1) + "," + scanBot.y.toFixed(1) : "n/a",
|
|
||||||
"ci.dimTL/BR=", dimTL ? dimTL.x.toFixed(1) + "," + dimTL.y.toFixed(1) : "n/a",
|
|
||||||
dimBR ? dimBR.x.toFixed(1) + "," + dimBR.y.toFixed(1) : "n/a",
|
|
||||||
"dimHoleB=", dimHoleB !== null ? dimHoleB.toFixed(1) : "n/a",
|
|
||||||
"win.screen=", w && w.screen ? w.screen.width + "x" + w.screen.height : "n/a",
|
|
||||||
"dimLayer wh=", dimLayer.width, "x", dimLayer.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 0 = scan QR, 1 = confirm before sending subscription */
|
|
||||||
property int pairingWizardStep: 0
|
property int pairingWizardStep: 0
|
||||||
property bool keepPhonePairingInBackgroundOnClose: false
|
property bool keepPhonePairingInBackgroundOnClose: false
|
||||||
|
|
||||||
@@ -188,11 +129,6 @@ PageType {
|
|||||||
if (addDeviceConfirmNavigationScheduled) {
|
if (addDeviceConfirmNavigationScheduled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.warn("[PairingQrSend] startMobileScanner Qt.platform.os=", Qt.platform.os,
|
|
||||||
"iosNativePairingQrOverlayBuild=", PairingUiController.iosNativePairingQrOverlayBuild,
|
|
||||||
"useIosNativePairingQrOverlay=", root.useIosNativePairingQrOverlay,
|
|
||||||
"androidNativePairingQrOverlayBuild=", PairingUiController.androidNativePairingQrOverlayBuild,
|
|
||||||
"useAndroidNativePairingQrOverlay=", root.useAndroidNativePairingQrOverlay)
|
|
||||||
if (!PairingUiController.isPairingCameraAccessGranted()) {
|
if (!PairingUiController.isPairingCameraAccessGranted()) {
|
||||||
awaitingCameraPermissionForScan = true
|
awaitingCameraPermissionForScan = true
|
||||||
PairingUiController.requestPairingCameraAccess()
|
PairingUiController.requestPairingCameraAccess()
|
||||||
@@ -208,14 +144,10 @@ PageType {
|
|||||||
if (root.useAndroidNativePairingQrScanner) {
|
if (root.useAndroidNativePairingQrScanner) {
|
||||||
const coolUntil = PairingUiController.androidPairingReaderCooldownUntilEpochMs
|
const coolUntil = PairingUiController.androidPairingReaderCooldownUntilEpochMs
|
||||||
if (Date.now() < coolUntil) {
|
if (Date.now() < coolUntil) {
|
||||||
console.warn("[PairingQrSend] startMobileScanner: skip (native camera cooldown), ms left=",
|
|
||||||
(coolUntil - Date.now()))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
if (now - _androidPairingReaderLastStartMs < 700) {
|
if (now - _androidPairingReaderLastStartMs < 700) {
|
||||||
console.warn("[PairingQrSend] startMobileScanner: skip duplicate Android CameraActivity within",
|
|
||||||
(now - _androidPairingReaderLastStartMs), "ms")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_androidPairingReaderLastStartMs = now
|
_androidPairingReaderLastStartMs = now
|
||||||
@@ -314,14 +246,8 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* StackView often instantiates the page already visible — onVisibleChanged(true) may never run, so
|
|
||||||
* startMobileScanner (and native overlay present) would be skipped; only stop/dismiss runs. Same pattern as
|
|
||||||
* PageSetupWizardApiQrPairingReceive (Component.onCompleted + visible).
|
|
||||||
*/
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (GC.isMobile() && root.visible && pairingWizardStep === 0) {
|
if (GC.isMobile() && root.visible && pairingWizardStep === 0) {
|
||||||
console.warn("[PairingQrSend] Component.onCompleted: schedule startMobileScanner (page created visible)")
|
|
||||||
Qt.callLater(startMobileScanner)
|
Qt.callLater(startMobileScanner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,29 +273,21 @@ PageType {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* No fixed ms delay: f1 reapply UIView transparency; f2 restart AVCapture; f3 refresh again —
|
|
||||||
* after restartPairingIosCamera, QUIMetalView / render thread often rebuilds opaque layers (same bug
|
|
||||||
* as status-bar-only camera) until underlay is reapplied once more.
|
|
||||||
*/
|
|
||||||
Qt.callLater(function () {
|
Qt.callLater(function () {
|
||||||
if (!root.visible || root.pairingWizardStep !== 0 || !GC.isMobile()) {
|
if (!root.visible || root.pairingWizardStep !== 0 || !GC.isMobile()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.warn("[PairingQrResume] ApplicationActive f1 underlay")
|
|
||||||
PairingUiController.embeddedPairingQrCameraActive = true
|
PairingUiController.embeddedPairingQrCameraActive = true
|
||||||
PairingUiController.refreshIosEmbeddedPairingQrChrome()
|
PairingUiController.refreshIosEmbeddedPairingQrChrome()
|
||||||
Qt.callLater(function () {
|
Qt.callLater(function () {
|
||||||
if (!root.visible || root.pairingWizardStep !== 0) {
|
if (!root.visible || root.pairingWizardStep !== 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.warn("[PairingQrResume] ApplicationActive f2 restart camera")
|
|
||||||
root.restartPairingIosCamera()
|
root.restartPairingIosCamera()
|
||||||
Qt.callLater(function () {
|
Qt.callLater(function () {
|
||||||
if (!root.visible || root.pairingWizardStep !== 0) {
|
if (!root.visible || root.pairingWizardStep !== 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.warn("[PairingQrResume] ApplicationActive f3 underlay post-camera")
|
|
||||||
PairingUiController.refreshIosEmbeddedPairingQrChrome()
|
PairingUiController.refreshIosEmbeddedPairingQrChrome()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,87 +19,15 @@ PageType {
|
|||||||
property bool isControlsDisabled: false
|
property bool isControlsDisabled: false
|
||||||
property bool isTabBarDisabled: false
|
property bool isTabBarDisabled: false
|
||||||
|
|
||||||
/** Loud colors (tab bar base green, extra overlap) when true — pair with PageSettingsApiQrPairingSend.pairingQrChromeDebug. */
|
|
||||||
property bool pairingQrChromeDebug: false
|
property bool pairingQrChromeDebug: false
|
||||||
|
|
||||||
/** Opaque extension of tab bar background upward (iOS embedded QR); see PairingTabChrome deltaY vs stack bottom. */
|
|
||||||
readonly property int tabBarChromeOverlapUp: (PairingUiController.embeddedPairingQrCameraActive && GC.isMobile()
|
readonly property int tabBarChromeOverlapUp: (PairingUiController.embeddedPairingQrCameraActive && GC.isMobile()
|
||||||
&& Qt.platform.os !== "android")
|
&& Qt.platform.os !== "android")
|
||||||
? (root.pairingQrChromeDebug ? 24 : 18) : 0
|
? (root.pairingQrChromeDebug ? 24 : 18) : 0
|
||||||
|
|
||||||
/** Pull stack under tab chrome so TabBar.background overlap fully covers stack bottom pixels. */
|
|
||||||
readonly property int tabStackPairingUnderlapDown: (PairingUiController.embeddedPairingQrCameraActive && GC.isMobile()
|
readonly property int tabStackPairingUnderlapDown: (PairingUiController.embeddedPairingQrCameraActive && GC.isMobile()
|
||||||
&& Qt.platform.os !== "android") ? 8 : 0
|
&& Qt.platform.os !== "android") ? 8 : 0
|
||||||
|
|
||||||
readonly property bool pairingTabChromeLogActive: PairingUiController.embeddedPairingQrCameraActive && GC.isMobile()
|
|
||||||
&& Qt.platform.os !== "android"
|
|
||||||
|
|
||||||
function logPairingTabChromeLayout(tag) {
|
|
||||||
if (!root.pairingTabChromeLogActive) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const w = Window.window
|
|
||||||
const ci = w && w.contentItem ? w.contentItem : null
|
|
||||||
let msg = "[PairingTabChrome] " + tag
|
|
||||||
msg += " PageStart=" + Math.round(root.width) + "x" + Math.round(root.height)
|
|
||||||
msg += " tabBar=" + Math.round(tabBar.width) + "x" + Math.round(tabBar.height) + " y=" + tabBar.y.toFixed(2)
|
|
||||||
msg += " imeBM=" + PageController.imeHeight
|
|
||||||
msg += " stack=" + Math.round(tabBarStackView.width) + "x" + Math.round(tabBarStackView.height)
|
|
||||||
msg += " overlapUp=" + tabBarChromeOverlapUp + " stackUnderlap=" + tabStackPairingUnderlapDown
|
|
||||||
msg += " tabBgRootH=" + (tabBarBackgroundRoot ? tabBarBackgroundRoot.height.toFixed(2) : "n/a")
|
|
||||||
if (ci) {
|
|
||||||
const tabOrigin = tabBar.mapToItem(ci, 0, 0)
|
|
||||||
const tabBandTop = tabBar.mapToItem(ci, 0, -tabBarChromeOverlapUp)
|
|
||||||
const stackOrigin = tabBarStackView.mapToItem(ci, 0, 0)
|
|
||||||
const stackBottomMid = tabBarStackView.mapToItem(ci, tabBarStackView.width * 0.5, tabBarStackView.height)
|
|
||||||
const bgTopLeft = tabBarBackgroundRoot.mapToItem(ci, 0, 0)
|
|
||||||
msg += " ci.tab(0,0)=" + tabOrigin.x.toFixed(1) + "," + tabOrigin.y.toFixed(1)
|
|
||||||
msg += " ci.tab(0,-ov)=" + tabBandTop.x.toFixed(1) + "," + tabBandTop.y.toFixed(1)
|
|
||||||
msg += " ci.stack(0,0)=" + stackOrigin.x.toFixed(1) + "," + stackOrigin.y.toFixed(1)
|
|
||||||
msg += " ci.stackMidBot=" + stackBottomMid.x.toFixed(1) + "," + stackBottomMid.y.toFixed(1)
|
|
||||||
msg += " ci.tabBgRoot(0,0)=" + bgTopLeft.x.toFixed(1) + "," + bgTopLeft.y.toFixed(1)
|
|
||||||
msg += " deltaY_tabBandTop_minus_stackMidBot=" + (tabBandTop.y - stackBottomMid.y).toFixed(2)
|
|
||||||
} else {
|
|
||||||
msg += " ci=missing"
|
|
||||||
}
|
|
||||||
console.warn(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: pairingTabChromeLogTimer50
|
|
||||||
interval: 50
|
|
||||||
repeat: false
|
|
||||||
onTriggered: root.logPairingTabChromeLayout("t50")
|
|
||||||
}
|
|
||||||
Timer {
|
|
||||||
id: pairingTabChromeLogTimer350
|
|
||||||
interval: 350
|
|
||||||
repeat: false
|
|
||||||
onTriggered: root.logPairingTabChromeLayout("t350")
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: PairingUiController
|
|
||||||
|
|
||||||
function onEmbeddedPairingQrCameraActiveChanged() {
|
|
||||||
if (PairingUiController.embeddedPairingQrCameraActive && GC.isMobile() && Qt.platform.os !== "android") {
|
|
||||||
pairingTabChromeLogTimer50.restart()
|
|
||||||
pairingTabChromeLogTimer350.restart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onWidthChanged: {
|
|
||||||
if (root.pairingTabChromeLogActive) {
|
|
||||||
pairingTabChromeLogTimer50.restart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onHeightChanged: {
|
|
||||||
if (root.pairingTabChromeLogActive) {
|
|
||||||
pairingTabChromeLogTimer50.restart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
objectName: "pageControllerConnection"
|
objectName: "pageControllerConnection"
|
||||||
|
|
||||||
@@ -413,12 +341,10 @@ PageType {
|
|||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
height: parent.height + root.tabBarChromeOverlapUp
|
height: parent.height + root.tabBarChromeOverlapUp
|
||||||
|
|
||||||
/** Opaque base: Shape alone can show the window-layer camera through anti-aliased edges when the window is clear (iOS QR pairing). */
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: root.pairingQrChromeDebug ? "#00ff66" : AmneziaStyle.color.onyxBlack
|
color: root.pairingQrChromeDebug ? "#00ff66" : AmneziaStyle.color.onyxBlack
|
||||||
}
|
}
|
||||||
/** Stroke around tab row; hidden during iOS embedded QR overlap — top horizontal slateGray reads as a hairline “strip” above tabs. */
|
|
||||||
Shape {
|
Shape {
|
||||||
id: tabBarChromeShape
|
id: tabBarChromeShape
|
||||||
objectName: "backgroundShape"
|
objectName: "backgroundShape"
|
||||||
|
|||||||
Reference in New Issue
Block a user