mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-07-01 14:15:24 +02:00
account: implement export-from-device using new API
- Implements new APIs - Implements export-from-device mechanism (link device in account settings) Gitlab: #1695 Change-Id: I3d3486380e695ea44c199dbe0a0448f724b4d2db
This commit is contained in:
parent
33da15daba
commit
d3c76eac8d
15 changed files with 857 additions and 314 deletions
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -27,3 +27,7 @@
|
|||
path = 3rdparty/tidy-html5
|
||||
url = https://github.com/htacg/tidy-html5.git
|
||||
ignore = dirty
|
||||
[submodule "3rdparty/zxing-cpp"]
|
||||
path = 3rdparty/zxing-cpp
|
||||
url = https://github.com/nu-book/zxing-cpp.git
|
||||
ignore = dirty
|
||||
|
|
1
3rdparty/zxing-cpp
vendored
Submodule
1
3rdparty/zxing-cpp
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit a920817b6fe0508cc4aca9003003c2812a78e935
|
|
@ -364,6 +364,8 @@ set(COMMON_SOURCES
|
|||
${APP_SRC_DIR}/pluginversionmanager.cpp
|
||||
${APP_SRC_DIR}/connectioninfolistmodel.cpp
|
||||
${APP_SRC_DIR}/pluginversionmanager.cpp
|
||||
${APP_SRC_DIR}/linkdevicemodel.cpp
|
||||
${APP_SRC_DIR}/qrcodescannermodel.cpp
|
||||
)
|
||||
|
||||
set(COMMON_HEADERS
|
||||
|
@ -436,6 +438,8 @@ set(COMMON_HEADERS
|
|||
${APP_SRC_DIR}/pttlistener.h
|
||||
${APP_SRC_DIR}/crashreportclient.h
|
||||
${APP_SRC_DIR}/crashreporter.h
|
||||
${APP_SRC_DIR}/linkdevicemodel.h
|
||||
${APP_SRC_DIR}/qrcodescannermodel.h
|
||||
)
|
||||
|
||||
# For libavutil/avframe.
|
||||
|
@ -678,6 +682,15 @@ list(APPEND CLIENT_LINK_DIRS ${tidy_BINARY_DIR}/Release)
|
|||
list(APPEND CLIENT_INCLUDE_DIRS ${tidy_SOURCE_DIR}/include)
|
||||
list(APPEND CLIENT_LIBS tidy-static)
|
||||
|
||||
# ZXing-cpp configuration
|
||||
set(BUILD_EXAMPLES OFF CACHE BOOL "")
|
||||
set(BUILD_BLACKBOX_TESTS OFF CACHE BOOL "")
|
||||
add_subdirectory(3rdparty/zxing-cpp)
|
||||
|
||||
# Add ZXing-cpp to includes and libraries
|
||||
list(APPEND CLIENT_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/zxing-cpp/core/src)
|
||||
list(APPEND CLIENT_LIBS ZXing)
|
||||
|
||||
# common executable sources
|
||||
qt_add_executable(
|
||||
${PROJECT_NAME}
|
||||
|
|
140
src/app/linkdevicemodel.cpp
Normal file
140
src/app/linkdevicemodel.cpp
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (C) 2025-2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "linkdevicemodel.h"
|
||||
#include "lrcinstance.h"
|
||||
#include "api/accountmodel.h"
|
||||
|
||||
#include "api/account.h"
|
||||
|
||||
using namespace lrc::api::account;
|
||||
|
||||
LinkDeviceModel::LinkDeviceModel(LRCInstance* lrcInstance, QObject* parent)
|
||||
: QObject(parent)
|
||||
, lrcInstance_(lrcInstance)
|
||||
{
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::INIT));
|
||||
connect(&lrcInstance_->accountModel(),
|
||||
&lrc::api::AccountModel::addDeviceStateChanged,
|
||||
this,
|
||||
[this](const QString& accountId,
|
||||
uint32_t operationId,
|
||||
int state,
|
||||
const MapStringString& details) {
|
||||
if (operationId != operationId_)
|
||||
return;
|
||||
|
||||
auto deviceState = static_cast<DeviceAuthState>(state);
|
||||
|
||||
switch (deviceState) {
|
||||
case DeviceAuthState::CONNECTING:
|
||||
handleConnectingSignal();
|
||||
break;
|
||||
case DeviceAuthState::AUTHENTICATING:
|
||||
handleAuthenticatingSignal(Utils::mapStringStringToVariantMap(details));
|
||||
break;
|
||||
case DeviceAuthState::IN_PROGRESS:
|
||||
handleInProgressSignal();
|
||||
break;
|
||||
case DeviceAuthState::DONE:
|
||||
handleDoneSignal(Utils::mapStringStringToVariantMap(details));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::addDevice(const QString& token)
|
||||
{
|
||||
set_tokenErrorMessage("");
|
||||
auto errorMessage = QObject::tr(
|
||||
"New device identifier is not recognized.\nPlease follow above instruction.");
|
||||
|
||||
if (!token.startsWith("jami-auth://") || (token.length() != 59)) {
|
||||
set_tokenErrorMessage(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t result = lrcInstance_->accountModel().addDevice(lrcInstance_->getCurrentAccountInfo().id,
|
||||
token);
|
||||
if (result > 0) {
|
||||
operationId_ = result;
|
||||
} else {
|
||||
set_tokenErrorMessage(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::handleConnectingSignal()
|
||||
{
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::CONNECTING));
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::handleAuthenticatingSignal(const QVariantMap& details)
|
||||
{
|
||||
QString peerAddress = details.value("peer_address").toString();
|
||||
set_ipAddress(peerAddress);
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::AUTHENTICATING));
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::handleInProgressSignal()
|
||||
{
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::IN_PROGRESS));
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::handleDoneSignal(const QVariantMap& details)
|
||||
{
|
||||
QString errorString = details.value("error").toString();
|
||||
if (!errorString.isEmpty() && errorString != "none") {
|
||||
auto error = mapLinkDeviceError(errorString.toStdString());
|
||||
set_linkDeviceError(getLinkDeviceString(error));
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::DONE));
|
||||
} else {
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::DONE));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::confirmAddDevice()
|
||||
{
|
||||
handleInProgressSignal();
|
||||
lrcInstance_->accountModel().confirmAddDevice(lrcInstance_->getCurrentAccountInfo().id,
|
||||
operationId_);
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::cancelAddDevice()
|
||||
{
|
||||
handleInProgressSignal();
|
||||
lrcInstance_->accountModel().cancelAddDevice(lrcInstance_->getCurrentAccountInfo().id,
|
||||
operationId_);
|
||||
}
|
||||
|
||||
void
|
||||
LinkDeviceModel::reset()
|
||||
{
|
||||
set_deviceAuthState(static_cast<int>(DeviceAuthState::INIT));
|
||||
|
||||
set_linkDeviceError("");
|
||||
set_ipAddress("");
|
||||
set_tokenErrorMessage("");
|
||||
}
|
57
src/app/linkdevicemodel.h
Normal file
57
src/app/linkdevicemodel.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (C) 2025-2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api/account.h"
|
||||
|
||||
#include "qmladapterbase.h"
|
||||
#include "qtutils.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QMap>
|
||||
|
||||
class LRCInstance;
|
||||
|
||||
class LinkDeviceModel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_PROPERTY(QString, tokenErrorMessage);
|
||||
QML_PROPERTY(QString, linkDeviceError);
|
||||
QML_PROPERTY(int, deviceAuthState);
|
||||
QML_PROPERTY(QString, ipAddress);
|
||||
|
||||
public:
|
||||
explicit LinkDeviceModel(LRCInstance* lrcInstance, QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void addDevice(const QString& token);
|
||||
|
||||
Q_INVOKABLE void confirmAddDevice();
|
||||
Q_INVOKABLE void cancelAddDevice();
|
||||
Q_INVOKABLE void reset();
|
||||
|
||||
private:
|
||||
bool checkNewStateValidity(lrc::api::account::DeviceAuthState newState) const;
|
||||
void handleConnectingSignal();
|
||||
void handleAuthenticatingSignal(const QVariantMap& details);
|
||||
void handleInProgressSignal();
|
||||
void handleDoneSignal(const QVariantMap& details);
|
||||
|
||||
LRCInstance* lrcInstance_ = nullptr;
|
||||
uint32_t operationId_;
|
||||
};
|
|
@ -74,7 +74,7 @@ Item {
|
|||
property string scanToImportAccount: qsTr("Scan this QR code on your other device to proceed with importing your account.")
|
||||
property string waitingForToken: qsTr("Please wait…")
|
||||
property string scanQRCode: qsTr("Scan QR code")
|
||||
property string connectingToDevice: qsTr("Action required.\nPlease confirm account on your old device.")
|
||||
property string connectingToDevice: qsTr("Action required.\nPlease confirm account on the source device.")
|
||||
property string confirmAccountImport: qsTr("Authenticating device")
|
||||
property string transferringAccount: qsTr("Transferring account…")
|
||||
property string cantScanQRCode: qsTr("If you are unable to scan the QR code, enter this token on your other device to proceed.")
|
||||
|
@ -600,12 +600,17 @@ Item {
|
|||
property string enterAccountPassword: qsTr("Enter account password")
|
||||
property string enterPasswordPinCode: qsTr("This account is password encrypted, enter the password to generate a PIN code.")
|
||||
property string addDevice: qsTr("Add Device")
|
||||
property string pinExpired: qsTr("PIN code has expired.")
|
||||
property string onAnotherDevice: qsTr("On another device")
|
||||
property string onAnotherDeviceInstruction: qsTr("Install and launch Jami, select “Import from another device” and scan the QR code.")
|
||||
property string linkNewDevice: qsTr("Link new device")
|
||||
property string linkingInstructions: qsTr("In Jami, scan the QR code or manually enter the PIN code.")
|
||||
property string pinValidity: qsTr("The PIN code will expire in: ")
|
||||
property string linkDeviceConnecting: qsTr("Connecting to your new device…")
|
||||
property string linkDeviceInProgress: qsTr("The export account operation to the new device is in progress.\nPlease confirm the import on the new device.")
|
||||
property string linkDeviceScanQR: qsTr("On the new device, initiate a new account.\nSelect Add account -> Connect from another device.\nWhen ready, scan the QR code.")
|
||||
property string linkDeviceEnterManually: qsTr("Alternatively you could enter the authentication code manually.")
|
||||
property string linkDeviceEnterCodePlaceholder: qsTr("Enter authentication code")
|
||||
property string linkDeviceAllSet: qsTr("You are all set!\nYour account is successfully imported on the new device!")
|
||||
property string linkDeviceFoundAddress: qsTr("New device found at address below. Is that you?\nClicking on confirm will continue transfering account.")
|
||||
property string linkDeviceNewDeviceIP: qsTr("New device IP address: %1")
|
||||
property string linkDeviceCloseWarningTitle: qsTr("Do you want to exit?")
|
||||
property string linkDeviceCloseWarningMessage: qsTr("Exiting will cancel the import account operation.")
|
||||
|
||||
// PasswordDialog
|
||||
property string enterPassword: qsTr("Enter password")
|
||||
|
|
|
@ -62,6 +62,8 @@
|
|||
#include "pluginlistpreferencemodel.h"
|
||||
#include "preferenceitemlistmodel.h"
|
||||
#include "wizardviewstepmodel.h"
|
||||
#include "linkdevicemodel.h"
|
||||
#include "qrcodescannermodel.h"
|
||||
|
||||
#include "api/peerdiscoverymodel.h"
|
||||
#include "api/codecmodel.h"
|
||||
|
@ -185,6 +187,12 @@ registerTypes(QQmlEngine* engine,
|
|||
QQmlEngine::setObjectOwnership(wizardViewStepModel, QQmlEngine::CppOwnership);
|
||||
REG_QML_SINGLETON<WizardViewStepModel>(REG_MODEL, "WizardViewStepModel", CREATE(wizardViewStepModel));
|
||||
|
||||
// LinkDeviceModel
|
||||
auto linkdevicemodel = new LinkDeviceModel(lrcInstance);
|
||||
qApp->setProperty("LinkDeviceModel", QVariant::fromValue(linkdevicemodel));
|
||||
QQmlEngine::setObjectOwnership(linkdevicemodel, QQmlEngine::CppOwnership);
|
||||
REG_QML_SINGLETON<LinkDeviceModel>(REG_MODEL, "LinkDeviceModel", CREATE(linkdevicemodel));
|
||||
|
||||
// Register app-level objects that are used by QML created objects.
|
||||
// These MUST be set prior to loading the initial QML file, in order to
|
||||
// be available to the QML adapter class factory creation methods.
|
||||
|
@ -195,6 +203,7 @@ registerTypes(QQmlEngine* engine,
|
|||
qApp->setProperty("PreviewEngine", QVariant::fromValue(previewEngine));
|
||||
|
||||
// qml adapter registration
|
||||
QML_REGISTERSINGLETON_TYPE(NS_HELPERS, QRCodeScannerModel);
|
||||
QML_REGISTERSINGLETON_TYPE(NS_HELPERS, AvatarRegistry);
|
||||
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, AccountAdapter);
|
||||
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, CallAdapter);
|
||||
|
|
63
src/app/qrcodescannermodel.cpp
Normal file
63
src/app/qrcodescannermodel.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (C) 2025-2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qrcodescannermodel.h"
|
||||
|
||||
#include <Barcode.h>
|
||||
#include <MultiFormatReader.h>
|
||||
#include <ReadBarcode.h>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
QRCodeScannerModel::QRCodeScannerModel(QObject* parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
QString
|
||||
QRCodeScannerModel::scanImage(const QImage& image)
|
||||
{
|
||||
if (image.isNull())
|
||||
return QString();
|
||||
|
||||
// Convert QImage to grayscale and get raw data
|
||||
QImage grayImage = image.convertToFormat(QImage::Format_Grayscale8);
|
||||
int width = grayImage.width();
|
||||
int height = grayImage.height();
|
||||
|
||||
try {
|
||||
// Create ZXing image
|
||||
ZXing::ImageView imageView(grayImage.bits(), width, height, ZXing::ImageFormat::Lum);
|
||||
|
||||
// Configure reader
|
||||
ZXing::ReaderOptions options;
|
||||
options.setTryHarder(true);
|
||||
options.setTryRotate(true);
|
||||
options.setFormats(ZXing::BarcodeFormat::QRCode);
|
||||
|
||||
// Try to detect QR code
|
||||
auto result = ZXing::ReadBarcode(imageView, options);
|
||||
|
||||
if (result.isValid()) {
|
||||
QString text = QString::fromStdString(result.text());
|
||||
return text;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
qWarning() << "QR code scanning error:" << e.what();
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
39
src/app/qrcodescannermodel.h
Normal file
39
src/app/qrcodescannermodel.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (C) 2025-2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
|
||||
#include <QQmlEngine> // QML registration
|
||||
|
||||
class QRCodeScannerModel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static QRCodeScannerModel* create(QQmlEngine*, QJSEngine*)
|
||||
{
|
||||
return new QRCodeScannerModel();
|
||||
}
|
||||
|
||||
explicit QRCodeScannerModel(QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE QString scanImage(const QImage& image);
|
||||
};
|
|
@ -20,6 +20,8 @@ import QtQuick.Layouts
|
|||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
import Qt.labs.platform
|
||||
import "../../commoncomponents"
|
||||
import "../../mainview/components"
|
||||
|
||||
|
@ -32,368 +34,381 @@ BaseModalDialog {
|
|||
|
||||
property bool darkTheme: UtilsAdapter.useApplicationTheme()
|
||||
|
||||
popupContent: StackLayout {
|
||||
id: stackedWidget
|
||||
autoClose: false
|
||||
closeButtonVisible: false
|
||||
|
||||
function setGeneratingPage() {
|
||||
if (passwordEdit.length === 0 && CurrentAccount.hasArchivePassword) {
|
||||
setExportPage(NameDirectory.ExportOnRingStatus.WRONG_PASSWORD, "");
|
||||
return;
|
||||
}
|
||||
stackedWidget.currentIndex = exportingSpinnerPage.pageIndex;
|
||||
spinnerMovie.playing = true;
|
||||
// Function to check if dialog can be closed directly
|
||||
function canCloseDirectly() {
|
||||
return LinkDeviceModel.deviceAuthState === DeviceAuthStateEnum.INIT ||
|
||||
LinkDeviceModel.deviceAuthState === DeviceAuthStateEnum.DONE
|
||||
}
|
||||
|
||||
// Close button. Use custom close button to show a confirmation dialog.
|
||||
JamiPushButton {
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
topMargin: 5
|
||||
rightMargin: 5
|
||||
}
|
||||
|
||||
function setExportPage(status, pin) {
|
||||
if (status === NameDirectory.ExportOnRingStatus.SUCCESS) {
|
||||
infoLabel.success = true;
|
||||
pinRectangle.visible = true
|
||||
exportedPIN.text = pin;
|
||||
Layout.preferredHeight: 20
|
||||
Layout.preferredWidth: 20
|
||||
|
||||
imageColor: hovered ? JamiTheme.textColor : JamiTheme.buttonTintedGreyHovered
|
||||
normalColor: "transparent"
|
||||
|
||||
source: JamiResources.round_close_24dp_svg
|
||||
onClicked: {
|
||||
if (canCloseDirectly()) {
|
||||
root.close();
|
||||
} else {
|
||||
infoLabel.success = false;
|
||||
infoLabel.visible = true;
|
||||
switch (status) {
|
||||
case NameDirectory.ExportOnRingStatus.WRONG_PASSWORD:
|
||||
infoLabel.text = JamiStrings.incorrectPassword;
|
||||
break;
|
||||
case NameDirectory.ExportOnRingStatus.NETWORK_ERROR:
|
||||
infoLabel.text = JamiStrings.linkDeviceNetWorkError;
|
||||
break;
|
||||
case NameDirectory.ExportOnRingStatus.INVALID:
|
||||
infoLabel.text = JamiStrings.somethingWentWrong;
|
||||
break;
|
||||
}
|
||||
}
|
||||
stackedWidget.currentIndex = exportingInfoPage.pageIndex;
|
||||
stackedWidget.height = exportingLayout.implicitHeight;
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
if (CurrentAccount.hasArchivePassword) {
|
||||
stackedWidget.currentIndex = enterPasswordPage.pageIndex;
|
||||
} else {
|
||||
setGeneratingPage();
|
||||
}
|
||||
confirmCloseDialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Index = 0
|
||||
Item {
|
||||
id: enterPasswordPage
|
||||
MessageDialog {
|
||||
id: confirmCloseDialog
|
||||
|
||||
readonly property int pageIndex: 0
|
||||
text: JamiStrings.linkDeviceCloseWarningTitle
|
||||
informativeText: JamiStrings.linkDeviceCloseWarningMessage
|
||||
buttons: MessageDialog.Ok | MessageDialog.Cancel
|
||||
|
||||
Component.onCompleted: passwordEdit.forceActiveFocus()
|
||||
onOkClicked: function(button) {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
onHeightChanged: {
|
||||
stackedWidget.height = passwordLayout.implicitHeight
|
||||
}
|
||||
popupContent: Item {
|
||||
id: content
|
||||
width: 400
|
||||
height: 450
|
||||
|
||||
ColumnLayout {
|
||||
id: passwordLayout
|
||||
spacing: JamiTheme.preferredMarginSize
|
||||
anchors.centerIn: parent
|
||||
// Scrollable container for StackLayout
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.maximumWidth: root.width - 4 * JamiTheme.preferredMarginSize
|
||||
wrapMode: Text.Wrap
|
||||
anchors.fill: parent
|
||||
|
||||
text: JamiStrings.enterPasswordPinCode
|
||||
color: JamiTheme.textColor
|
||||
font.pointSize: JamiTheme.textFontSize
|
||||
font.kerning: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
anchors.leftMargin: 20
|
||||
anchors.rightMargin: 20
|
||||
anchors.bottomMargin: 20
|
||||
clip: true
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 10
|
||||
Layout.leftMargin: JamiTheme.cornerIconSize
|
||||
Layout.rightMargin: JamiTheme.cornerIconSize
|
||||
spacing: JamiTheme.preferredMarginSize
|
||||
Layout.bottomMargin: JamiTheme.preferredMarginSize
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
contentHeight: stackLayout.implicitHeight
|
||||
|
||||
PasswordTextEdit {
|
||||
id: passwordEdit
|
||||
StackLayout {
|
||||
id: stackLayout
|
||||
width: Math.min(scrollView.width, scrollView.availableWidth)
|
||||
|
||||
firstEntry: true
|
||||
placeholderText: JamiStrings.password
|
||||
currentIndex: scanAndEnterCodeView.index
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
Connections {
|
||||
target: LinkDeviceModel
|
||||
|
||||
KeyNavigation.up: btnConfirm
|
||||
KeyNavigation.down: KeyNavigation.up
|
||||
|
||||
onDynamicTextChanged: {
|
||||
btnConfirm.enabled = dynamicText.length > 0;
|
||||
btnConfirm.hoverEnabled = dynamicText.length > 0;
|
||||
function onDeviceAuthStateChanged() {
|
||||
switch (LinkDeviceModel.deviceAuthState) {
|
||||
case DeviceAuthStateEnum.INIT:
|
||||
stackLayout.currentIndex = scanAndEnterCodeView.index
|
||||
break
|
||||
case DeviceAuthStateEnum.CONNECTING:
|
||||
stackLayout.currentIndex = deviceLinkLoadingView.index
|
||||
deviceLinkLoadingView.loadingText = JamiStrings.linkDeviceConnecting
|
||||
break
|
||||
case DeviceAuthStateEnum.AUTHENTICATING:
|
||||
stackLayout.currentIndex = deviceConfirmationView.index
|
||||
break
|
||||
case DeviceAuthStateEnum.IN_PROGRESS:
|
||||
stackLayout.currentIndex = deviceLinkLoadingView.index
|
||||
deviceLinkLoadingView.loadingText = JamiStrings.linkDeviceInProgress
|
||||
break
|
||||
case DeviceAuthStateEnum.DONE:
|
||||
if (LinkDeviceModel.linkDeviceError.length > 0) {
|
||||
stackLayout.currentIndex = deviceLinkErrorView.index
|
||||
} else {
|
||||
stackLayout.currentIndex = deviceLinkSuccessView.index
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
onAccepted: btnConfirm.clicked()
|
||||
}
|
||||
|
||||
JamiPushButton {
|
||||
id: btnConfirm
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
height: 36
|
||||
width: 36
|
||||
|
||||
hoverEnabled: false
|
||||
enabled: false
|
||||
|
||||
imageColor: JamiTheme.secondaryBackgroundColor
|
||||
hoveredColor: JamiTheme.buttonTintedBlueHovered
|
||||
source: JamiResources.check_black_24dp_svg
|
||||
normalColor: JamiTheme.tintedBlue
|
||||
|
||||
onClicked: stackedWidget.setGeneratingPage()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Index = 1
|
||||
Item {
|
||||
id: exportingSpinnerPage
|
||||
// Common base component for stack layout items
|
||||
component StackViewBase: Item {
|
||||
id: baseItem
|
||||
|
||||
readonly property int pageIndex: 1
|
||||
required property string title
|
||||
default property alias content: contentLayout.data
|
||||
|
||||
onHeightChanged: {
|
||||
stackedWidget.height = spinnerLayout.implicitHeight
|
||||
}
|
||||
onWidthChanged: stackedWidget.width = exportingLayout.implicitWidth
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitHeight: contentLayout.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: spinnerLayout
|
||||
|
||||
spacing: JamiTheme.preferredMarginSize
|
||||
anchors.centerIn: parent
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
text: JamiStrings.linkDevice
|
||||
color: JamiTheme.textColor
|
||||
font.pointSize: JamiTheme.headerFontSize
|
||||
font.kerning: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
Layout.preferredWidth: scrollView.width
|
||||
spacing: 20
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedImage {
|
||||
id: spinnerMovie
|
||||
StackViewBase {
|
||||
id: deviceLinkErrorView
|
||||
property int index: 0
|
||||
title: "Error"
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
Layout.preferredWidth: 30
|
||||
Layout.preferredHeight: 30
|
||||
|
||||
source: JamiResources.jami_rolling_spinner_gif
|
||||
playing: visible
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Index = 2
|
||||
Item {
|
||||
id: exportingInfoPage
|
||||
|
||||
readonly property int pageIndex: 2
|
||||
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
|
||||
onHeightChanged: {
|
||||
stackedWidget.height = exportingLayout.implicitHeight
|
||||
}
|
||||
onWidthChanged: stackedWidget.width = exportingLayout.implicitWidth
|
||||
|
||||
ColumnLayout {
|
||||
id: exportingLayout
|
||||
|
||||
spacing: JamiTheme.preferredMarginSize
|
||||
|
||||
Label {
|
||||
id: instructionLabel
|
||||
|
||||
Layout.maximumWidth: Math.min(root.maximumPopupWidth, root.width) - 2 * root.popupMargins
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
color: JamiTheme.textColor
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
text: JamiStrings.linkingInstructions
|
||||
font.pointSize: JamiTheme.textFontSize
|
||||
font.kerning: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: LinkDeviceModel.linkDeviceError
|
||||
Layout.preferredWidth: scrollView.width
|
||||
color: JamiTheme.textColor
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
|
||||
MaterialButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: JamiStrings.close
|
||||
toolTipText: JamiStrings.optionTryAgain
|
||||
primary: true
|
||||
onClicked: {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Layout.maximumWidth: Math.min(root.maximumPopupWidth, root.width) - 2 * root.popupMargins
|
||||
StackViewBase {
|
||||
id: deviceLinkSuccessView
|
||||
property int index: 1
|
||||
title: "Success"
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
text: JamiStrings.linkDeviceAllSet
|
||||
color: JamiTheme.textColor
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
|
||||
radius: 5
|
||||
color: JamiTheme.backgroundRectangleColor
|
||||
width: 100
|
||||
height: 100
|
||||
MaterialButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: JamiStrings.close
|
||||
toolTipText: JamiStrings.optionTryAgain
|
||||
primary: true
|
||||
onClicked: {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: qrImage.width + 4
|
||||
height: qrImage.height + 4
|
||||
anchors.centerIn: parent
|
||||
radius: 5
|
||||
color: JamiTheme.whiteColor
|
||||
Image {
|
||||
id: qrImage
|
||||
anchors.centerIn: parent
|
||||
mipmap: false
|
||||
smooth: false
|
||||
source: "image://qrImage/raw_" + exportedPIN.text
|
||||
sourceSize.width: 80
|
||||
sourceSize.height: 80
|
||||
StackViewBase {
|
||||
id: deviceLinkLoadingView
|
||||
property int index: 2
|
||||
title: "Loading"
|
||||
property string loadingText: ""
|
||||
|
||||
BusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: 50
|
||||
Layout.preferredHeight: 50
|
||||
running: true
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
text: deviceLinkLoadingView.loadingText
|
||||
color: JamiTheme.textColor
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
opacity: 1
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
from: 1
|
||||
to: 0.3
|
||||
duration: 1000
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
from: 0.3
|
||||
to: 1
|
||||
duration: 1000
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: pinRectangle
|
||||
|
||||
radius: 5
|
||||
color: JamiTheme.backgroundRectangleColor
|
||||
Layout.fillWidth: true
|
||||
height: 100
|
||||
Layout.minimumWidth: exportedPIN.width + 20
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
MaterialLineEdit {
|
||||
id: exportedPIN
|
||||
|
||||
padding: 10
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: JamiStrings.pin
|
||||
wrapMode: Text.NoWrap
|
||||
|
||||
backgroundColor: JamiTheme.backgroundRectangleColor
|
||||
|
||||
color: darkTheme ? JamiTheme.editLineColor : JamiTheme.darkTintedBlue
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
font.pointSize: JamiTheme.tinyCreditsTextSize
|
||||
font.kerning: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
radius: 5
|
||||
color: JamiTheme.infoRectangleColor
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: infoLabels.height + 38
|
||||
StackViewBase {
|
||||
id: deviceConfirmationView
|
||||
property int index: 3
|
||||
title: "Confirmation"
|
||||
|
||||
Text {
|
||||
id: explanationConnect
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
text: JamiStrings.linkDeviceFoundAddress
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
color: JamiTheme.textColor
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
text: JamiStrings.linkDeviceNewDeviceIP.arg(LinkDeviceModel.ipAddress)
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
color: JamiTheme.textColor
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: infoLayout
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 16
|
||||
|
||||
anchors.centerIn: parent
|
||||
anchors.fill: parent
|
||||
anchors.margins: 14
|
||||
spacing: 10
|
||||
|
||||
ResponsiveImage{
|
||||
Layout.fillWidth: true
|
||||
|
||||
source: JamiResources.outline_info_24dp_svg
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
color: darkTheme ? JamiTheme.editLineColor : JamiTheme.darkTintedBlue
|
||||
Layout.fillHeight: true
|
||||
MaterialButton {
|
||||
id: confirm
|
||||
primary: true
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: JamiStrings.optionConfirm
|
||||
toolTipText: JamiStrings.optionConfirm
|
||||
onClicked: {
|
||||
LinkDeviceModel.confirmAddDevice()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout{
|
||||
id: infoLabels
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
id: otherDeviceLabel
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
color: JamiTheme.textColor
|
||||
text: JamiStrings.onAnotherDevice
|
||||
|
||||
font.pointSize: JamiTheme.smallFontSize
|
||||
font.kerning: true
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Label {
|
||||
id: otherInstructionLabel
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
color: JamiTheme.textColor
|
||||
text: JamiStrings.onAnotherDeviceInstruction
|
||||
|
||||
font.pointSize: JamiTheme.smallFontSize
|
||||
font.kerning: true
|
||||
MaterialButton {
|
||||
id: cancel
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
secondary: true
|
||||
toolTipText: JamiStrings.cancel
|
||||
textLeftPadding: JamiTheme.buttontextWizzardPadding / 2
|
||||
textRightPadding: JamiTheme.buttontextWizzardPadding / 2
|
||||
text: JamiStrings.cancel
|
||||
onClicked: {
|
||||
LinkDeviceModel.cancelAddDevice()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Displays error messages
|
||||
Label {
|
||||
id: infoLabel
|
||||
StackViewBase {
|
||||
id: scanAndEnterCodeView
|
||||
property int index: 4
|
||||
title: "Scan"
|
||||
|
||||
visible: false
|
||||
Component.onDestruction: {
|
||||
if (qrScanner) {
|
||||
qrScanner.stopScanner()
|
||||
}
|
||||
}
|
||||
|
||||
property bool success: false
|
||||
property int borderWidth: success ? 1 : 0
|
||||
property int borderRadius: success ? 15 : 0
|
||||
property string backgroundColor: success ? "whitesmoke" : "transparent"
|
||||
property string borderColor: success ? "lightgray" : "transparent"
|
||||
Text {
|
||||
id: explanationScan
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
text: JamiStrings.linkDeviceScanQR
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
color: JamiTheme.textColor
|
||||
}
|
||||
|
||||
Layout.maximumWidth: JamiTheme.preferredDialogWidth
|
||||
Layout.margins: JamiTheme.preferredMarginSize
|
||||
QRCodeScanner {
|
||||
id: qrScanner
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
width: 250
|
||||
height: width * aspectRatio
|
||||
visible: VideoDevices.listSize !== 0
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
onQrCodeDetected: function(code) {
|
||||
console.log("QR code detected:", code)
|
||||
LinkDeviceModel.addDevice(code)
|
||||
}
|
||||
}
|
||||
|
||||
color: success ? JamiTheme.successLabelColor : JamiTheme.redColor
|
||||
padding: success ? 8 : 0
|
||||
ColumnLayout {
|
||||
id: manualEntry
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
spacing: 10
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
font.pointSize: success ? JamiTheme.textFontSize : JamiTheme.textFontSize + 3
|
||||
font.kerning: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Text {
|
||||
id: explanation
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
text: JamiStrings.linkDeviceEnterManually
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
lineHeight: JamiTheme.wizardViewTextLineHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
color: JamiTheme.textColor
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
id: infoLabelBackground
|
||||
ModalTextEdit {
|
||||
id: codeInput
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: scrollView.width
|
||||
Layout.preferredHeight: JamiTheme.preferredFieldHeight
|
||||
placeholderText: JamiStrings.linkDeviceEnterCodePlaceholder
|
||||
}
|
||||
|
||||
border.width: infoLabel.borderWidth
|
||||
border.color: infoLabel.borderColor
|
||||
radius: infoLabel.borderRadius
|
||||
color: JamiTheme.secondaryBackgroundColor
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: parent.width - 40
|
||||
visible: LinkDeviceModel.tokenErrorMessage.length > 0
|
||||
text: LinkDeviceModel.tokenErrorMessage
|
||||
font.pointSize: JamiTheme.tinyFontSize
|
||||
color: JamiTheme.redColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
}
|
||||
|
||||
MaterialButton {
|
||||
id: connect
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
primary: true
|
||||
text: JamiStrings.connect
|
||||
toolTipText: JamiStrings.connect
|
||||
enabled: codeInput.dynamicText.length > 0
|
||||
onClicked: {
|
||||
LinkDeviceModel.addDevice(codeInput.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Reset everything when dialog is closed
|
||||
onClosed: {
|
||||
LinkDeviceModel.reset()
|
||||
}
|
||||
}
|
||||
|
|
134
src/app/settingsview/components/QRCodeScanner.qml
Normal file
134
src/app/settingsview/components/QRCodeScanner.qml
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright (C) 2025-2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Helpers 1.1
|
||||
import "../../commoncomponents"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool isScanning: false
|
||||
property real aspectRatio: 0.5625
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
startScanner()
|
||||
} else {
|
||||
stopScanner()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
stopScanner()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: cameraContainer
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
color: JamiTheme.primaryForegroundColor
|
||||
clip: true
|
||||
|
||||
LocalVideo {
|
||||
id: previewWidget
|
||||
anchors.fill: parent
|
||||
flip: true
|
||||
|
||||
// Camera not available
|
||||
underlayItems: Text {
|
||||
id: noCameraText
|
||||
anchors.centerIn: parent
|
||||
font.pointSize: 18
|
||||
font.capitalization: Font.AllUppercase
|
||||
color: "white"
|
||||
text: JamiStrings.noCamera
|
||||
visible: false // Start hidden
|
||||
|
||||
// Delay "No Camera" message to avoid flashing it when camera is starting up.
|
||||
// If camera starts successfully within 5 seconds, user won't see this message.
|
||||
// If there's a camera issue, message will be shown after the delay.
|
||||
Timer {
|
||||
id: visibilityTimer
|
||||
interval: 5000
|
||||
running: true
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
noCameraText.visible = true
|
||||
destroy() // Remove the timer after it's done
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scanning line animation
|
||||
Rectangle {
|
||||
id: scanLine
|
||||
width: parent.width
|
||||
height: 2
|
||||
color: JamiTheme.whiteColor
|
||||
opacity: 0.8
|
||||
visible: root.isScanning && previewWidget.isRendering
|
||||
|
||||
SequentialAnimation on y {
|
||||
running: root.isScanning
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
from: 0
|
||||
to: cameraContainer.height
|
||||
duration: 2500
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
from: cameraContainer.height
|
||||
to: 0
|
||||
duration: 2500
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: scanTimer
|
||||
interval: 500
|
||||
repeat: true
|
||||
running: root.isScanning && previewWidget.isRendering
|
||||
onTriggered: {
|
||||
var result = QRCodeScannerModel.scanImage(videoProvider.captureRawVideoFrame(VideoDevices.getDefaultDevice()));
|
||||
if (result !== "") {
|
||||
root.isScanning = false
|
||||
root.qrCodeDetected(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signal qrCodeDetected(string code)
|
||||
|
||||
function startScanner() {
|
||||
previewWidget.startWithId(VideoDevices.getDefaultDevice())
|
||||
root.isScanning = true
|
||||
}
|
||||
|
||||
function stopScanner() {
|
||||
previewWidget.startWithId("")
|
||||
root.isScanning = true
|
||||
}
|
||||
}
|
|
@ -17,11 +17,11 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Dialogs
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
import Qt.labs.platform
|
||||
import "../../commoncomponents"
|
||||
import "../../mainview/components"
|
||||
|
||||
|
@ -77,11 +77,9 @@ Rectangle {
|
|||
informativeText: JamiStrings.linkDeviceCloseWarningMessage
|
||||
buttons: MessageDialog.Ok | MessageDialog.Cancel
|
||||
|
||||
onButtonClicked: function(button) {
|
||||
if (button === MessageDialog.Ok) {
|
||||
AccountAdapter.cancelImportAccount();
|
||||
WizardViewStepModel.previousStep();
|
||||
}
|
||||
onOkClicked: function(button) {
|
||||
AccountAdapter.cancelImportAccount();
|
||||
WizardViewStepModel.previousStep();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -126,6 +126,18 @@ public Q_SLOTS:
|
|||
int state,
|
||||
const MapStringString& details);
|
||||
|
||||
/**
|
||||
* Emit addDeviceStateChanged.
|
||||
* @param accountId
|
||||
* @param operationId
|
||||
* @param state
|
||||
* @param details
|
||||
*/
|
||||
void slotAddDeviceStateChanged(const QString& accountID,
|
||||
uint32_t operationId,
|
||||
int state,
|
||||
const MapStringString& details);
|
||||
|
||||
/**
|
||||
* @param accountId
|
||||
* @param details
|
||||
|
@ -430,6 +442,10 @@ AccountModelPimpl::AccountModelPimpl(AccountModel& linked,
|
|||
&CallbacksHandler::deviceAuthStateChanged,
|
||||
&linked,
|
||||
&AccountModel::deviceAuthStateChanged);
|
||||
connect(&callbacksHandler,
|
||||
&CallbacksHandler::addDeviceStateChanged,
|
||||
&linked,
|
||||
&AccountModel::addDeviceStateChanged);
|
||||
connect(&callbacksHandler,
|
||||
&CallbacksHandler::nameRegistrationEnded,
|
||||
this,
|
||||
|
@ -627,6 +643,15 @@ AccountModelPimpl::slotDeviceAuthStateChanged(const QString& accountId,
|
|||
Q_EMIT linked.deviceAuthStateChanged(accountId, state, details);
|
||||
}
|
||||
|
||||
void
|
||||
AccountModelPimpl::slotAddDeviceStateChanged(const QString& accountId,
|
||||
uint32_t operationId,
|
||||
int state,
|
||||
const MapStringString& details)
|
||||
{
|
||||
Q_EMIT linked.addDeviceStateChanged(accountId, operationId, state, details);
|
||||
}
|
||||
|
||||
void
|
||||
AccountModelPimpl::slotNameRegistrationEnded(const QString& accountId,
|
||||
int status,
|
||||
|
|
|
@ -247,6 +247,12 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
|
|||
&CallbacksHandler::slotDeviceAuthStateChanged,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(&ConfigurationManager::instance(),
|
||||
&ConfigurationManagerInterface::addDeviceStateChanged,
|
||||
this,
|
||||
&CallbacksHandler::slotAddDeviceStateChanged,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(&ConfigurationManager::instance(),
|
||||
&ConfigurationManagerInterface::nameRegistrationEnded,
|
||||
this,
|
||||
|
@ -687,6 +693,15 @@ CallbacksHandler::slotDeviceAuthStateChanged(const QString& accountId,
|
|||
Q_EMIT deviceAuthStateChanged(accountId, state, details);
|
||||
}
|
||||
|
||||
void
|
||||
CallbacksHandler::slotAddDeviceStateChanged(const QString& accountId,
|
||||
uint32_t operationId,
|
||||
int state,
|
||||
const MapStringString& details)
|
||||
{
|
||||
Q_EMIT addDeviceStateChanged(accountId, operationId, state, details);
|
||||
}
|
||||
|
||||
void
|
||||
CallbacksHandler::slotNameRegistrationEnded(const QString& accountId,
|
||||
int status,
|
||||
|
|
|
@ -244,6 +244,19 @@ Q_SIGNALS:
|
|||
*/
|
||||
void deviceAuthStateChanged(const QString& accountId, int state, const MapStringString& details);
|
||||
|
||||
/**
|
||||
* Add device state has changed
|
||||
* @param accountId
|
||||
* @param operationId
|
||||
* @param state
|
||||
* @param details map
|
||||
*/
|
||||
|
||||
void addDeviceStateChanged(const QString& accountId,
|
||||
uint32_t operationId,
|
||||
int state,
|
||||
const MapStringString& details);
|
||||
|
||||
/**
|
||||
* Name registration has ended
|
||||
* @param accountId
|
||||
|
@ -587,6 +600,18 @@ private Q_SLOTS:
|
|||
int state,
|
||||
const MapStringString& details);
|
||||
|
||||
/**
|
||||
* Add device state has changed
|
||||
* @param accountId
|
||||
* @param operationId
|
||||
* @param state
|
||||
* @param details map
|
||||
*/
|
||||
void slotAddDeviceStateChanged(const QString& accountId,
|
||||
uint32_t operationId,
|
||||
int state,
|
||||
const MapStringString& details);
|
||||
|
||||
/**
|
||||
* Emit nameRegistrationEnded
|
||||
* @param accountId
|
||||
|
|
Loading…
Add table
Reference in a new issue