mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +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)
|
||||
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})
|
||||
|
||||
# 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)
|
||||
void AmneziaApplication::startLocalServer() {
|
||||
#ifdef AMNEZIA_QR_PAIRING_ALLOW
|
||||
return;
|
||||
#endif
|
||||
|
||||
const QString serverName("AmneziaVPNInstance");
|
||||
QLocalServer::removeServer(serverName);
|
||||
|
||||
|
||||
@@ -7,40 +7,7 @@ import kotlin.math.floor
|
||||
import kotlin.math.max
|
||||
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 {
|
||||
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(
|
||||
viewW: 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 {
|
||||
val d = density
|
||||
var holeR = min(28f * d, max(10f * d, sidePx * 0.056f))
|
||||
@@ -77,7 +34,6 @@ object PairingQrScanGeometry {
|
||||
return max(holeR, 1f)
|
||||
}
|
||||
|
||||
/** Area(roi ∩ box) / area(box); 0 if disjoint. */
|
||||
fun barcodeBoxOverlapFraction(roi: RectF, box: Rect): Float {
|
||||
val bf = RectF(box)
|
||||
val inter = RectF(roi)
|
||||
@@ -87,40 +43,6 @@ object PairingQrScanGeometry {
|
||||
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(
|
||||
roiInImageSpace: RectF,
|
||||
imageW: Int,
|
||||
@@ -161,14 +83,8 @@ object PairingQrScanGeometry {
|
||||
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
|
||||
|
||||
/**
|
||||
* 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(
|
||||
viewW: Int,
|
||||
viewH: Int,
|
||||
@@ -201,9 +117,6 @@ object PairingQrScanGeometry {
|
||||
return RectF(sqX, sqY, sqX + sqSz, sqY + sqSz)
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertical center of the torch control in px (same math as iOS `torchCenterYConstraint` update).
|
||||
*/
|
||||
fun pairingIosStyleTorchCenterYPx(
|
||||
holeBottomPx: Float,
|
||||
bandBottomPx: Float,
|
||||
@@ -224,7 +137,6 @@ object PairingQrScanGeometry {
|
||||
return max(torchCy, hdr)
|
||||
}
|
||||
|
||||
/** [pairingIosStyleHoleRectF] mapped with the legacy FILL_CENTER preview model (transform fallback). */
|
||||
fun pairingIosStyleHoleInImageCoords(
|
||||
viewW: Int,
|
||||
viewH: Int,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QSysInfo>
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
@@ -13,8 +12,6 @@ using namespace amnezia;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto kGenerateQrEndpoint = "%1api/v1/generate_qr";
|
||||
constexpr auto kScanQrEndpoint = "%1api/v1/scan_qr";
|
||||
constexpr qsizetype kPairingMaxQrUuidChars = 128;
|
||||
constexpr qsizetype kPairingMaxVpnConfigChars = 256 * 1024;
|
||||
constexpr qsizetype kPairingMaxApiKeyChars = 8192;
|
||||
@@ -186,50 +183,3 @@ QJsonObject PairingController::buildScanQrPayload(const QString &qrUuid, const Q
|
||||
o[apiDefs::key::osVersion] = QSysInfo::productType();
|
||||
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). */
|
||||
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:
|
||||
SecureAppSettingsRepository *m_appSettingsRepository;
|
||||
};
|
||||
|
||||
@@ -55,7 +55,6 @@ int pairingRetryDelayMs(int zeroBasedAttempt)
|
||||
return baseMs * (1 << zeroBasedAttempt);
|
||||
}
|
||||
|
||||
/** Legacy TV QR: generateQrCodeImageSeries base64url-wrapped QDataStream chunk (see qrCodeUtils.cpp). */
|
||||
bool tryDecodeLegacyChunkedPairingQrPayload(const QString &t, QString *outUuid)
|
||||
{
|
||||
static const QRegularExpression binUrlSafe(QStringLiteral("^[A-Za-z0-9_-]+$"));
|
||||
@@ -99,10 +98,6 @@ bool tryDecodeLegacyChunkedPairingQrPayload(const QString &t, QString *outUuid)
|
||||
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)
|
||||
{
|
||||
const QString t = raw.trimmed();
|
||||
|
||||
@@ -24,7 +24,6 @@ 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)
|
||||
@@ -33,19 +32,11 @@ class PairingUiController : public QObject
|
||||
pendingPhonePairingUuidChanged)
|
||||
Q_PROPERTY(QString lastSuccessfulPhonePairingDisplayName READ lastSuccessfulPhonePairingDisplayName NOTIFY
|
||||
lastSuccessfulPhonePairingDisplayNameChanged)
|
||||
/** TV flow for QA: 0=idle, 1=waitingForPeer, 2=error, 3=sessionExpired */
|
||||
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
|
||||
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)
|
||||
/** True only on Android builds: full-screen CameraActivity pairing scanner; QML hides duplicate scan chrome. */
|
||||
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
|
||||
androidPairingReaderCooldownUntilEpochMsChanged)
|
||||
|
||||
@@ -72,23 +63,12 @@ public:
|
||||
bool iosNativePairingQrOverlayBuild() const;
|
||||
bool androidNativePairingQrOverlayBuild() const;
|
||||
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);
|
||||
/**
|
||||
* 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();
|
||||
|
||||
qint64 androidPairingReaderCooldownUntilEpochMs() const { return m_androidPairingReaderCooldownUntilEpochMs; }
|
||||
/** Lengthens androidPairingReaderCooldownUntilEpochMs to at least now + ms (Android pairing; no-op elsewhere). */
|
||||
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(),
|
||||
const QString &subtitle = QString());
|
||||
Q_INVOKABLE void dismissIosPairingQrNativeOverlayScanner();
|
||||
@@ -96,36 +76,25 @@ public:
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
static bool tryConsumeAndroidQrScan(const QString &code);
|
||||
/** JNI from CameraActivity onDestroy: avoid reopening native reader while camera HAL is still releasing. */
|
||||
static void notifyAndroidPairingQrCameraClosed();
|
||||
/** JNI before CameraActivity finish when user pressed back — Qt should reopen native scan (QML shell has no preview). */
|
||||
static void notifyAndroidPairingQrCameraUserDismissed();
|
||||
#endif
|
||||
|
||||
public slots:
|
||||
/** Fast preflight before opening receive QR page; emits errorOccurred on failure. */
|
||||
bool canOpenTvQrPairingPage();
|
||||
void startTvQrSession();
|
||||
void cancelTvQrSession();
|
||||
/** TV receive + phone send: call when leaving QR pairing (back / pop) so long-poll state does not stick. */
|
||||
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);
|
||||
|
||||
/** Android: system camera activity. iOS: toggle camera from QML. */
|
||||
void openPairingQrScanner();
|
||||
|
||||
/** Mobile: whether the app may use the camera for QR pairing (OS permission). Desktop: true. */
|
||||
Q_INVOKABLE bool isPairingCameraAccessGranted() const;
|
||||
/** Mobile: show rationale / system camera permission UI; emits pairingCameraAccessFinished. Desktop: emits granted. */
|
||||
Q_INVOKABLE void requestPairingCameraAccess();
|
||||
/** Open system settings for this app (camera can be enabled there). No-op on desktop. */
|
||||
Q_INVOKABLE void openPairingCameraAppSettings();
|
||||
/** Android: torch for embedded pairing camera. No-op elsewhere. */
|
||||
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);
|
||||
|
||||
Q_INVOKABLE void clearPendingPhonePairingUuid();
|
||||
@@ -146,15 +115,11 @@ signals:
|
||||
|
||||
void pairingUuidFromScan(const QString &uuid);
|
||||
void tvPairingUiPhaseChanged();
|
||||
/** After requestPairingCameraAccess(): true if OS granted camera access. */
|
||||
void pairingCameraAccessFinished(bool granted);
|
||||
void embeddedPairingQrCameraActiveChanged();
|
||||
void androidPairingReaderCooldownUntilEpochMsChanged();
|
||||
/** iOS native overlay scanner: payload was not a pairing session UUID (toast in QML). */
|
||||
void pairingSendQrScanRejectedInvalidPayload();
|
||||
/** Native overlay back chevron tapped — dismiss scanner and close page from QML. */
|
||||
void pairingIosNativeQrOverlayBackRequested();
|
||||
/** Android CameraActivity: user pressed back — QML should exit pairing send (e.g. closePage), not reopen camera. */
|
||||
void pairingAndroidNativeQrScannerUserDismissed();
|
||||
|
||||
private:
|
||||
|
||||
@@ -16,31 +16,24 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
/** Loud dim colors when true (red/blue/cyan/orange regions). Sync with PageStart.pairingQrChromeDebug. */
|
||||
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"
|
||||
|
||||
/** iOS-only: full-screen UIKit UIWindow scanner (PairingUiController.iosNativePairingQrOverlayBuild — not Qt.platform.os). */
|
||||
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"
|
||||
/** Android: iOS-like flow — titles and camera preview only in CameraActivity; QML hides duplicate scan chrome. */
|
||||
readonly property bool useAndroidNativePairingQrOverlay: PairingUiController.androidNativePairingQrOverlayBuild
|
||||
&& GC.isMobile()
|
||||
&& 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
|
||||
&& PairingUiController.embeddedPairingQrCameraActive
|
||||
&& !root.useAndroidNativePairingQrOverlay
|
||||
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
|
||||
/** Pixels of window above this page (status bar / safe area gap). */
|
||||
readonly property real scanDimBleedTop: {
|
||||
if (!extendScanDimToScreenEdges || !appWindow || !appWindow.contentItem)
|
||||
return 0
|
||||
@@ -49,7 +42,6 @@ PageType {
|
||||
bleed = Math.max(bleed, PageController.safeAreaTopMargin)
|
||||
return bleed
|
||||
}
|
||||
/** Pixels of window below this page (tab bar + home indicator). */
|
||||
readonly property real scanDimBleedBottom: {
|
||||
if (!extendScanDimToScreenEdges || !appWindow || !appWindow.contentItem)
|
||||
return 0
|
||||
@@ -63,15 +55,9 @@ PageType {
|
||||
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
|
||||
&& PairingUiController.embeddedPairingQrCameraActive) ? 0 : scanDimBleedBottom
|
||||
|
||||
/** iOS: extend UIKit bottom dim under QML tab bar (see iosPairingCameraAccess + PairingUiController). */
|
||||
function pushIosNativeBottomBleedSync() {
|
||||
if (!root.useIosStyleNativeQrReader || !PairingUiController.embeddedPairingQrCameraActive) {
|
||||
return
|
||||
@@ -85,61 +71,16 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: pairingScanLayoutLogTimer
|
||||
interval: 50
|
||||
repeat: false
|
||||
onTriggered: root.logPairingScanLayout("timer")
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PairingUiController
|
||||
|
||||
function onEmbeddedPairingQrCameraActiveChanged() {
|
||||
if (PairingUiController.embeddedPairingQrCameraActive) {
|
||||
pairingScanLayoutLogTimer.restart()
|
||||
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 bool keepPhonePairingInBackgroundOnClose: false
|
||||
|
||||
@@ -188,11 +129,6 @@ PageType {
|
||||
if (addDeviceConfirmNavigationScheduled) {
|
||||
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()) {
|
||||
awaitingCameraPermissionForScan = true
|
||||
PairingUiController.requestPairingCameraAccess()
|
||||
@@ -208,14 +144,10 @@ PageType {
|
||||
if (root.useAndroidNativePairingQrScanner) {
|
||||
const coolUntil = PairingUiController.androidPairingReaderCooldownUntilEpochMs
|
||||
if (Date.now() < coolUntil) {
|
||||
console.warn("[PairingQrSend] startMobileScanner: skip (native camera cooldown), ms left=",
|
||||
(coolUntil - Date.now()))
|
||||
return
|
||||
}
|
||||
const now = Date.now()
|
||||
if (now - _androidPairingReaderLastStartMs < 700) {
|
||||
console.warn("[PairingQrSend] startMobileScanner: skip duplicate Android CameraActivity within",
|
||||
(now - _androidPairingReaderLastStartMs), "ms")
|
||||
return
|
||||
}
|
||||
_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: {
|
||||
if (GC.isMobile() && root.visible && pairingWizardStep === 0) {
|
||||
console.warn("[PairingQrSend] Component.onCompleted: schedule startMobileScanner (page created visible)")
|
||||
Qt.callLater(startMobileScanner)
|
||||
}
|
||||
}
|
||||
@@ -347,29 +273,21 @@ PageType {
|
||||
})
|
||||
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 () {
|
||||
if (!root.visible || root.pairingWizardStep !== 0 || !GC.isMobile()) {
|
||||
return
|
||||
}
|
||||
console.warn("[PairingQrResume] ApplicationActive f1 underlay")
|
||||
PairingUiController.embeddedPairingQrCameraActive = true
|
||||
PairingUiController.refreshIosEmbeddedPairingQrChrome()
|
||||
Qt.callLater(function () {
|
||||
if (!root.visible || root.pairingWizardStep !== 0) {
|
||||
return
|
||||
}
|
||||
console.warn("[PairingQrResume] ApplicationActive f2 restart camera")
|
||||
root.restartPairingIosCamera()
|
||||
Qt.callLater(function () {
|
||||
if (!root.visible || root.pairingWizardStep !== 0) {
|
||||
return
|
||||
}
|
||||
console.warn("[PairingQrResume] ApplicationActive f3 underlay post-camera")
|
||||
PairingUiController.refreshIosEmbeddedPairingQrChrome()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -19,87 +19,15 @@ PageType {
|
||||
property bool isControlsDisabled: false
|
||||
property bool isTabBarDisabled: false
|
||||
|
||||
/** Loud colors (tab bar base green, extra overlap) when true — pair with PageSettingsApiQrPairingSend.pairingQrChromeDebug. */
|
||||
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()
|
||||
&& Qt.platform.os !== "android")
|
||||
? (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()
|
||||
&& 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 {
|
||||
objectName: "pageControllerConnection"
|
||||
|
||||
@@ -413,12 +341,10 @@ PageType {
|
||||
anchors.bottom: parent.bottom
|
||||
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 {
|
||||
anchors.fill: parent
|
||||
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 {
|
||||
id: tabBarChromeShape
|
||||
objectName: "backgroundShape"
|
||||
|
||||
Reference in New Issue
Block a user