mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
remove log & remove temp code
This commit is contained in:
@@ -108,7 +108,6 @@ class AmneziaActivity : QtActivity(), LifecycleOwner {
|
||||
private var pendingOpenFileUri: String? = null
|
||||
private var openFileDeliveryScheduled = false
|
||||
|
||||
private var pairingQrEmbeddedCamera: PairingQrEmbeddedCamera? = null
|
||||
private var lastPairingQrReaderStartUptimeMs: Long = 0L
|
||||
|
||||
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
||||
@@ -998,26 +997,6 @@ class AmneziaActivity : QtActivity(), LifecycleOwner {
|
||||
return heightDp
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun startPairingQrEmbeddedCamera() {
|
||||
Log.v(TAG, "startPairingQrEmbeddedCamera")
|
||||
if (pairingQrEmbeddedCamera == null) {
|
||||
pairingQrEmbeddedCamera = PairingQrEmbeddedCamera(this)
|
||||
}
|
||||
pairingQrEmbeddedCamera?.start()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun stopPairingQrEmbeddedCamera() {
|
||||
Log.v(TAG, "stopPairingQrEmbeddedCamera")
|
||||
pairingQrEmbeddedCamera?.stop()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun setPairingQrEmbeddedTorch(enabled: Boolean) {
|
||||
pairingQrEmbeddedCamera?.setTorch(enabled)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun startQrCodeReader() {
|
||||
Log.v(TAG, "Start camera")
|
||||
@@ -1030,11 +1009,9 @@ class AmneziaActivity : QtActivity(), LifecycleOwner {
|
||||
fun startPairingQrCodeReader() {
|
||||
val now = SystemClock.uptimeMillis()
|
||||
if (now - lastPairingQrReaderStartUptimeMs < 1200L) {
|
||||
Log.w(TAG, "startPairingQrCodeReader: suppressed duplicate (${now - lastPairingQrReaderStartUptimeMs}ms)")
|
||||
return
|
||||
}
|
||||
lastPairingQrReaderStartUptimeMs = now
|
||||
Log.v(TAG, "Start pairing QR camera")
|
||||
Intent(this, CameraActivity::class.java).also {
|
||||
it.putExtra(CameraActivity.EXTRA_PAIRING_QR_CAMERA, true)
|
||||
startActivity(it)
|
||||
|
||||
@@ -170,7 +170,6 @@ class CameraActivity : ComponentActivity() {
|
||||
if (!::viewBinding.isInitialized) {
|
||||
return
|
||||
}
|
||||
Log.v(TAG, "onNewIntent: rebind pairing camera")
|
||||
cleanupCameraResources()
|
||||
qrHandledOrClosing.set(false)
|
||||
pairingQrDeliveredToQt = false
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.camera.core.Camera
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.ExperimentalGetImage
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||
import com.google.mlkit.vision.barcode.BarcodeScannerOptions.Builder
|
||||
import com.google.mlkit.vision.barcode.ZoomSuggestionOptions
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import org.amnezia.vpn.qt.QtAndroidController
|
||||
import org.amnezia.vpn.util.Log
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
private const val TAG = "PairingQrEmbedded"
|
||||
|
||||
@OptIn(ExperimentalGetImage::class)
|
||||
class PairingQrEmbeddedCamera(private val activity: AmneziaActivity) {
|
||||
|
||||
private var previewView: PreviewView? = null
|
||||
private var cameraProvider: ProcessCameraProvider? = null
|
||||
private var boundCamera: Camera? = null
|
||||
private val checkedBarcodes = hashSetOf<String>()
|
||||
private var barcodeScanner: com.google.mlkit.vision.barcode.BarcodeScanner? = null
|
||||
|
||||
private val windowBgOpaque = ColorDrawable(Color.parseColor("#0E0E11"))
|
||||
|
||||
private var savedWindowFormat: Int? = null
|
||||
|
||||
private var imageAnalysisExecutor: ExecutorService? = null
|
||||
|
||||
fun start() {
|
||||
activity.runOnUiThread {
|
||||
if (previewView != null) {
|
||||
return@runOnUiThread
|
||||
}
|
||||
checkedBarcodes.clear()
|
||||
activity.window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
val lp = activity.window.attributes
|
||||
if (savedWindowFormat == null) {
|
||||
savedWindowFormat = lp.format
|
||||
}
|
||||
lp.format = PixelFormat.TRANSLUCENT
|
||||
activity.window.attributes = lp
|
||||
|
||||
val content = activity.findViewById<ViewGroup>(android.R.id.content)
|
||||
content.clipChildren = false
|
||||
content.clipToPadding = false
|
||||
val pv = PreviewView(activity).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
// TextureView-style path so preview can show through “holes” in Qt Quick on some OEMs (e.g. Samsung).
|
||||
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
|
||||
scaleType = PreviewView.ScaleType.FILL_CENTER
|
||||
setBackgroundColor(Color.TRANSPARENT)
|
||||
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||
}
|
||||
content.addView(pv, 0)
|
||||
for (i in 1 until content.childCount) {
|
||||
content.getChildAt(i).bringToFront()
|
||||
}
|
||||
previewView = pv
|
||||
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(activity)
|
||||
cameraProviderFuture.addListener({
|
||||
try {
|
||||
cameraProvider = cameraProviderFuture.get()
|
||||
bindUseCases(pv)
|
||||
pv.post {
|
||||
pv.requestLayout()
|
||||
pv.invalidate()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Camera bind failed: $e")
|
||||
stop()
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(activity))
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindUseCases(viewFinder: PreviewView) {
|
||||
val provider = cameraProvider ?: return
|
||||
provider.unbindAll()
|
||||
imageAnalysisExecutor?.shutdown()
|
||||
imageAnalysisExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
val preview = Preview.Builder().build().also {
|
||||
it.setSurfaceProvider(viewFinder.surfaceProvider)
|
||||
}
|
||||
|
||||
val imageAnalysis = ImageAnalysis.Builder()
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.build()
|
||||
|
||||
val cam = provider.bindToLifecycle(
|
||||
activity,
|
||||
CameraSelector.DEFAULT_BACK_CAMERA,
|
||||
preview,
|
||||
imageAnalysis
|
||||
)
|
||||
boundCamera = cam
|
||||
|
||||
barcodeScanner = BarcodeScanning.getClient(
|
||||
Builder()
|
||||
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
|
||||
.setZoomSuggestionOptions(
|
||||
ZoomSuggestionOptions.Builder { zoomLevel ->
|
||||
activity.runOnUiThread {
|
||||
try {
|
||||
cam.cameraControl.setZoomRatio(zoomLevel)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Zoom: $e")
|
||||
}
|
||||
}
|
||||
true
|
||||
}.apply {
|
||||
cam.cameraInfo.zoomState.value?.maxZoomRatio?.let { maxZoom ->
|
||||
setMaxSupportedZoomRatio(maxZoom)
|
||||
}
|
||||
}.build()
|
||||
).build()
|
||||
)
|
||||
|
||||
val scanner = barcodeScanner!!
|
||||
val analysisExecutor = imageAnalysisExecutor!!
|
||||
imageAnalysis.setAnalyzer(analysisExecutor) { imageProxy ->
|
||||
val mediaImage = imageProxy.image ?: run {
|
||||
imageProxy.close()
|
||||
return@setAnalyzer
|
||||
}
|
||||
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
|
||||
scanner.process(image)
|
||||
.addOnSuccessListener(analysisExecutor) { barcodes ->
|
||||
barcodes.firstOrNull()?.displayValue?.let { code ->
|
||||
if (code.isNotEmpty() && code !in checkedBarcodes) {
|
||||
checkedBarcodes.add(code)
|
||||
if (QtAndroidController.decodeQrCode(code)) {
|
||||
scanner.close()
|
||||
barcodeScanner = null
|
||||
activity.runOnUiThread { stop() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.addOnFailureListener(analysisExecutor) {
|
||||
Log.e(TAG, "QR process failed: ${it.message}")
|
||||
}
|
||||
.addOnCompleteListener(analysisExecutor) {
|
||||
imageProxy.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setTorch(on: Boolean) {
|
||||
activity.runOnUiThread {
|
||||
try {
|
||||
boundCamera?.cameraControl?.enableTorch(on)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Torch: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
activity.runOnUiThread {
|
||||
imageAnalysisExecutor?.shutdown()
|
||||
imageAnalysisExecutor = null
|
||||
try {
|
||||
boundCamera?.cameraControl?.enableTorch(false)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
boundCamera = null
|
||||
barcodeScanner?.close()
|
||||
barcodeScanner = null
|
||||
cameraProvider?.unbindAll()
|
||||
cameraProvider = null
|
||||
|
||||
previewView?.let { pv ->
|
||||
(pv.parent as? ViewGroup)?.removeView(pv)
|
||||
}
|
||||
previewView = null
|
||||
|
||||
activity.window.setBackgroundDrawable(windowBgOpaque)
|
||||
savedWindowFormat?.let { fmt ->
|
||||
val lp = activity.window.attributes
|
||||
lp.format = fmt
|
||||
activity.window.attributes = lp
|
||||
savedWindowFormat = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,21 +250,6 @@ void AndroidController::startPairingQrReaderActivity()
|
||||
callActivityMethod("startPairingQrCodeReader", "()V");
|
||||
}
|
||||
|
||||
void AndroidController::startPairingQrEmbeddedCamera()
|
||||
{
|
||||
callActivityMethod("startPairingQrEmbeddedCamera", "()V");
|
||||
}
|
||||
|
||||
void AndroidController::stopPairingQrEmbeddedCamera()
|
||||
{
|
||||
callActivityMethod("stopPairingQrEmbeddedCamera", "()V");
|
||||
}
|
||||
|
||||
void AndroidController::setPairingQrEmbeddedTorch(bool enabled)
|
||||
{
|
||||
callActivityMethod("setPairingQrEmbeddedTorch", "(Z)V", enabled);
|
||||
}
|
||||
|
||||
void AndroidController::setSaveLogs(bool enabled)
|
||||
{
|
||||
callActivityMethod("setSaveLogs", "(Z)V", enabled);
|
||||
|
||||
@@ -47,9 +47,6 @@ public:
|
||||
int getNavigationBarHeight();
|
||||
void startQrReaderActivity();
|
||||
void startPairingQrReaderActivity();
|
||||
void startPairingQrEmbeddedCamera();
|
||||
void stopPairingQrEmbeddedCamera();
|
||||
void setPairingQrEmbeddedTorch(bool enabled);
|
||||
void setSaveLogs(bool enabled);
|
||||
void exportLogsFile(const QString &fileName);
|
||||
void clearLogs();
|
||||
|
||||
@@ -4,51 +4,15 @@
|
||||
#include "platforms/ios/iosPairingCameraAccess.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
static NSString *amneziaQrThreadTag(void)
|
||||
{
|
||||
if ([NSThread isMainThread]) {
|
||||
return @"main";
|
||||
}
|
||||
return [NSString stringWithFormat:@"bg:%p", (void *)[NSThread currentThread]];
|
||||
}
|
||||
|
||||
static void amneziaQrLogDeviceAuth(void)
|
||||
{
|
||||
AVAuthorizationStatus st = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
||||
NSString *stName = @"unknown";
|
||||
switch (st) {
|
||||
case AVAuthorizationStatusNotDetermined:
|
||||
stName = @"notDetermined";
|
||||
break;
|
||||
case AVAuthorizationStatusRestricted:
|
||||
stName = @"restricted";
|
||||
break;
|
||||
case AVAuthorizationStatusDenied:
|
||||
stName = @"denied";
|
||||
break;
|
||||
case AVAuthorizationStatusAuthorized:
|
||||
stName = @"authorized";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
NSLog(@"[QRCodeReader] camera auth status=%@ (%ld)", stName, (long)st);
|
||||
}
|
||||
|
||||
static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
{
|
||||
UIApplication *app = [UIApplication sharedApplication];
|
||||
NSMutableArray<NSString *> *trace = [NSMutableArray array];
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
NSInteger sceneCount = app.connectedScenes.count;
|
||||
[trace addObject:[NSString stringWithFormat:@"connectedScenes=%ld", (long)sceneCount]];
|
||||
for (UIScene *scene in app.connectedScenes) {
|
||||
if (scene.activationState != UISceneActivationStateForegroundActive) {
|
||||
continue;
|
||||
@@ -57,17 +21,13 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
continue;
|
||||
}
|
||||
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
||||
NSInteger winN = windowScene.windows.count;
|
||||
[trace addObject:[NSString stringWithFormat:@"foreground UIWindowScene windows=%ld", (long)winN]];
|
||||
for (UIWindow *window in windowScene.windows) {
|
||||
if (window.isKeyWindow) {
|
||||
NSLog(@"[QRCodeReader] keyWindow pick: scene keyWindow=%@ bounds=%@", window, NSStringFromCGRect(window.bounds));
|
||||
return window;
|
||||
}
|
||||
}
|
||||
for (UIWindow *window in windowScene.windows) {
|
||||
if (!window.isHidden) {
|
||||
NSLog(@"[QRCodeReader] keyWindow pick: scene nonHidden=%@ bounds=%@", window, NSStringFromCGRect(window.bounds));
|
||||
return window;
|
||||
}
|
||||
}
|
||||
@@ -75,25 +35,14 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
}
|
||||
|
||||
if (app.keyWindow) {
|
||||
[trace addObject:@"app.keyWindow"];
|
||||
NSLog(@"[QRCodeReader] keyWindow pick: application.keyWindow=%@ bounds=%@",
|
||||
app.keyWindow, NSStringFromCGRect(app.keyWindow.bounds));
|
||||
return app.keyWindow;
|
||||
}
|
||||
for (UIWindow *window in app.windows) {
|
||||
if (window.isKeyWindow) {
|
||||
NSLog(@"[QRCodeReader] keyWindow pick: windows scan key=%@ bounds=%@", window, NSStringFromCGRect(window.bounds));
|
||||
return window;
|
||||
}
|
||||
}
|
||||
UIWindow *first = app.windows.firstObject;
|
||||
if (first) {
|
||||
NSLog(@"[QRCodeReader] keyWindow pick: firstObject=%@ bounds=%@ trace=[%@]",
|
||||
first, NSStringFromCGRect(first.bounds), [trace componentsJoinedByString:@", "]);
|
||||
return first;
|
||||
}
|
||||
NSLog(@"[QRCodeReader] keyWindow pick: NONE trace=[%@]", [trace componentsJoinedByString:@", "]);
|
||||
return nil;
|
||||
return app.windows.firstObject;
|
||||
}
|
||||
|
||||
@interface QRCodeReaderImpl : UIViewController
|
||||
@@ -135,7 +84,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
if ([input isKindOfClass:[AVCaptureDeviceInput class]]) {
|
||||
AVCaptureDevice *d = ((AVCaptureDeviceInput *)input).device;
|
||||
if (d) {
|
||||
NSLog(@"[QRCodeReader] resolvedCaptureDevice from session input device=%p", d);
|
||||
return d;
|
||||
}
|
||||
}
|
||||
@@ -155,13 +103,11 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
return;
|
||||
}
|
||||
if (![device hasTorch]) {
|
||||
NSLog(@"[QRCodeReader] torch: device %p has no torch", device);
|
||||
return;
|
||||
}
|
||||
|
||||
AVCaptureSession *session = self.captureSession;
|
||||
if (on && session && ![session isRunning]) {
|
||||
NSLog(@"[QRCodeReader] torch: session not running yet; retry in 0.25s (session=%p)", session);
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
if (on) {
|
||||
[self applyTorchOnMainThread:YES];
|
||||
@@ -183,8 +129,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
if ([device isTorchModeSupported:AVCaptureTorchModeOn]) {
|
||||
device.torchMode = AVCaptureTorchModeOn;
|
||||
}
|
||||
} else {
|
||||
NSLog(@"[QRCodeReader] torch ON ok level=maxAvailable");
|
||||
}
|
||||
} else {
|
||||
device.torchMode = AVCaptureTorchModeOff;
|
||||
@@ -193,7 +137,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
}
|
||||
|
||||
- (void)applyTorch:(BOOL)on {
|
||||
NSLog(@"[QRCodeReader] applyTorch requested on=%d thread=%@", (int)on, amneziaQrThreadTag());
|
||||
if ([NSThread isMainThread]) {
|
||||
[self applyTorchOnMainThread:on];
|
||||
} else {
|
||||
@@ -204,9 +147,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
}
|
||||
|
||||
- (BOOL)startReadingOnMainThread {
|
||||
NSLog(@"[QRCodeReader] startReadingOnMainThread begin thread=%@", amneziaQrThreadTag());
|
||||
amneziaQrLogDeviceAuth();
|
||||
|
||||
[self stopReadingOnMainThread];
|
||||
|
||||
NSError *error = nil;
|
||||
@@ -216,7 +156,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
NSLog(@"[QRCodeReader] defaultDeviceWithMediaType:Video is nil");
|
||||
return NO;
|
||||
}
|
||||
NSLog(@"[QRCodeReader] capture device=%p localizedName=%@", captureDevice, captureDevice.localizedName);
|
||||
|
||||
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
|
||||
|
||||
@@ -226,7 +165,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
}
|
||||
|
||||
self.activeCaptureDevice = captureDevice;
|
||||
NSLog(@"[QRCodeReader] activeCaptureDevice set to %p", self.activeCaptureDevice);
|
||||
|
||||
AVCaptureSession *session = [[AVCaptureSession alloc] init];
|
||||
[session addInput:deviceInput];
|
||||
@@ -260,26 +198,16 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
self.videoPreviewPlayer.zPosition = -1000.f;
|
||||
[keyWindow.layer insertSublayer:self.videoPreviewPlayer atIndex:0];
|
||||
amneziaIosPairingRelayoutChromeIfNeeded();
|
||||
NSLog(@"[QRCodeReader] previewLayer inserted window=%@ layer.sublayers.count=%lu bounds=%@",
|
||||
keyWindow, (unsigned long)keyWindow.layer.sublayers.count, NSStringFromCGRect(bounds));
|
||||
|
||||
AVCaptureSession *runningSession = self.captureSession;
|
||||
dispatch_async(_sessionQueue, ^{
|
||||
NSLog(@"[QRCodeReader] session startRunning on session queue session=%p", runningSession);
|
||||
[runningSession startRunning];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSLog(@"[QRCodeReader] after startRunning isRunning=%d", (int)runningSession.isRunning);
|
||||
});
|
||||
});
|
||||
|
||||
NSLog(@"[QRCodeReader] startReading OK activeDevice=%p window bounds=%@",
|
||||
self.activeCaptureDevice, NSStringFromCGRect(bounds));
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)startReading {
|
||||
NSLog(@"[QRCodeReader] startReading entry thread=%@ qt=%p", amneziaQrThreadTag(), (void *)QThread::currentThread());
|
||||
if ([NSThread isMainThread]) {
|
||||
return [self startReadingOnMainThread];
|
||||
}
|
||||
@@ -287,12 +215,10 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
ok = [self startReadingOnMainThread];
|
||||
});
|
||||
NSLog(@"[QRCodeReader] startReading exit ok=%d (dispatched to main)", (int)ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
- (void)stopReadingOnMainThread {
|
||||
NSLog(@"[QRCodeReader] stopReadingOnMainThread thread=%@", amneziaQrThreadTag());
|
||||
[self applyTorchOnMainThread:NO];
|
||||
self.activeCaptureDevice = nil;
|
||||
|
||||
@@ -311,7 +237,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
dispatch_sync(_sessionQueue, ^{
|
||||
@try {
|
||||
if ([session isRunning]) {
|
||||
NSLog(@"[QRCodeReader] session stopRunning (sync) session=%p", session);
|
||||
[session stopRunning];
|
||||
}
|
||||
} @catch (NSException *ex) {
|
||||
@@ -321,14 +246,12 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
}
|
||||
|
||||
if (self.videoPreviewPlayer) {
|
||||
NSLog(@"[QRCodeReader] remove preview from superlayer");
|
||||
[self.videoPreviewPlayer removeFromSuperlayer];
|
||||
self.videoPreviewPlayer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopReading {
|
||||
NSLog(@"[QRCodeReader] stopReading entry thread=%@ qt=%p", amneziaQrThreadTag(), (void *)QThread::currentThread());
|
||||
if ([NSThread isMainThread]) {
|
||||
[self stopReadingOnMainThread];
|
||||
} else {
|
||||
@@ -336,7 +259,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
[self stopReadingOnMainThread];
|
||||
});
|
||||
}
|
||||
NSLog(@"[QRCodeReader] stopReading exit");
|
||||
}
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)output
|
||||
@@ -351,7 +273,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
|
||||
if (value.length == 0) {
|
||||
return;
|
||||
}
|
||||
NSLog(@"[QRCodeReader] metadata QR len=%lu", static_cast<unsigned long>(value.length));
|
||||
QRCodeReader *cpp = _qrCodeReader;
|
||||
const QByteArray utf8([value UTF8String]);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@@ -374,21 +295,13 @@ QRect QRCodeReader::cameraSize() {
|
||||
|
||||
void QRCodeReader::setCameraSize(QRect value) {
|
||||
m_cameraSize = value;
|
||||
qInfo() << "[QRCodeReader] setCameraSize" << value;
|
||||
}
|
||||
|
||||
void QRCodeReader::startReading() {
|
||||
qInfo() << "[QRCodeReader] C++ startReading thread" << QThread::currentThread();
|
||||
const BOOL ok = [m_qrCodeReader startReading];
|
||||
if (!ok) {
|
||||
qWarning() << "[QRCodeReader] C++ startReading failed (see NSLogs)";
|
||||
} else {
|
||||
qInfo() << "[QRCodeReader] C++ startReading ok";
|
||||
}
|
||||
[m_qrCodeReader startReading];
|
||||
}
|
||||
|
||||
void QRCodeReader::stopReading() {
|
||||
qInfo() << "[QRCodeReader] C++ stopReading thread" << QThread::currentThread();
|
||||
[m_qrCodeReader stopReading];
|
||||
}
|
||||
|
||||
@@ -397,7 +310,6 @@ void QRCodeReader::notifyCodeRead(const QString &code) {
|
||||
}
|
||||
|
||||
void QRCodeReader::setTorchEnabled(bool on) {
|
||||
qInfo() << "[QRCodeReader] C++ setTorchEnabled" << on << "thread" << QThread::currentThread();
|
||||
[(QRCodeReaderImpl *)m_qrCodeReader applyTorch:on ? YES : NO];
|
||||
}
|
||||
#else
|
||||
|
||||
@@ -130,11 +130,9 @@ static void amneziaLayoutPairingSafeAreaDimStrips(void)
|
||||
CGFloat bottomY = hb.size.height - insets.bottom;
|
||||
CGFloat bottomH = insets.bottom;
|
||||
UIView *qt = s_pairingDimQtRoot;
|
||||
CGFloat qMaxYForLog = hb.size.height;
|
||||
if (qt && qt.superview) {
|
||||
CGRect qInHost = [host convertRect:qt.bounds fromView:qt];
|
||||
const CGFloat qMaxY = CGRectGetMaxY(qInHost);
|
||||
qMaxYForLog = qMaxY;
|
||||
if (qMaxY < hb.size.height - 0.5f) {
|
||||
bottomY = qMaxY;
|
||||
bottomH = hb.size.height - qMaxY;
|
||||
@@ -168,28 +166,6 @@ static void amneziaLayoutPairingSafeAreaDimStrips(void)
|
||||
s_pairingSafeBottomDim.frame = CGRectMake(0, bottomY, hb.size.width, bottomH);
|
||||
|
||||
amneziaSyncPairingWindowBottomMaskLayer(w, host, bottomY, bottomH, hb.size.width);
|
||||
|
||||
CGRect winStrip = [w convertRect:s_pairingSafeBottomDim.frame fromView:host];
|
||||
CALayer *previewLy = amneziaFindVideoPreviewLayerInWindow(w);
|
||||
CGRect previewWin = CGRectZero;
|
||||
if (previewLy) {
|
||||
previewWin = [w.layer convertRect:previewLy.bounds fromLayer:previewLy];
|
||||
}
|
||||
CGRect maskWin = s_pairingWindowBottomMaskLayer ? s_pairingWindowBottomMaskLayer.frame : CGRectZero;
|
||||
CGRect qtWin = CGRectZero;
|
||||
if (qt) {
|
||||
qtWin = [w convertRect:qt.bounds fromView:qt];
|
||||
}
|
||||
|
||||
NSLog(@"[PairingCamera] safeAreaDim bottom strip y=%.1f h=%.1f qtMaxY=%.1f hostH=%.1f safeBottom=%.1f extraPt=%d winMask=%d",
|
||||
bottomY, bottomH, qMaxYForLog, hb.size.height, insets.bottom, s_pairingNativeBottomExtraPt,
|
||||
(int)(s_pairingWindowBottomMaskLayer.superlayer != nil));
|
||||
NSLog(@"[PairingCamera] geom winStrip={{%.1f,%.1f},{%.1f,%.1f}} qtWin={{%.1f,%.1f},{%.1f,%.1f}} previewWin={{%.1f,%.1f},{%.1f,%.1f}} "
|
||||
@"maskWin={{%.1f,%.1f},{%.1f,%.1f}} dyStripTopMinusPreviewTop=%.1f dyStripTopMinusQtMaxY=%.1f",
|
||||
winStrip.origin.x, winStrip.origin.y, winStrip.size.width, winStrip.size.height, qtWin.origin.x, qtWin.origin.y,
|
||||
qtWin.size.width, qtWin.size.height, previewWin.origin.x, previewWin.origin.y, previewWin.size.width,
|
||||
previewWin.size.height, maskWin.origin.x, maskWin.origin.y, maskWin.size.width, maskWin.size.height,
|
||||
CGRectGetMinY(winStrip) - CGRectGetMinY(previewWin), CGRectGetMinY(winStrip) - CGRectGetMaxY(qtWin));
|
||||
}
|
||||
|
||||
static void amneziaInstallPairingSafeAreaDimStrips(UIWindow *window, UIView *qtRootView)
|
||||
@@ -227,10 +203,6 @@ static void amneziaInstallPairingSafeAreaDimStrips(UIWindow *window, UIView *qtR
|
||||
[host insertSubview:s_pairingSafeTopDim belowSubview:qtRootView];
|
||||
[host insertSubview:s_pairingSafeBottomDim belowSubview:qtRootView];
|
||||
|
||||
NSLog(@"[PairingCamera] safeAreaDim host=%@ top=%.1f bottomInset=%.1f qtFrame=%@ hostBounds=%@ window=%@",
|
||||
NSStringFromClass(host.class), insets.top, insets.bottom, NSStringFromCGRect(qtRootView.frame), NSStringFromCGRect(hb),
|
||||
NSStringFromCGRect(window.bounds));
|
||||
|
||||
s_pairingSafeDimOrientationToken = [[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIDeviceOrientationDidChangeNotification
|
||||
object:nil
|
||||
@@ -277,7 +249,6 @@ static void amneziaForceMetalViewsTransparent(UIView *view, NSUInteger depth, NS
|
||||
if (view.layer) {
|
||||
view.layer.backgroundColor = [UIColor clearColor].CGColor;
|
||||
}
|
||||
NSLog(@"[PairingCamera] forceMetalTransparent depth=%lu class=%@", (unsigned long)depth, cn);
|
||||
}
|
||||
if (depth < maxDepth) {
|
||||
for (UIView *child in view.subviews) {
|
||||
@@ -358,7 +329,6 @@ void amneziaIosApplyEmbeddedCameraUnderlayToQtView(bool enable)
|
||||
void (^work)(void) = ^{
|
||||
UIViewController *vc = amneziaKeyWindowViewController();
|
||||
if (!vc || !vc.view) {
|
||||
NSLog(@"[PairingCamera] amneziaIosApplyEmbeddedCameraUnderlayToQtView: no root VC (enable=%d)", (int)enable);
|
||||
return;
|
||||
}
|
||||
UIView *root = vc.view;
|
||||
@@ -378,9 +348,6 @@ void amneziaIosApplyEmbeddedCameraUnderlayToQtView(bool enable)
|
||||
amneziaInstallPairingSafeAreaDimStrips(win, root);
|
||||
amneziaLayoutPairingSafeAreaDimStrips();
|
||||
}
|
||||
NSLog(@"[PairingCamera] Qt view underlay transparency %@ subviews=%lu",
|
||||
enable ? @"ON" : @"OFF", (unsigned long)root.subviews.count);
|
||||
/** QUIMetalView is often updated after QSG resize; repeat walk next runloop so camera shows below status bar. */
|
||||
if (enable) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIViewController *vc2 = amneziaKeyWindowViewController();
|
||||
@@ -388,7 +355,6 @@ void amneziaIosApplyEmbeddedCameraUnderlayToQtView(bool enable)
|
||||
return;
|
||||
}
|
||||
amneziaApplyPairingUnderlayWalk(vc2.view, YES);
|
||||
NSLog(@"[PairingCamera] underlay repeat pass (post-runloop)");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,56 +17,6 @@ static bool gTorchRequested = false;
|
||||
/** Last time overlay became key; used to ignore restartCapture during warm-up (see QML kick timer removal). */
|
||||
static CFAbsoluteTime gPairingQrOverlayKeySince = -1.0;
|
||||
|
||||
static void amneziaPairingQrLogScenes(NSString *tag)
|
||||
{
|
||||
NSUInteger n = 0;
|
||||
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
|
||||
NSString *cls = NSStringFromClass(scene.class);
|
||||
NSString *state = @"?";
|
||||
switch (scene.activationState) {
|
||||
case UISceneActivationStateUnattached:
|
||||
state = @"unattached";
|
||||
break;
|
||||
case UISceneActivationStateForegroundActive:
|
||||
state = @"foregroundActive";
|
||||
break;
|
||||
case UISceneActivationStateForegroundInactive:
|
||||
state = @"foregroundInactive";
|
||||
break;
|
||||
case UISceneActivationStateBackground:
|
||||
state = @"background";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
CGRect bounds = CGRectZero;
|
||||
if ([scene isKindOfClass:[UIWindowScene class]]) {
|
||||
bounds = ((UIWindowScene *)scene).coordinateSpace.bounds;
|
||||
}
|
||||
NSLog(@"[PairingQrOverlay] %@ scene[%lu] class=%@ state=%@ bounds=%@", tag, (unsigned long)n, cls, state,
|
||||
NSStringFromCGRect(bounds));
|
||||
n++;
|
||||
}
|
||||
if (n == 0) {
|
||||
NSLog(@"[PairingQrOverlay] %@ connectedScenes count=0", tag);
|
||||
}
|
||||
}
|
||||
|
||||
static void amneziaPairingQrLogWindows(NSString *tag)
|
||||
{
|
||||
NSUInteger i = 0;
|
||||
for (UIWindow *cw in UIApplication.sharedApplication.windows) {
|
||||
NSLog(@"[PairingQrOverlay] %@ UIWindow[%lu] ptr=%p level=%.1f hidden=%d key=%d bounds=%@ rootVC=%@", tag,
|
||||
(unsigned long)i, (void *)cw, cw.windowLevel, (int)cw.hidden, (int)cw.isKeyWindow,
|
||||
NSStringFromCGRect(cw.bounds),
|
||||
cw.rootViewController ? NSStringFromClass(cw.rootViewController.class) : @"(nil)");
|
||||
i++;
|
||||
}
|
||||
if (i == 0) {
|
||||
NSLog(@"[PairingQrOverlay] %@ UIApplication.windows count=0", tag);
|
||||
}
|
||||
}
|
||||
|
||||
static UIWindowScene *amneziaForegroundWindowScene(void)
|
||||
{
|
||||
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
|
||||
@@ -113,11 +63,9 @@ static CGFloat amneziaPairingQrBottomTabStripReserve(UIWindowScene *scene)
|
||||
if ([cw.rootViewController isKindOfClass:qios]) {
|
||||
const CGFloat inset = cw.safeAreaInsets.bottom;
|
||||
const CGFloat reserve = inset + 49.f;
|
||||
NSLog(@"[PairingQrOverlay] bottomReserve: QUIWindow safeBottom=%.1f -> reserve=%.1f", inset, reserve);
|
||||
return MIN(MAX(reserve, 72.f), 140.f);
|
||||
}
|
||||
}
|
||||
NSLog(@"[PairingQrOverlay] bottomReserve: QUIWindow not found, default 83");
|
||||
return 83.f;
|
||||
}
|
||||
|
||||
@@ -548,7 +496,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
|
||||
|
||||
- (void)backTapped
|
||||
{
|
||||
NSLog(@"[PairingQrOverlay] native back tapped");
|
||||
if (gOnBack) {
|
||||
gOnBack();
|
||||
}
|
||||
@@ -557,7 +504,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
|
||||
- (void)torchTapped
|
||||
{
|
||||
gTorchRequested = !gTorchRequested;
|
||||
NSLog(@"[PairingQrOverlay] native torch toggle -> %d", (int)gTorchRequested);
|
||||
[self applyTorchFromGlobalFlag];
|
||||
if (gTorchRequested) {
|
||||
self.torchButton.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.42];
|
||||
@@ -597,8 +543,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
|
||||
{
|
||||
AVCaptureDevice *device = self.videoDevice;
|
||||
if (!device || ![device hasTorch]) {
|
||||
NSLog(@"[PairingQrOverlay] applyTorch skipped on=%d device=%p hasTorch=%d", (int)on, (void *)device,
|
||||
device ? (int)[device hasTorch] : -1);
|
||||
if (on && gTorchRequested) {
|
||||
__unsafe_unretained AmneziaPairingQrOverlayViewController *unsafeSelf = self;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.12 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
@@ -676,9 +620,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
|
||||
|
||||
- (BOOL)startCapturePipelineOnMainThread
|
||||
{
|
||||
NSLog(@"[PairingQrOverlay] startCapturePipelineOnMainThread entry camera.bounds=%@ thread=%@",
|
||||
self.cameraContainer ? NSStringFromCGRect(self.cameraContainer.bounds) : @"(nil)",
|
||||
[NSThread isMainThread] ? @"main" : @"bg");
|
||||
|
||||
[self stopCapturePipelineOnMainThread];
|
||||
|
||||
@@ -693,8 +634,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
|
||||
NSLog(@"[PairingQrOverlay] no default video device");
|
||||
return NO;
|
||||
}
|
||||
NSLog(@"[PairingQrOverlay] capture device=%p modelID=%@ localizedName=%@", (void *)device, device.modelID,
|
||||
device.localizedName);
|
||||
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
|
||||
if (!input) {
|
||||
NSLog(@"[PairingQrOverlay] deviceInput failed: %@", error.localizedDescription);
|
||||
@@ -706,7 +645,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
|
||||
if ([session canSetSessionPreset:AVCaptureSessionPresetHigh]) {
|
||||
session.sessionPreset = AVCaptureSessionPresetHigh;
|
||||
}
|
||||
NSLog(@"[PairingQrOverlay] session preset=%@", session.sessionPreset);
|
||||
|
||||
[session addInput:input];
|
||||
|
||||
@@ -732,7 +670,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
|
||||
self.previewLayer = preview;
|
||||
[self.cameraContainer.layer insertSublayer:preview atIndex:0];
|
||||
preview.frame = self.cameraContainer.bounds;
|
||||
NSLog(@"[PairingQrOverlay] previewLayer on host frame=%@", NSStringFromCGRect(preview.frame));
|
||||
|
||||
[self.view layoutIfNeeded];
|
||||
[self layoutScanOverlayGeometry];
|
||||
@@ -740,22 +677,18 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
|
||||
AVCaptureSession *runningSession = session;
|
||||
__unsafe_unretained AmneziaPairingQrOverlayViewController *weakSelf = self;
|
||||
dispatch_async(q, ^{
|
||||
NSLog(@"[PairingQrOverlay] session queue: startRunning begin session=%p", (void *)runningSession);
|
||||
@try {
|
||||
[runningSession startRunning];
|
||||
} @catch (NSException *ex) {
|
||||
NSLog(@"[PairingQrOverlay] startRunning exception: %@", ex);
|
||||
}
|
||||
const BOOL running = [runningSession isRunning];
|
||||
NSLog(@"[PairingQrOverlay] session queue: startRunning end isRunning=%d", (int)running);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
AmneziaPairingQrOverlayViewController *strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
AVCaptureVideoPreviewLayer *pl = strongSelf.previewLayer;
|
||||
NSLog(@"[PairingQrOverlay] post-start main: sessionRunning=%d previewFrame=%@ hostBounds=%@", (int)runningSession.isRunning,
|
||||
pl ? NSStringFromCGRect(pl.frame) : @"(no preview)", NSStringFromCGRect(strongSelf.cameraContainer.bounds));
|
||||
[strongSelf applyTorchFromGlobalFlag];
|
||||
});
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.35 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
@@ -764,9 +697,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
|
||||
return;
|
||||
}
|
||||
AVCaptureVideoPreviewLayer *pl = strongSelf.previewLayer;
|
||||
NSLog(@"[PairingQrOverlay] probe+350ms: sessionRunning=%d previewFrame=%@", (int)runningSession.isRunning,
|
||||
pl ? NSStringFromCGRect(pl.frame) : @"(nil)");
|
||||
amneziaPairingQrLogWindows(@"probe+350ms");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -791,10 +721,8 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (gOnScanned) {
|
||||
NSLog(@"[PairingQrOverlay] metadata QR len=%lu", (unsigned long)copy.size());
|
||||
gOnScanned(copy.c_str());
|
||||
} else {
|
||||
NSLog(@"[PairingQrOverlay] metadata QR but gOnScanned is nil");
|
||||
}
|
||||
});
|
||||
break;
|
||||
@@ -805,7 +733,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
|
||||
|
||||
static void amneziaPairingQrOverlayTeardownOnMain(void)
|
||||
{
|
||||
NSLog(@"[PairingQrOverlay] teardownOnMain overlayPtr=%p", (void *)gPairingQrOverlayWindow);
|
||||
UIWindow *w = gPairingQrOverlayWindow;
|
||||
gPairingQrOverlayWindow = nil;
|
||||
gOnScanned = nullptr;
|
||||
@@ -824,12 +751,9 @@ static void amneziaPairingQrOverlayTeardownOnMain(void)
|
||||
|
||||
UIWindow *restore = amneziaPickQtAppWindowToRestore();
|
||||
if (restore) {
|
||||
NSLog(@"[PairingQrOverlay] teardown: restore keyWindow to %@", restore);
|
||||
[restore makeKeyWindow];
|
||||
} else {
|
||||
NSLog(@"[PairingQrOverlay] teardown: no window to restore as key");
|
||||
}
|
||||
amneziaPairingQrLogWindows(@"afterTeardown");
|
||||
}
|
||||
|
||||
void amneziaIosPairingQrOverlayPresent(AmneziaPairingQrScannedUtf8Handler onScanned, AmneziaPairingQrOverlayBackHandler onBack,
|
||||
@@ -837,26 +761,20 @@ void amneziaIosPairingQrOverlayPresent(AmneziaPairingQrScannedUtf8Handler onScan
|
||||
{
|
||||
const bool hasScan = static_cast<bool>(onScanned);
|
||||
const bool hasBack = static_cast<bool>(onBack);
|
||||
NSLog(@"[PairingQrOverlay] C++ present requested hasScan=%d hasBack=%d callerThread=%p", (int)hasScan, (int)hasBack,
|
||||
(void *)NSThread.currentThread);
|
||||
AmneziaPairingQrScannedUtf8Handler scanH = std::move(onScanned);
|
||||
AmneziaPairingQrOverlayBackHandler backH = std::move(onBack);
|
||||
const std::string titleCopy = titleUtf8;
|
||||
const std::string subCopy = subtitleUtf8;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSLog(@"[PairingQrOverlay] present block on main");
|
||||
amneziaPairingQrLogScenes(@"beforeTeardown");
|
||||
amneziaPairingQrLogWindows(@"beforeTeardown");
|
||||
|
||||
amneziaPairingQrOverlayTeardownOnMain();
|
||||
gOnScanned = std::move(scanH);
|
||||
gOnBack = std::move(backH);
|
||||
NSLog(@"[PairingQrOverlay] callbacks stored scan=%d back=%d", (int)(bool)gOnScanned, (int)(bool)gOnBack);
|
||||
|
||||
UIWindowScene *scene = amneziaForegroundWindowScene();
|
||||
if (!scene) {
|
||||
NSLog(@"[PairingQrOverlay] present: no UIWindowScene");
|
||||
amneziaPairingQrLogScenes(@"sceneNil");
|
||||
gOnScanned = nullptr;
|
||||
gOnBack = nullptr;
|
||||
return;
|
||||
@@ -878,28 +796,22 @@ void amneziaIosPairingQrOverlayPresent(AmneziaPairingQrScannedUtf8Handler onScan
|
||||
w.backgroundColor = [UIColor blackColor];
|
||||
w.rootViewController = vc;
|
||||
gPairingQrOverlayWindow = w;
|
||||
NSLog(@"[PairingQrOverlay] created UIWindow ptr=%p frame=%@ (sceneH=%.0f bottomReserve=%.0f) level=%.1f", (void *)w,
|
||||
NSStringFromCGRect(w.frame), sceneBounds.size.height, bottomReserve, w.windowLevel);
|
||||
|
||||
[w makeKeyAndVisible];
|
||||
[w layoutIfNeeded];
|
||||
[vc.view setNeedsLayout];
|
||||
[vc.view layoutIfNeeded];
|
||||
NSLog(@"[PairingQrOverlay] after layout vc.view.bounds=%@ window.key=%d", NSStringFromCGRect(vc.view.bounds),
|
||||
(int)w.isKeyWindow);
|
||||
|
||||
gPairingQrOverlayKeySince = CFAbsoluteTimeGetCurrent();
|
||||
|
||||
if (![vc startCapturePipelineOnMainThread]) {
|
||||
NSLog(@"[PairingQrOverlay] startCapture failed");
|
||||
}
|
||||
amneziaPairingQrLogWindows(@"afterPresent");
|
||||
});
|
||||
}
|
||||
|
||||
void amneziaIosPairingQrOverlayDismiss()
|
||||
{
|
||||
NSLog(@"[PairingQrOverlay] C++ dismiss requested");
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
amneziaPairingQrOverlayTeardownOnMain();
|
||||
});
|
||||
@@ -907,12 +819,10 @@ void amneziaIosPairingQrOverlayDismiss()
|
||||
|
||||
void amneziaIosPairingQrOverlaySetTorchEnabled(bool on)
|
||||
{
|
||||
NSLog(@"[PairingQrOverlay] C++ setTorch=%d", (int)on);
|
||||
gTorchRequested = on;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIWindow *win = gPairingQrOverlayWindow;
|
||||
if (!win) {
|
||||
NSLog(@"[PairingQrOverlay] setTorch: no overlay window");
|
||||
return;
|
||||
}
|
||||
UIViewController *root = win.rootViewController;
|
||||
@@ -935,25 +845,20 @@ void amneziaIosPairingQrOverlaySetTorchEnabled(bool on)
|
||||
|
||||
void amneziaIosPairingQrOverlayRestartCapture()
|
||||
{
|
||||
NSLog(@"[PairingQrOverlay] C++ restartCapture requested");
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
const CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
|
||||
if (gPairingQrOverlayKeySince > 0 && (now - gPairingQrOverlayKeySince) < 1.0) {
|
||||
NSLog(@"[PairingQrOverlay] restartCapture: skipped warm-up (%.3fs since overlay key)", now - gPairingQrOverlayKeySince);
|
||||
return;
|
||||
}
|
||||
UIWindow *w = gPairingQrOverlayWindow;
|
||||
if (!w) {
|
||||
NSLog(@"[PairingQrOverlay] restartCapture: no overlay window");
|
||||
return;
|
||||
}
|
||||
UIViewController *root = w.rootViewController;
|
||||
if (![root isKindOfClass:[AmneziaPairingQrOverlayViewController class]]) {
|
||||
NSLog(@"[PairingQrOverlay] restartCapture: rootVC class=%@", root ? NSStringFromClass(root.class) : @"(nil)");
|
||||
return;
|
||||
}
|
||||
AmneziaPairingQrOverlayViewController *vc = (AmneziaPairingQrOverlayViewController *)root;
|
||||
NSLog(@"[PairingQrOverlay] restartCapture: stop+start");
|
||||
[vc stopCapturePipelineOnMainThread];
|
||||
if (![vc startCapturePipelineOnMainThread]) {
|
||||
NSLog(@"[PairingQrOverlay] restart startCapture failed");
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDataStream>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QIODevice>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
@@ -241,7 +240,6 @@ void PairingUiController::clearPendingPhonePairingUuid()
|
||||
void PairingUiController::openPairingQrScanner()
|
||||
{
|
||||
#if defined(Q_OS_ANDROID)
|
||||
qInfo() << "[PairingUi] openPairingQrScanner (Android native activity)";
|
||||
AndroidController::instance()->startPairingQrReaderActivity();
|
||||
#endif
|
||||
}
|
||||
@@ -251,9 +249,7 @@ bool PairingUiController::isPairingCameraAccessGranted() const
|
||||
#if defined(Q_OS_ANDROID)
|
||||
return AndroidController::instance()->isCameraPermissionGranted();
|
||||
#elif defined(Q_OS_IOS)
|
||||
const bool ok = amneziaIosPairingCameraAccessGranted();
|
||||
qInfo() << "[PairingUi] iOS isPairingCameraAccessGranted =" << ok;
|
||||
return ok;
|
||||
return amneziaIosPairingCameraAccessGranted();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
@@ -265,7 +261,6 @@ void PairingUiController::requestPairingCameraAccess()
|
||||
AndroidController::instance()->requestCameraPermissionForQrPairing();
|
||||
#elif defined(Q_OS_IOS)
|
||||
amneziaIosRequestPairingCameraAccess([this](bool granted) {
|
||||
qInfo() << "[PairingUi] iOS requestPairingCameraAccess callback granted =" << granted;
|
||||
QMetaObject::invokeMethod(
|
||||
this, [this, granted]() { emit pairingCameraAccessFinished(granted); }, Qt::QueuedConnection);
|
||||
});
|
||||
@@ -283,52 +278,10 @@ void PairingUiController::openPairingCameraAppSettings()
|
||||
#endif
|
||||
}
|
||||
|
||||
void PairingUiController::setEmbeddedPairingQrCameraActive(bool active)
|
||||
{
|
||||
if (m_embeddedPairingQrCameraActive == active) {
|
||||
return;
|
||||
}
|
||||
m_embeddedPairingQrCameraActive = active;
|
||||
#if defined(Q_OS_IOS)
|
||||
qInfo() << "[PairingUi] iOS embeddedPairingQrCameraActive ->" << active;
|
||||
amneziaIosApplyEmbeddedCameraUnderlayToQtView(active);
|
||||
#endif
|
||||
#if defined(Q_OS_ANDROID)
|
||||
if (active) {
|
||||
AndroidController::instance()->startPairingQrEmbeddedCamera();
|
||||
} else {
|
||||
AndroidController::instance()->stopPairingQrEmbeddedCamera();
|
||||
}
|
||||
#endif
|
||||
emit embeddedPairingQrCameraActiveChanged();
|
||||
}
|
||||
|
||||
void PairingUiController::syncIosEmbeddedPairingQrNativeBottomExtra(int extraPt)
|
||||
{
|
||||
#if defined(Q_OS_IOS)
|
||||
amneziaIosSetPairingEmbeddedCameraNativeBottomExtraPt(extraPt);
|
||||
#else
|
||||
Q_UNUSED(extraPt);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PairingUiController::refreshIosEmbeddedPairingQrChrome()
|
||||
{
|
||||
#if defined(Q_OS_IOS)
|
||||
if (!m_embeddedPairingQrCameraActive) {
|
||||
return;
|
||||
}
|
||||
qInfo() << "[PairingUi] refreshIosEmbeddedPairingQrChrome (reapply UIView underlay)";
|
||||
amneziaIosApplyEmbeddedCameraUnderlayToQtView(true);
|
||||
#else
|
||||
// no-op on non-iOS
|
||||
#endif
|
||||
}
|
||||
|
||||
void PairingUiController::setPairingQrTorchEnabled(bool enabled)
|
||||
{
|
||||
#if defined(Q_OS_ANDROID)
|
||||
AndroidController::instance()->setPairingQrEmbeddedTorch(enabled);
|
||||
Q_UNUSED(enabled);
|
||||
#elif defined(Q_OS_IOS)
|
||||
amneziaIosPairingQrOverlaySetTorchEnabled(enabled);
|
||||
#else
|
||||
@@ -339,7 +292,6 @@ void PairingUiController::setPairingQrTorchEnabled(bool enabled)
|
||||
void PairingUiController::presentIosPairingQrNativeOverlayScanner(const QString &title, const QString &subtitle)
|
||||
{
|
||||
#if defined(Q_OS_IOS)
|
||||
qInfo() << "[PairingUi] presentIosPairingQrNativeOverlayScanner: scheduling native UIWindow overlay";
|
||||
const std::string titleUtf8 = title.isEmpty() ? std::string() : title.toStdString();
|
||||
const std::string subtitleUtf8 = subtitle.isEmpty() ? std::string() : subtitle.toStdString();
|
||||
amneziaIosPairingQrOverlayPresent(
|
||||
@@ -364,14 +316,12 @@ void PairingUiController::presentIosPairingQrNativeOverlayScanner(const QString
|
||||
#else
|
||||
Q_UNUSED(title);
|
||||
Q_UNUSED(subtitle);
|
||||
qInfo() << "[PairingUi] presentIosPairingQrNativeOverlayScanner: no-op (not iOS build)";
|
||||
#endif
|
||||
}
|
||||
|
||||
void PairingUiController::dismissIosPairingQrNativeOverlayScanner()
|
||||
{
|
||||
#if defined(Q_OS_IOS)
|
||||
qInfo() << "[PairingUi] dismissIosPairingQrNativeOverlayScanner";
|
||||
amneziaIosPairingQrOverlayDismiss();
|
||||
#endif
|
||||
}
|
||||
@@ -379,25 +329,17 @@ void PairingUiController::dismissIosPairingQrNativeOverlayScanner()
|
||||
void PairingUiController::restartIosPairingQrNativeOverlayCapture()
|
||||
{
|
||||
#if defined(Q_OS_IOS)
|
||||
qInfo() << "[PairingUi] restartIosPairingQrNativeOverlayCapture";
|
||||
amneziaIosPairingQrOverlayRestartCapture();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool PairingUiController::applyScannedTextAsPairingUuid(const QString &raw)
|
||||
{
|
||||
const QString t = raw.trimmed();
|
||||
qInfo() << "[PairingUi] scan raw len=" << t.size();
|
||||
const QString uuid = extractPairingSessionUuidFromScanText(raw);
|
||||
if (uuid.isEmpty()) {
|
||||
if (t.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
|
||||
qInfo() << "[PairingUi] scan rejected: looks like vpn:// bundle, not session UUID";
|
||||
} else {
|
||||
qInfo() << "[PairingUi] scan rejected: no session UUID recognized in payload";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
qInfo() << "[PairingUi] scan accepted uuid=" << uuid.left(13) << "...";
|
||||
|
||||
emit pairingUuidFromScan(uuid);
|
||||
return true;
|
||||
}
|
||||
@@ -406,7 +348,6 @@ bool PairingUiController::applyScannedTextAsPairingUuid(const QString &raw)
|
||||
bool PairingUiController::tryConsumeAndroidQrScan(const QString &code)
|
||||
{
|
||||
if (!g_pairingUiForAndroidQr) {
|
||||
qWarning() << "[PairingUi] tryConsumeAndroidQrScan: no controller (g_pairingUiForAndroidQr null)";
|
||||
return false;
|
||||
}
|
||||
const QString codeCopy = code;
|
||||
@@ -423,7 +364,6 @@ bool PairingUiController::tryConsumeAndroidQrScan(const QString &code)
|
||||
}
|
||||
ctlPtr->applyScannedTextAsPairingUuid(codeCopy);
|
||||
});
|
||||
qInfo() << "[PairingUi] tryConsumeAndroidQrScan: scheduled apply on Qt thread, rawLen=" << codeCopy.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -743,8 +683,6 @@ void PairingUiController::cancelAllPairingActivity()
|
||||
}
|
||||
|
||||
cancelTvQrSession();
|
||||
|
||||
setEmbeddedPairingQrCameraActive(false);
|
||||
}
|
||||
|
||||
void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIndex)
|
||||
@@ -757,15 +695,12 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
|
||||
}
|
||||
|
||||
const QString trimmedUuid = qrUuid.trimmed();
|
||||
qInfo() << "[PairingUi] submitPhonePairing serverIndex=" << serverIndex << "uuidLen=" << trimmedUuid.size();
|
||||
if (trimmedUuid.isEmpty()) {
|
||||
qWarning() << "[PairingUi] submitPhonePairing aborted: empty UUID (paste or scan first)";
|
||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverIndex < 0 || serverIndex >= m_serversController->getServersCount()) {
|
||||
qWarning() << "[PairingUi] submitPhonePairing invalid serverIndex";
|
||||
emit errorOccurred(ErrorCode::InternalError);
|
||||
return;
|
||||
}
|
||||
@@ -773,7 +708,6 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
|
||||
const QString serverId = m_serversController->getServerId(serverIndex);
|
||||
const auto apiV2Opt = m_serversController->apiV2Config(serverId);
|
||||
if (!apiV2Opt.has_value()) {
|
||||
qWarning() << "[PairingUi] submitPhonePairing server is not API v2";
|
||||
emit errorOccurred(ErrorCode::InternalError);
|
||||
return;
|
||||
}
|
||||
@@ -783,7 +717,6 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
|
||||
QString vpnKey;
|
||||
const ErrorCode keyErr = m_subscriptionController->prepareVpnKeyExport(serverId, vpnKey);
|
||||
if (keyErr != ErrorCode::NoError) {
|
||||
qWarning() << "[PairingUi] prepareVpnKeyExport failed" << static_cast<int>(keyErr);
|
||||
emit errorOccurred(keyErr);
|
||||
return;
|
||||
}
|
||||
@@ -792,7 +725,6 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
|
||||
const QJsonArray supportedProtocols = apiV2.apiConfig.supportedProtocols;
|
||||
const QString apiKey = apiV2.authData.apiKey;
|
||||
if (apiKey.isEmpty()) {
|
||||
qWarning() << "[PairingUi] submitPhonePairing aborted: empty API key on server card";
|
||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -32,8 +32,6 @@ class PairingUiController : public QObject
|
||||
pendingPhonePairingUuidChanged)
|
||||
Q_PROPERTY(QString lastSuccessfulPhonePairingDisplayName READ lastSuccessfulPhonePairingDisplayName NOTIFY
|
||||
lastSuccessfulPhonePairingDisplayNameChanged)
|
||||
Q_PROPERTY(bool embeddedPairingQrCameraActive READ embeddedPairingQrCameraActive WRITE setEmbeddedPairingQrCameraActive NOTIFY
|
||||
embeddedPairingQrCameraActiveChanged)
|
||||
Q_PROPERTY(bool iosNativePairingQrOverlayBuild READ iosNativePairingQrOverlayBuild CONSTANT)
|
||||
Q_PROPERTY(bool androidNativePairingQrOverlayBuild READ androidNativePairingQrOverlayBuild CONSTANT)
|
||||
Q_PROPERTY(qint64 androidPairingReaderCooldownUntilEpochMs READ androidPairingReaderCooldownUntilEpochMs NOTIFY
|
||||
@@ -57,12 +55,8 @@ public:
|
||||
QString pendingPhonePairingUuid() const { return m_pendingPhonePairingUuid; }
|
||||
void setPendingPhonePairingUuid(const QString &uuid);
|
||||
QString lastSuccessfulPhonePairingDisplayName() const { return m_lastSuccessfulPhonePairingDisplayName; }
|
||||
bool embeddedPairingQrCameraActive() const { return m_embeddedPairingQrCameraActive; }
|
||||
bool iosNativePairingQrOverlayBuild() const;
|
||||
bool androidNativePairingQrOverlayBuild() const;
|
||||
Q_INVOKABLE void setEmbeddedPairingQrCameraActive(bool active);
|
||||
Q_INVOKABLE void syncIosEmbeddedPairingQrNativeBottomExtra(int extraPt);
|
||||
Q_INVOKABLE void refreshIosEmbeddedPairingQrChrome();
|
||||
|
||||
qint64 androidPairingReaderCooldownUntilEpochMs() const { return m_androidPairingReaderCooldownUntilEpochMs; }
|
||||
|
||||
@@ -110,7 +104,6 @@ signals:
|
||||
|
||||
void pairingUuidFromScan(const QString &uuid);
|
||||
void pairingCameraAccessFinished(bool granted);
|
||||
void embeddedPairingQrCameraActiveChanged();
|
||||
void androidPairingReaderCooldownUntilEpochMsChanged();
|
||||
void pairingSendQrScanRejectedInvalidPayload();
|
||||
void pairingIosNativeQrOverlayBackRequested();
|
||||
@@ -149,7 +142,6 @@ private:
|
||||
QPointer<QNetworkReply> m_phoneNetworkReply;
|
||||
quint64 m_phoneSessionGeneration { 0 };
|
||||
|
||||
bool m_embeddedPairingQrCameraActive = false;
|
||||
qint64 m_androidPairingReaderCooldownUntilEpochMs = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ namespace PageLoader
|
||||
PageSetupWizardApiPremiumInfo,
|
||||
PageSetupWizardApiTrialEmail,
|
||||
|
||||
PageSettingsApiQrPairingDev,
|
||||
PageSettingsApiQrPairingSend,
|
||||
PageSetupWizardApiQrPairingReceive,
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import QtQuick.Controls
|
||||
Menu {
|
||||
property var textObj
|
||||
|
||||
popupType: Popup.Native
|
||||
popupType: Qt.platform.os === "ios" ? Popup.Item : Popup.Native
|
||||
|
||||
onAboutToShow: blocker.enabled = true
|
||||
onClosed: blocker.enabled = false
|
||||
|
||||
@@ -53,10 +53,7 @@ TabButton {
|
||||
background: Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
/** iOS embedded QR camera: clear window + preview under Qt; transparent tab cells show the camera. */
|
||||
color: (PairingUiController.embeddedPairingQrCameraActive && Qt.platform.os !== "android")
|
||||
? AmneziaStyle.color.onyxBlack
|
||||
: AmneziaStyle.color.transparent
|
||||
color: AmneziaStyle.color.transparent
|
||||
radius: 10
|
||||
|
||||
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
|
||||
|
||||
@@ -90,21 +90,6 @@ PageType {
|
||||
footer: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
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
|
||||
|
||||
@@ -109,11 +109,11 @@ PageType {
|
||||
}
|
||||
|
||||
function onPhonePairingSucceeded() {
|
||||
const serverIndex = ServersUiController.getProcessedServerIndex()
|
||||
if (serverIndex < 0) {
|
||||
var serverId = ServersUiController.getServerId(ServersUiController.processedServerIndex)
|
||||
if (serverId.length === 0) {
|
||||
return
|
||||
}
|
||||
SubscriptionUiController.getAccountInfo(serverIndex, true)
|
||||
SubscriptionUiController.getAccountInfo(serverId, true)
|
||||
SubscriptionUiController.updateApiDevicesModel()
|
||||
if (!root.visible) {
|
||||
return
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
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 int qrImageIndex: 0
|
||||
property bool pairingCameraOpen: false
|
||||
property int lastPairingScanToastClockMs: 0
|
||||
|
||||
function notifyPairingScanSuccess() {
|
||||
const now = new Date().getTime()
|
||||
if (now - root.lastPairingScanToastClockMs < 1600) {
|
||||
return
|
||||
}
|
||||
root.lastPairingScanToastClockMs = now
|
||||
PageController.showNotificationMessage(
|
||||
qsTr("QR session ID captured. Tap Send from current subscription to complete pairing."))
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: pairingCameraKickTimer
|
||||
interval: 180
|
||||
repeat: false
|
||||
onTriggered: root.restartPairingIosCamera()
|
||||
}
|
||||
|
||||
function restartPairingIosCamera() {
|
||||
if (Qt.platform.os !== "ios" || !root.pairingCameraOpen) {
|
||||
return
|
||||
}
|
||||
if (cameraSlot.width < 32 || cameraSlot.height < 32) {
|
||||
console.info("[PairingQr] cameraSlot too small wxh=", cameraSlot.width, cameraSlot.height, "retry")
|
||||
pairingCameraKickTimer.restart()
|
||||
return
|
||||
}
|
||||
var p = cameraSlot.mapToItem(root, 0, 0)
|
||||
console.info("[PairingQr] start preview frame", p.x, p.y, cameraSlot.width, cameraSlot.height)
|
||||
pairingQrReader.stopReading()
|
||||
pairingQrReader.setCameraSize(Qt.rect(Math.round(p.x), Math.round(p.y), Math.round(cameraSlot.width), Math.round(cameraSlot.height)))
|
||||
pairingQrReader.startReading()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onVisibleChanged() {
|
||||
if (!root.visible) {
|
||||
pairingCameraKickTimer.stop()
|
||||
pairingQrReader.stopReading()
|
||||
root.pairingCameraOpen = false
|
||||
PairingUiController.cancelAllPairingActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onPairingCameraOpenChanged() {
|
||||
if (!root.pairingCameraOpen) {
|
||||
pairingCameraKickTimer.stop()
|
||||
pairingQrReader.stopReading()
|
||||
return
|
||||
}
|
||||
if (Qt.platform.os === "ios") {
|
||||
pairingCameraKickTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: cameraSlot
|
||||
enabled: Qt.platform.os === "ios" && root.pairingCameraOpen
|
||||
function onWidthChanged() {
|
||||
pairingCameraKickTimer.restart()
|
||||
}
|
||||
function onHeightChanged() {
|
||||
pairingCameraKickTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
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("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
|
||||
Layout.rightMargin: 16
|
||||
text: qsTr("Experimental: transfer API configuration to another device via gateway. Use “Receive” on the device that shows the QR code, and “Send” on the premium device.")
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
text: qsTr("Receive configuration (TV / second device)")
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
implicitHeight: width
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 24
|
||||
text: qsTr("Send configuration (premium device)")
|
||||
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 TV 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
|
||||
root.notifyPairingScanSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
pairingQrReader.stopReading()
|
||||
return
|
||||
}
|
||||
if (Qt.platform.os === "ios") {
|
||||
pairingCameraKickTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 onTvQrCodesChanged() {
|
||||
root.qrImageIndex = 0
|
||||
}
|
||||
|
||||
function onTvSessionUuidChanged() {
|
||||
root.qrImageIndex = 0
|
||||
uuidField.textField.text = PairingUiController.tvSessionUuid
|
||||
}
|
||||
|
||||
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) {
|
||||
uuidField.textField.text = uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import QRCodeReader 1.0
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
@@ -16,71 +14,12 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool pairingQrChromeDebug: false
|
||||
|
||||
readonly property bool useIosStyleNativeQrReader: GC.isMobile() && Qt.platform.os !== "android"
|
||||
|
||||
readonly property bool useIosNativePairingQrOverlay: PairingUiController.iosNativePairingQrOverlayBuild
|
||||
|
||||
readonly property bool useAndroidNativePairingQrScanner: GC.isMobile() && Qt.platform.os === "android"
|
||||
readonly property bool useAndroidNativePairingQrOverlay: PairingUiController.androidNativePairingQrOverlayBuild
|
||||
&& GC.isMobile()
|
||||
&& Qt.platform.os === "android"
|
||||
|
||||
readonly property bool extendScanDimToScreenEdges: GC.isMobile() && pairingWizardStep === 0
|
||||
&& PairingUiController.embeddedPairingQrCameraActive
|
||||
&& !root.useAndroidNativePairingQrOverlay
|
||||
clip: !extendScanDimToScreenEdges
|
||||
|
||||
/** QQuickWindow as var — typing as Item breaks bindings on Qt 6. */
|
||||
readonly property var appWindow: Window.window
|
||||
readonly property real scanDimBleedTop: {
|
||||
if (!extendScanDimToScreenEdges || !appWindow || !appWindow.contentItem)
|
||||
return 0
|
||||
let bleed = Math.max(0, root.mapToItem(appWindow.contentItem, 0, 0).y)
|
||||
if (bleed < 2 && root.useIosStyleNativeQrReader)
|
||||
bleed = Math.max(bleed, PageController.safeAreaTopMargin)
|
||||
return bleed
|
||||
}
|
||||
readonly property real scanDimBleedBottom: {
|
||||
if (!extendScanDimToScreenEdges || !appWindow || !appWindow.contentItem)
|
||||
return 0
|
||||
const o = root.mapToItem(appWindow.contentItem, 0, root.height)
|
||||
let bleed = Math.max(0, appWindow.height - o.y)
|
||||
const slack = Math.max(0, appWindow.height - root.height - scanDimBleedTop)
|
||||
if (bleed < slack - 1)
|
||||
bleed = Math.max(bleed, slack)
|
||||
if (bleed < 2 && root.useIosStyleNativeQrReader)
|
||||
bleed = Math.max(bleed, PageController.safeAreaBottomMargin + 72)
|
||||
return bleed
|
||||
}
|
||||
|
||||
readonly property real scanDimBleedBottomForDimLayer: (root.useIosStyleNativeQrReader
|
||||
&& PairingUiController.embeddedPairingQrCameraActive) ? 0 : scanDimBleedBottom
|
||||
|
||||
function pushIosNativeBottomBleedSync() {
|
||||
if (!root.useIosStyleNativeQrReader || !PairingUiController.embeddedPairingQrCameraActive) {
|
||||
return
|
||||
}
|
||||
PairingUiController.syncIosEmbeddedPairingQrNativeBottomExtra(Math.max(0, Math.round(root.scanDimBleedBottom)))
|
||||
}
|
||||
|
||||
onScanDimBleedBottomChanged: {
|
||||
if (PairingUiController.embeddedPairingQrCameraActive && root.useIosStyleNativeQrReader) {
|
||||
Qt.callLater(root.pushIosNativeBottomBleedSync)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PairingUiController
|
||||
|
||||
function onEmbeddedPairingQrCameraActiveChanged() {
|
||||
if (PairingUiController.embeddedPairingQrCameraActive) {
|
||||
Qt.callLater(root.pushIosNativeBottomBleedSync)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property int pairingWizardStep: 0
|
||||
property bool keepPhonePairingInBackgroundOnClose: false
|
||||
|
||||
@@ -88,31 +27,17 @@ PageType {
|
||||
property bool addDeviceConfirmNavigationScheduled: false
|
||||
property bool awaitingCameraPermissionForScan: false
|
||||
property bool waitingSettingsReturnForScan: false
|
||||
property bool torchOn: false
|
||||
|
||||
/** Suppress double startActivity when StackView fires both Component.onCompleted and onVisibleChanged. */
|
||||
property int _androidPairingReaderLastStartMs: 0
|
||||
|
||||
Timer {
|
||||
id: pairingCameraKickTimer
|
||||
interval: 220
|
||||
repeat: false
|
||||
onTriggered: root.restartPairingIosCamera()
|
||||
}
|
||||
|
||||
function stopMobileScanner() {
|
||||
torchOn = false
|
||||
if (root.useIosNativePairingQrOverlay) {
|
||||
PairingUiController.setPairingQrTorchEnabled(false)
|
||||
PairingUiController.dismissIosPairingQrNativeOverlayScanner()
|
||||
return
|
||||
}
|
||||
if (Qt.platform.os === "android") {
|
||||
} else if (Qt.platform.os === "android") {
|
||||
PairingUiController.setPairingQrTorchEnabled(false)
|
||||
} else if (root.useIosStyleNativeQrReader) {
|
||||
pairingQrReader.setTorchEnabled(false)
|
||||
}
|
||||
pairingQrReader.stopReading()
|
||||
PairingUiController.embeddedPairingQrCameraActive = false
|
||||
}
|
||||
|
||||
function startMobileScanner() {
|
||||
@@ -122,7 +47,6 @@ PageType {
|
||||
if (!root.visible) {
|
||||
return
|
||||
}
|
||||
/** Confirm step (or transition to it): never reopen native / embedded scanner from stray taps or visibility. */
|
||||
if (root.pairingWizardStep !== 0) {
|
||||
return
|
||||
}
|
||||
@@ -138,10 +62,9 @@ PageType {
|
||||
PairingUiController.presentIosPairingQrNativeOverlayScanner(
|
||||
qsTr("Add device via QR"),
|
||||
qsTr("Scan the session QR shown on the device you want to add. You will confirm before the subscription is sent."))
|
||||
/** Do not run pairingCameraKickTimer here: restartCapture during first startRunning races the session (torch needs 2–3 taps). */
|
||||
return
|
||||
}
|
||||
if (root.useAndroidNativePairingQrScanner) {
|
||||
if (Qt.platform.os === "android") {
|
||||
const coolUntil = PairingUiController.androidPairingReaderCooldownUntilEpochMs
|
||||
if (Date.now() < coolUntil) {
|
||||
return
|
||||
@@ -152,23 +75,7 @@ PageType {
|
||||
}
|
||||
_androidPairingReaderLastStartMs = now
|
||||
PairingUiController.openPairingQrScanner()
|
||||
return
|
||||
}
|
||||
PairingUiController.embeddedPairingQrCameraActive = true
|
||||
if (root.useIosStyleNativeQrReader) {
|
||||
// Session must start here, not only after pairingCameraKickTimer (220ms), otherwise
|
||||
// torch/scan run before startReading and native layer never attaches.
|
||||
restartPairingIosCamera()
|
||||
pairingCameraKickTimer.restart()
|
||||
/** After resume embedded can stay true so setEmbedded skips; QUIMetalView may be opaque again. */
|
||||
Qt.callLater(function () {
|
||||
PairingUiController.refreshIosEmbeddedPairingQrChrome()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function startPairingScanAfterPermission() {
|
||||
startMobileScanner()
|
||||
}
|
||||
|
||||
function showScanCameraDeniedDrawer() {
|
||||
@@ -195,20 +102,6 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
function restartPairingIosCamera() {
|
||||
if (root.useIosNativePairingQrOverlay) {
|
||||
PairingUiController.restartIosPairingQrNativeOverlayCapture()
|
||||
return
|
||||
}
|
||||
if (!root.useIosStyleNativeQrReader || pairingWizardStep !== 0) {
|
||||
return
|
||||
}
|
||||
// Never gate on root.visible here: under StackView the active page often has
|
||||
// visible === false while it is on screen, so startReading never ran (no session, no torch).
|
||||
pairingQrReader.stopReading()
|
||||
pairingQrReader.startReading()
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (!keepPhonePairingInBackgroundOnClose && !PairingUiController.phonePairingBusy) {
|
||||
PairingUiController.cancelAllPairingActivity()
|
||||
@@ -217,18 +110,16 @@ PageType {
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
/** Only reset confirm flag on scan step; clearing it on confirm breaks guards if visible flickers. */
|
||||
if (pairingWizardStep === 0) {
|
||||
addDeviceConfirmNavigationScheduled = false
|
||||
Qt.callLater(startMobileScanner)
|
||||
}
|
||||
} else {
|
||||
pairingCameraKickTimer.stop()
|
||||
} else if (!PairingUiController.phonePairingBusy) {
|
||||
stopMobileScanner()
|
||||
_androidPairingReaderLastStartMs = 0
|
||||
pairingWizardStep = 0
|
||||
waitingSettingsReturnForScan = false
|
||||
if (!keepPhonePairingInBackgroundOnClose && !PairingUiController.phonePairingBusy) {
|
||||
if (!keepPhonePairingInBackgroundOnClose) {
|
||||
PairingUiController.cancelAllPairingActivity()
|
||||
}
|
||||
}
|
||||
@@ -238,16 +129,12 @@ PageType {
|
||||
if (pairingWizardStep !== 0) {
|
||||
stopMobileScanner()
|
||||
} else if (root.visible) {
|
||||
/**
|
||||
* Android native: use Qt.callLater like iOS — a multi-second Timer delay left the QML scan chrome
|
||||
* visible with an empty (black) viewport until CameraActivity opened.
|
||||
*/
|
||||
Qt.callLater(startMobileScanner)
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (GC.isMobile() && root.visible && pairingWizardStep === 0) {
|
||||
if (visible && pairingWizardStep === 0) {
|
||||
Qt.callLater(startMobileScanner)
|
||||
}
|
||||
}
|
||||
@@ -260,37 +147,15 @@ PageType {
|
||||
return
|
||||
}
|
||||
root.tryResumeScanAfterCameraSettings()
|
||||
if (!root.useIosStyleNativeQrReader || root.pairingWizardStep !== 0
|
||||
|| !PairingUiController.isPairingCameraAccessGranted()) {
|
||||
return
|
||||
}
|
||||
if (root.useIosNativePairingQrOverlay) {
|
||||
Qt.callLater(function () {
|
||||
if (!root.visible || root.pairingWizardStep !== 0 || !GC.isMobile()) {
|
||||
return
|
||||
}
|
||||
PairingUiController.restartIosPairingQrNativeOverlayCapture()
|
||||
})
|
||||
if (!root.useIosNativePairingQrOverlay || root.pairingWizardStep !== 0
|
||||
|| !PairingUiController.isPairingCameraAccessGranted()) {
|
||||
return
|
||||
}
|
||||
Qt.callLater(function () {
|
||||
if (!root.visible || root.pairingWizardStep !== 0 || !GC.isMobile()) {
|
||||
return
|
||||
}
|
||||
PairingUiController.embeddedPairingQrCameraActive = true
|
||||
PairingUiController.refreshIosEmbeddedPairingQrChrome()
|
||||
Qt.callLater(function () {
|
||||
if (!root.visible || root.pairingWizardStep !== 0) {
|
||||
return
|
||||
}
|
||||
root.restartPairingIosCamera()
|
||||
Qt.callLater(function () {
|
||||
if (!root.visible || root.pairingWizardStep !== 0) {
|
||||
return
|
||||
}
|
||||
PairingUiController.refreshIosEmbeddedPairingQrChrome()
|
||||
})
|
||||
})
|
||||
PairingUiController.restartIosPairingQrNativeOverlayCapture()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -308,15 +173,15 @@ PageType {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
/** Brief Qt backdrop + back while CameraActivity is starting (native holds title/instructions like iOS overlay). */
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: pairingWizardStep === 0 && root.useAndroidNativePairingQrOverlay
|
||||
color: AmneziaStyle.color.midnightBlack
|
||||
z: 1
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
visible: pairingWizardStep === 0 && root.useAndroidNativePairingQrOverlay
|
||||
visible: pairingWizardStep === 0 && (root.useAndroidNativePairingQrOverlay || !GC.isMobile())
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: PageController.safeAreaTopMargin
|
||||
anchors.left: parent.left
|
||||
@@ -327,312 +192,25 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: scanStep
|
||||
anchors.fill: parent
|
||||
visible: pairingWizardStep === 0 && !root.useAndroidNativePairingQrOverlay
|
||||
/** Extra guard: invisible alone can race one frame on some stacks; deny input off scan step. */
|
||||
enabled: pairingWizardStep === 0 && !root.useAndroidNativePairingQrOverlay
|
||||
|
||||
readonly property real sqSz: Math.floor(Math.min(width, height) * 0.72)
|
||||
readonly property real sqX: (width - sqSz) / 2
|
||||
readonly property real sqY: (height - sqSz) / 2 - height * 0.06
|
||||
readonly property real dimAlpha: 0.55
|
||||
readonly property color dimTopDebug: "#aa3333"
|
||||
readonly property color dimBottomDebug: "#33aaff"
|
||||
readonly property color dimLeftDebug: "#3333ff"
|
||||
readonly property color dimRightDebug: "#ffaa33"
|
||||
readonly property int bracketThick: 5
|
||||
readonly property int bracketLen: Math.max(28, Math.floor(sqSz * 0.13))
|
||||
readonly property real bracketRadius: bracketThick * 0.5
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: AmneziaStyle.color.midnightBlack
|
||||
visible: !GC.isMobile()
|
||||
}
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 48
|
||||
spacing: 12
|
||||
visible: pairingWizardStep === 0 && !GC.isMobile()
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 48
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
visible: !GC.isMobile()
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
font.pixelSize: 15
|
||||
text: qsTr("QR pairing is available in the mobile app.")
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dimLayer
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -root.scanDimBleedTop
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: -root.scanDimBleedBottomForDimLayer
|
||||
visible: GC.isMobile()
|
||||
z: 0
|
||||
|
||||
readonly property real holeTop: root.scanDimBleedTop + scanStep.sqY
|
||||
readonly property real holeBottom: holeTop + scanStep.sqSz
|
||||
|
||||
Rectangle {
|
||||
x: 0
|
||||
y: 0
|
||||
width: parent.width
|
||||
height: Math.max(0, dimLayer.holeTop)
|
||||
color: root.pairingQrChromeDebug ? scanStep.dimTopDebug : Qt.rgba(0, 0, 0, scanStep.dimAlpha)
|
||||
}
|
||||
Rectangle {
|
||||
x: 0
|
||||
y: dimLayer.holeBottom
|
||||
width: parent.width
|
||||
height: Math.max(0, dimLayer.height - dimLayer.holeBottom)
|
||||
color: root.pairingQrChromeDebug ? scanStep.dimBottomDebug : Qt.rgba(0, 0, 0, scanStep.dimAlpha)
|
||||
}
|
||||
Rectangle {
|
||||
x: 0
|
||||
y: dimLayer.holeTop
|
||||
width: Math.max(0, scanStep.sqX)
|
||||
height: scanStep.sqSz
|
||||
color: root.pairingQrChromeDebug ? scanStep.dimLeftDebug : Qt.rgba(0, 0, 0, scanStep.dimAlpha)
|
||||
}
|
||||
Rectangle {
|
||||
x: scanStep.sqX + scanStep.sqSz
|
||||
y: dimLayer.holeTop
|
||||
width: Math.max(0, parent.width - (scanStep.sqX + scanStep.sqSz))
|
||||
height: scanStep.sqSz
|
||||
color: root.pairingQrChromeDebug ? scanStep.dimRightDebug : Qt.rgba(0, 0, 0, scanStep.dimAlpha)
|
||||
}
|
||||
}
|
||||
|
||||
/** Same onyx as tab bar: bridges dim/camera to TabBar sibling so the seam is not only TabBar.background overlap. */
|
||||
Rectangle {
|
||||
id: pairingIosStackBottomChromeBridge
|
||||
objectName: "pairingIosStackBottomChromeBridge"
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 22
|
||||
visible: GC.isMobile() && root.useIosStyleNativeQrReader && PairingUiController.embeddedPairingQrCameraActive
|
||||
color: root.pairingQrChromeDebug ? "#8844ff" : AmneziaStyle.color.onyxBlack
|
||||
z: 1
|
||||
}
|
||||
|
||||
Item {
|
||||
x: scanStep.sqX
|
||||
y: scanStep.sqY
|
||||
width: scanStep.sqSz
|
||||
height: scanStep.sqSz
|
||||
visible: GC.isMobile()
|
||||
|
||||
Rectangle {
|
||||
x: 0
|
||||
y: 0
|
||||
width: scanStep.bracketLen
|
||||
height: scanStep.bracketThick
|
||||
radius: scanStep.bracketRadius
|
||||
color: AmneziaStyle.color.paleGray
|
||||
}
|
||||
Rectangle {
|
||||
x: 0
|
||||
y: 0
|
||||
width: scanStep.bracketThick
|
||||
height: scanStep.bracketLen
|
||||
radius: scanStep.bracketRadius
|
||||
color: AmneziaStyle.color.paleGray
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: scanStep.sqSz - scanStep.bracketLen
|
||||
y: 0
|
||||
width: scanStep.bracketLen
|
||||
height: scanStep.bracketThick
|
||||
radius: scanStep.bracketRadius
|
||||
color: AmneziaStyle.color.paleGray
|
||||
}
|
||||
Rectangle {
|
||||
x: scanStep.sqSz - scanStep.bracketThick
|
||||
y: 0
|
||||
width: scanStep.bracketThick
|
||||
height: scanStep.bracketLen
|
||||
radius: scanStep.bracketRadius
|
||||
color: AmneziaStyle.color.paleGray
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: 0
|
||||
y: scanStep.sqSz - scanStep.bracketThick
|
||||
width: scanStep.bracketLen
|
||||
height: scanStep.bracketThick
|
||||
radius: scanStep.bracketRadius
|
||||
color: AmneziaStyle.color.paleGray
|
||||
}
|
||||
Rectangle {
|
||||
x: 0
|
||||
y: scanStep.sqSz - scanStep.bracketLen
|
||||
width: scanStep.bracketThick
|
||||
height: scanStep.bracketLen
|
||||
radius: scanStep.bracketRadius
|
||||
color: AmneziaStyle.color.paleGray
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: scanStep.sqSz - scanStep.bracketLen
|
||||
y: scanStep.sqSz - scanStep.bracketThick
|
||||
width: scanStep.bracketLen
|
||||
height: scanStep.bracketThick
|
||||
radius: scanStep.bracketRadius
|
||||
color: AmneziaStyle.color.paleGray
|
||||
}
|
||||
Rectangle {
|
||||
x: scanStep.sqSz - scanStep.bracketThick
|
||||
y: scanStep.sqSz - scanStep.bracketLen
|
||||
width: scanStep.bracketThick
|
||||
height: scanStep.bracketLen
|
||||
radius: scanStep.bracketRadius
|
||||
color: AmneziaStyle.color.paleGray
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: headerBlock
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 8 + PageController.safeAreaTopMargin
|
||||
spacing: 10
|
||||
z: 2
|
||||
/** Native iOS overlay draws its own header. */
|
||||
visible: !GC.isMobile() || !root.useIosNativePairingQrOverlay
|
||||
|
||||
BackButtonType {
|
||||
width: parent.width
|
||||
backButtonFunction: function() {
|
||||
PageController.closePage()
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
width: parent.width - 32
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("Add device via QR")
|
||||
font.pixelSize: 28
|
||||
font.bold: true
|
||||
color: AmneziaStyle.color.paleGray
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
width: parent.width - 32
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("Scan the session QR shown on the device you want to add. You will confirm before the subscription is sent.")
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
z: 2
|
||||
width: 56
|
||||
height: 56
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottomMargin: 28 + PageController.safeAreaBottomMargin
|
||||
visible: GC.isMobile() && !root.useIosNativePairingQrOverlay
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 28
|
||||
color: Qt.rgba(1, 1, 1, root.torchOn ? 0.42 : 0.22)
|
||||
border.width: root.torchOn ? 2 : 0
|
||||
border.color: AmneziaStyle.color.goldenApricot
|
||||
}
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "🔦"
|
||||
font.pixelSize: 26
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.torchOn = !root.torchOn
|
||||
if (Qt.platform.os === "android") {
|
||||
PairingUiController.setPairingQrTorchEnabled(root.torchOn)
|
||||
} else if (root.useIosNativePairingQrOverlay) {
|
||||
PairingUiController.setPairingQrTorchEnabled(root.torchOn)
|
||||
} else if (root.useIosStyleNativeQrReader) {
|
||||
pairingQrReader.setTorchEnabled(root.torchOn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottomMargin: 100 + PageController.safeAreaBottomMargin
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
visible: PairingUiController.phoneStatusMessage.length > 0
|
||||
text: PairingUiController.phoneStatusMessage
|
||||
wrapMode: Text.Wrap
|
||||
z: 2
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 0
|
||||
height: 0
|
||||
visible: false
|
||||
|
||||
QRCodeReader {
|
||||
id: pairingQrReader
|
||||
|
||||
// Same idea as PageSetupWizardQrReader: ensure startReading runs even if
|
||||
// StackView/onVisible timing skips startMobileScanner once.
|
||||
Component.onCompleted: {
|
||||
if (root.useIosNativePairingQrOverlay) {
|
||||
return
|
||||
}
|
||||
if (!root.useIosStyleNativeQrReader || root.pairingWizardStep !== 0) {
|
||||
return
|
||||
}
|
||||
Qt.callLater(function () {
|
||||
if (root.pairingWizardStep !== 0 || !PairingUiController.isPairingCameraAccessGranted()) {
|
||||
return
|
||||
}
|
||||
PairingUiController.embeddedPairingQrCameraActive = true
|
||||
pairingQrReader.stopReading()
|
||||
pairingQrReader.startReading()
|
||||
})
|
||||
}
|
||||
|
||||
onCodeReaded: function(code) {
|
||||
if (addDeviceConfirmNavigationScheduled) {
|
||||
return
|
||||
}
|
||||
if (PairingUiController.applyScannedTextAsPairingUuid(code)) {
|
||||
addDeviceConfirmNavigationScheduled = true
|
||||
stopMobileScanner()
|
||||
} else {
|
||||
const now = new Date().getTime()
|
||||
if (now - lastInvalidPairingQrToastClockMs >= 2200) {
|
||||
lastInvalidPairingQrToastClockMs = now
|
||||
PageController.showNotificationMessage(
|
||||
qsTr("This QR code is not a pairing session. Show the code from the other device’s “receive config” screen."))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: confirmStep
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 0
|
||||
anchors.rightMargin: 0
|
||||
visible: pairingWizardStep === 1
|
||||
z: 10
|
||||
spacing: 16
|
||||
@@ -732,7 +310,6 @@ PageType {
|
||||
addDeviceConfirmNavigationScheduled = true
|
||||
stopMobileScanner()
|
||||
PairingUiController.pendingPhonePairingUuid = uuid
|
||||
/** Immediate step switch so scan chrome is not hit-testable for another frame (avoids reopening CameraActivity). */
|
||||
pairingWizardStep = 1
|
||||
}
|
||||
|
||||
@@ -753,7 +330,6 @@ PageType {
|
||||
PageController.closePage()
|
||||
}
|
||||
|
||||
/** Native CameraActivity back: leave pairing flow (same as iOS overlay back). Do NOT reopen scanner. */
|
||||
function onPairingAndroidNativeQrScannerUserDismissed() {
|
||||
if (!root.useAndroidNativePairingQrOverlay) {
|
||||
return
|
||||
|
||||
@@ -12,7 +12,6 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property int qrImageIndex: 0
|
||||
readonly property int qrRefreshIntervalMs: Math.max(5000, PairingUiController.tvPairingWaitWindowSeconds * 1000)
|
||||
|
||||
function scrollPairingToBottom() {
|
||||
@@ -134,15 +133,7 @@ PageType {
|
||||
anchors.margins: 20
|
||||
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
|
||||
}
|
||||
}
|
||||
source: PairingUiController.tvQrCodesCount > 0 ? PairingUiController.tvQrCodes[0] : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,7 +161,6 @@ PageType {
|
||||
target: PairingUiController
|
||||
|
||||
function onTvQrCodesChanged() {
|
||||
root.qrImageIndex = 0
|
||||
if (PairingUiController.tvQrCodesCount > 0) {
|
||||
scrollToBottomRetryTimer.retries = 0
|
||||
scrollToBottomRetryTimer.start()
|
||||
@@ -185,10 +175,6 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
function onTvSessionUuidChanged() {
|
||||
root.qrImageIndex = 0
|
||||
}
|
||||
|
||||
function onTvPairingConfigReceived() {
|
||||
scrollToBottomRetryTimer.stop()
|
||||
qrRotationTimer.stop()
|
||||
|
||||
@@ -19,15 +19,6 @@ PageType {
|
||||
property bool isControlsDisabled: false
|
||||
property bool isTabBarDisabled: false
|
||||
|
||||
property bool pairingQrChromeDebug: false
|
||||
|
||||
readonly property int tabBarChromeOverlapUp: (PairingUiController.embeddedPairingQrCameraActive && GC.isMobile()
|
||||
&& Qt.platform.os !== "android")
|
||||
? (root.pairingQrChromeDebug ? 24 : 18) : 0
|
||||
|
||||
readonly property int tabStackPairingUnderlapDown: (PairingUiController.embeddedPairingQrCameraActive && GC.isMobile()
|
||||
&& Qt.platform.os !== "android") ? 8 : 0
|
||||
|
||||
Connections {
|
||||
objectName: "pageControllerConnection"
|
||||
|
||||
@@ -279,7 +270,7 @@ PageType {
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: tabBar.top
|
||||
anchors.bottomMargin: -root.tabStackPairingUnderlapDown
|
||||
anchors.bottomMargin: 0
|
||||
|
||||
enabled: !root.isControlsDisabled
|
||||
|
||||
@@ -351,16 +342,16 @@ PageType {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: parent.height + root.tabBarChromeOverlapUp
|
||||
height: parent.height
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: root.pairingQrChromeDebug ? "#00ff66" : AmneziaStyle.color.onyxBlack
|
||||
color: AmneziaStyle.color.onyxBlack
|
||||
}
|
||||
Shape {
|
||||
id: tabBarChromeShape
|
||||
objectName: "backgroundShape"
|
||||
visible: root.tabBarChromeOverlapUp === 0
|
||||
visible: true
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
@@ -16,8 +16,7 @@ Window {
|
||||
id: root
|
||||
objectName: "mainWindow"
|
||||
|
||||
readonly property bool pairingQrCameraUnderlay: PairingUiController.embeddedPairingQrCameraActive && GC.isMobile()
|
||||
color: pairingQrCameraUnderlay ? "#00000000" : AmneziaStyle.color.midnightBlack
|
||||
color: AmneziaStyle.color.midnightBlack
|
||||
|
||||
Connections {
|
||||
target: Qt.application
|
||||
|
||||
@@ -97,7 +97,6 @@
|
||||
<file>Pages2/PageSettingsAbout.qml</file>
|
||||
<file>Pages2/PageSettingsApiAvailableCountries.qml</file>
|
||||
<file>Pages2/PageSettingsApiServerInfo.qml</file>
|
||||
<file>Pages2/PageSettingsApiQrPairingDev.qml</file>
|
||||
<file>Pages2/PageSettingsApiQrPairingSend.qml</file>
|
||||
<file>Pages2/PageSetupWizardApiQrPairingReceive.qml</file>
|
||||
<file>Pages2/PageSettingsApplication.qml</file>
|
||||
|
||||
Reference in New Issue
Block a user