1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-03-28 14:56:19 +01:00

account: implement import-from-device using new API

- Implements new APIs
- Implements import-from-device mechanism (creation wizard)
- Minor refactoring of accountmodel and accountadapter

Gitlab: #1695
Change-Id: Ib3c6301b82b19a25320dd703f2f7e941f8048a8e
This commit is contained in:
Kateryna Kostiuk 2025-02-24 09:46:30 -05:00 committed by Andreas Traczyk
parent 82c876c0fa
commit 33da15daba
25 changed files with 820 additions and 347 deletions

1
.gitignore vendored
View file

@ -7,6 +7,7 @@ doc/Doxyfile
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
### VisualStudioCode Patch ###
# Ignore all local history of files

2
daemon

@ -1 +1 @@
Subproject commit 597cde8d30814b5078e2ac8c8a0953dd471ec716
Subproject commit 86d3bb664489077107e68b838e419f4cd6459859

View file

@ -22,8 +22,11 @@
#include "systemtray.h"
#include "lrcinstance.h"
#include "accountlistmodel.h"
#include "wizardviewstepmodel.h"
#include "global.h"
#include "api/account.h"
#include <QtConcurrent/QtConcurrent>
#include <QThreadPool>
AccountAdapter::AccountAdapter(AppSettingsManager* settingsManager,
SystemTray* systemTray,
@ -111,7 +114,10 @@ AccountAdapter::createJamiAccount(const QVariantMap& settings)
&lrcInstance_->accountModel(),
&lrc::api::AccountModel::accountAdded,
[this, registeredName, settings](const QString& accountId) {
lrcInstance_->accountModel().setAvatar(accountId, settings["avatar"].toString(), true,1);
lrcInstance_->accountModel().setAvatar(accountId,
settings["avatar"].toString(),
true,
1);
Utils::oneShotConnect(&lrcInstance_->accountModel(),
&lrc::api::AccountModel::accountDetailsChanged,
[this](const QString& accountId) {
@ -159,8 +165,9 @@ AccountAdapter::createJamiAccount(const QVariantMap& settings)
connectFailure();
auto futureResult = QtConcurrent::run([this, settings] {
QThreadPool::globalInstance()->start([this, settings] {
lrcInstance_->accountModel().createNewAccount(lrc::api::profile::Type::JAMI,
{},
settings["alias"].toString(),
settings["archivePath"].toString(),
settings["password"].toString(),
@ -206,14 +213,14 @@ AccountAdapter::createSIPAccount(const QVariantMap& settings)
connectFailure();
auto futureResult = QtConcurrent::run([this, settings] {
QThreadPool::globalInstance()->start([this, settings] {
lrcInstance_->accountModel().createNewAccount(lrc::api::profile::Type::SIP,
{},
settings["alias"].toString(),
settings["archivePath"].toString(),
"",
"",
settings["username"].toString(),
{});
settings["username"].toString());
});
}
@ -250,7 +257,7 @@ AccountAdapter::createJAMSAccount(const QVariantMap& settings)
connectFailure();
auto futureResult = QtConcurrent::run([this, settings] {
QThreadPool::globalInstance()->start([this, settings] {
lrcInstance_->accountModel().connectToAccountManager(settings["username"].toString(),
settings["password"].toString(),
settings["manager"].toString());
@ -293,7 +300,7 @@ AccountAdapter::setCurrAccDisplayName(const QString& text)
void
AccountAdapter::setCurrentAccountAvatarFile(const QString& source)
{
auto futureResult = QtConcurrent::run([this, source]() {
QThreadPool::globalInstance()->start([this, source]() {
QPixmap image;
if (!image.load(source)) {
qWarning() << "Not a valid image file";
@ -308,7 +315,7 @@ AccountAdapter::setCurrentAccountAvatarFile(const QString& source)
void
AccountAdapter::setCurrentAccountAvatarBase64(const QString& data)
{
auto futureResult = QtConcurrent::run([this, data]() {
QThreadPool::globalInstance()->start([this, data]() {
auto accountId = lrcInstance_->get_currentAccountId();
lrcInstance_->accountModel().setAvatar(accountId, data, true, 1);
});
@ -339,9 +346,73 @@ AccountAdapter::exportToFile(const QString& accountId,
void
AccountAdapter::setArchivePasswordAsync(const QString& accountID, const QString& password)
{
auto futureResult = QtConcurrent::run([this, accountID, password] {
QThreadPool::globalInstance()->start([this, accountID, password] {
auto config = lrcInstance_->accountModel().getAccountConfig(accountID);
config.archivePassword = password;
lrcInstance_->accountModel().setAccountConfig(accountID, config);
});
}
void
AccountAdapter::startImportAccount()
{
auto wizardModel = qApp->property("WizardViewStepModel").value<WizardViewStepModel*>();
wizardModel->set_deviceAuthState(lrc::api::account::DeviceAuthState::INIT);
wizardModel->set_deviceLinkDetails({});
// This will create an account with the ARCHIVE_URL configured to start the import process.
importAccountId_ = lrcInstance_->accountModel().createDeviceImportAccount();
}
void
AccountAdapter::provideAccountAuthentication(const QString& password)
{
if (importAccountId_.isEmpty()) {
qWarning() << "No import account to provide password to";
return;
}
auto wizardModel = qApp->property("WizardViewStepModel").value<WizardViewStepModel*>();
wizardModel->set_deviceAuthState(lrc::api::account::DeviceAuthState::IN_PROGRESS);
Utils::oneShotConnect(
&lrcInstance_->accountModel(),
&lrc::api::AccountModel::accountAdded,
[this](const QString& accountId) {
Q_EMIT lrcInstance_->accountListChanged();
Q_EMIT accountAdded(accountId,
lrcInstance_->accountModel().getAccountList().indexOf(accountId));
},
this,
&AccountAdapter::accountCreationFailed);
connectFailure();
QThreadPool::globalInstance()->start([this, password] {
lrcInstance_->accountModel().provideAccountAuthentication(importAccountId_, password);
});
}
QString
AccountAdapter::getImportErrorMessage(QVariantMap details)
{
QString errorString = details.value("error").toString();
if (!errorString.isEmpty() && errorString != "none") {
auto error = lrc::api::account::mapLinkDeviceError(errorString.toStdString());
return lrc::api::account::getLinkDeviceString(error);
}
return "";
}
void
AccountAdapter::cancelImportAccount()
{
auto wizardModel = qApp->property("WizardViewStepModel").value<WizardViewStepModel*>();
wizardModel->set_deviceAuthState(lrc::api::account::DeviceAuthState::INIT);
wizardModel->set_deviceLinkDetails({});
// Remove the account if it was created
lrcInstance_->accountModel().removeAccount(importAccountId_);
importAccountId_.clear();
}

View file

@ -81,6 +81,13 @@ public:
const bool& state);
Q_INVOKABLE QStringList getDefaultModerators(const QString& accountId);
// New import account / link device functions
// import: (note: Listen for: DeviceAuthStateChanged)
Q_INVOKABLE void startImportAccount();
Q_INVOKABLE void provideAccountAuthentication(const QString& password = {});
Q_INVOKABLE QString getImportErrorMessage(QVariantMap details);
Q_INVOKABLE void cancelImportAccount();
Q_SIGNALS:
// Trigger other components to reconnect account related signals.
void accountStatusChanged(QString accountId);
@ -98,6 +105,9 @@ private:
QMetaObject::Connection registeredNameSavedConnection_;
// The account ID of the last used import account.
QString importAccountId_;
AppSettingsManager* settingsManager_;
SystemTray* systemTray_;
};

View file

@ -22,6 +22,7 @@
#include "lrcinstance.h"
#include <QImage>
#include <QRegularExpression>
class AsyncAvatarImageResponseRunnable : public AsyncImageResponseRunnable
{
@ -69,6 +70,16 @@ public:
image = Utils::accountPhoto(lrcInstance_, imageId, requestedSize_);
} else if (type == "contact") {
image = Utils::contactPhoto(lrcInstance_, imageId, requestedSize_);
} else if (type == "temporaryAccount") {
// Check if imageId is a SHA-1 hash (jamiId or registered name)
static const QRegularExpression sha1Pattern("^[0-9a-fA-F]{40}$");
if (sha1Pattern.match(imageId).hasMatch()) {
// If we only have a jamiId use default avatar
image = Utils::fallbackAvatar("jami:" + imageId, QString(), requestedSize_);
} else {
// For registered usernames, use fallbackAvatar avatar with the name
image = Utils::fallbackAvatar(QString(), imageId, requestedSize_);
}
} else {
qWarning() << Q_FUNC_INFO << "Missing valid prefix in the image url";
return;

View file

@ -28,7 +28,8 @@ Item {
enum Mode {
Account,
Contact,
Conversation
Conversation,
TemporaryAccount
}
property int mode: Avatar.Mode.Account
property alias sourceSize: image.sourceSize
@ -45,6 +46,8 @@ Item {
return 'contact';
case Avatar.Mode.Conversation:
return 'conversation';
case Avatar.Mode.TemporaryAccount:
return 'temporaryAccount';
}
}

View file

@ -70,6 +70,21 @@ Item {
property string transferThisCall: qsTr("Transfer this call")
property string transferTo: qsTr("Transfer to")
// Device import/linking
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 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.")
property string optionConfirm: qsTr("Confirm")
property string optionTryAgain: qsTr("Try again")
property string importFailed: qsTr("Import failed")
property string importFromAnotherAccount: qsTr("Import from another account")
property string connectToAccount: qsTr("Connect to account")
property string authenticationError: qsTr("An authentication error occurred. Please check credentials and try again.")
// AccountMigrationDialog
property string authenticationRequired: qsTr("Authentication required")
property string migrationReason: qsTr("Your session has expired or been revoked on this device. Please enter your password.")
@ -579,19 +594,8 @@ Item {
// ImportFromDevicePage
property string importButton: qsTr("Import")
property string pin: qsTr("Enter the PIN code")
property string importFromDeviceDescription: qsTr("A PIN code is required to use an existing Jami account on this device.")
property string importStep1: qsTr("Step 1")
property string importStep2: qsTr("Step 2")
property string importStep3: qsTr("Step 3")
property string importStep4: qsTr("Step 4")
property string importStep1Desc: qsTr("Open the manage account tab in the settings of the previous device.")
property string importStep2Desc: qsTr("Select the account to link.")
property string importStep3Desc: qsTr("Select “Link new device.”")
property string importStep4Desc: qsTr("The PIN code will expire in 10 minutes.")
property string importPasswordDesc: qsTr("Fill if the account is password-encrypted.")
// LinkDevicesDialog
property string pinTimerInfos: qsTr("The PIN code and the account password should be entered in the device within 10 minutes.")
property string close: qsTr("Close")
property string enterAccountPassword: qsTr("Enter account password")
property string enterPasswordPinCode: qsTr("This account is password encrypted, enter the password to generate a PIN code.")

View file

@ -179,6 +179,12 @@ registerTypes(QQmlEngine* engine,
QQmlEngine::setObjectOwnership(pluginStoreListModel, QQmlEngine::CppOwnership);
REG_QML_SINGLETON<PluginStoreListModel>(REG_MODEL, "PluginStoreListModel", CREATE(pluginStoreListModel));
// WizardViewStepModel
auto wizardViewStepModel = new WizardViewStepModel(lrcInstance, settingsManager, app);
qApp->setProperty("WizardViewStepModel", QVariant::fromValue(wizardViewStepModel));
QQmlEngine::setObjectOwnership(wizardViewStepModel, QQmlEngine::CppOwnership);
REG_QML_SINGLETON<WizardViewStepModel>(REG_MODEL, "WizardViewStepModel", CREATE(wizardViewStepModel));
// 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.
@ -205,7 +211,6 @@ registerTypes(QQmlEngine* engine,
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, TipsModel);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, VideoDevices);
QML_REGISTERSINGLETON_TYPE(NS_ADAPTERS, CurrentAccountToMigrate);
QML_REGISTERSINGLETON_TYPE(NS_MODELS, WizardViewStepModel);
QML_REGISTERSINGLETON_TYPE(NS_HELPERS, ImageDownloader);
// TODO: remove these
@ -263,12 +268,12 @@ registerTypes(QQmlEngine* engine,
// Enums
QML_REGISTERUNCREATABLE(NS_ENUMS, Settings)
QML_REGISTERUNCREATABLE(NS_ENUMS, NetworkManager)
QML_REGISTERUNCREATABLE(NS_ENUMS, WizardViewStepModel)
QML_REGISTERUNCREATABLE(NS_ENUMS, DeviceItemListModel)
QML_REGISTERUNCREATABLE(NS_ENUMS, ModeratorListModel)
QML_REGISTERUNCREATABLE(NS_ENUMS, VideoInputDeviceModel)
QML_REGISTERUNCREATABLE(NS_ENUMS, VideoFormatResolutionModel)
QML_REGISTERUNCREATABLE(NS_ENUMS, VideoFormatFpsModel)
QML_REGISTERUNCREATABLE(NS_ENUMS, DeviceAuthStateEnum)
engine->addImageProvider(QLatin1String("qrImage"), new QrImageProvider(lrcInstance));
engine->addImageProvider(QLatin1String("avatarimage"), new AvatarImageProvider(lrcInstance));

View file

@ -18,7 +18,6 @@
#pragma once
#include "quickimageproviderbase.h"
#include "accountlistmodel.h"
#include <QPair>
#include <QString>

View file

@ -42,7 +42,6 @@ BaseModalDialog {
}
stackedWidget.currentIndex = exportingSpinnerPage.pageIndex;
spinnerMovie.playing = true;
timerForExport.restart();
}
function setExportPage(status, pin) {
@ -69,25 +68,6 @@ BaseModalDialog {
stackedWidget.height = exportingLayout.implicitHeight;
}
Timer {
id: timerForExport
repeat: false
interval: 200
onTriggered: {
AccountAdapter.model.exportOnRing(LRCInstance.currentAccountId, passwordEdit.dynamicText);
}
}
Connections {
target: NameDirectory
function onExportOnRingEnded(status, pin) {
stackedWidget.setExportPage(status, pin);
}
}
onVisibleChanged: {
if (visible) {
if (CurrentAccount.hasArchivePassword) {

View file

@ -165,8 +165,7 @@ Utils::CreateStartupLink(const std::wstring& wstrAppName)
#endif
if (desktopPath.isEmpty() || !(QFile::exists(desktopPath))) {
qDebug() << "Error while attempting to locate .desktop file at"
<< desktopPath;
qDebug() << "Error while attempting to locate .desktop file at" << desktopPath;
return false;
}
@ -193,8 +192,7 @@ Utils::CreateStartupLink(const std::wstring& wstrAppName)
if (QDir().mkdir(autoStartDir)) {
qDebug() << "Created autostart directory:" << autoStartDir;
} else {
qWarning() << "Error while creating autostart directory:"
<< autoStartDir;
qWarning() << "Error while creating autostart directory:" << autoStartDir;
return false;
}
}
@ -283,7 +281,8 @@ Utils::CheckStartupLink(const std::wstring& wstrAppName)
#else
Q_UNUSED(wstrAppName)
return (
!QStandardPaths::locate(QStandardPaths::ConfigLocation, "autostart/net.jami.Jami.desktop").isEmpty());
!QStandardPaths::locate(QStandardPaths::ConfigLocation, "autostart/net.jami.Jami.desktop")
.isEmpty());
#endif
}
@ -616,14 +615,16 @@ Utils::getProjectCredits()
return {};
}
QTextStream in(&projectCreditsFile);
return in.readAll().arg(
QObject::tr("We would like to thank our contributors, whose efforts over many years have made this software what it is."),
QObject::tr("Developers"),
QObject::tr("Media"),
QObject::tr("Community Management"),
QObject::tr("Special thanks to"),
QObject::tr("This is a list of people who have made a significant investment of time, with useful results, into Jami. Any such contributors who want to be added to the list should contact us.")
);
return in.readAll().arg(QObject::tr("We would like to thank our contributors, whose efforts "
"over many years have made this software what it is."),
QObject::tr("Developers"),
QObject::tr("Media"),
QObject::tr("Community Management"),
QObject::tr("Special thanks to"),
QObject::tr(
"This is a list of people who have made a significant investment "
"of time, with useful results, into Jami. Any such contributors "
"who want to be added to the list should contact us."));
}
inline QString
@ -951,3 +952,13 @@ Utils::getTempSwarmAvatarPath()
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QDir::separator()
+ "tmpSwarmImage";
}
QVariantMap
Utils::mapStringStringToVariantMap(const MapStringString& map)
{
QVariantMap variantMap;
for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
variantMap.insert(it.key(), it.value());
}
return variantMap;
}

View file

@ -120,4 +120,7 @@ QString generateUid();
QString humanFileSize(qint64 fileSize);
QString getDebugFilePath();
// Convert a MapStringString to a QVariantMap
QVariantMap mapStringStringToVariantMap(const MapStringString& map);
} // namespace Utils

View file

@ -56,9 +56,11 @@ BaseView {
case WizardViewStepModel.AccountCreationOption.CreateJamiAccount:
case WizardViewStepModel.AccountCreationOption.CreateRendezVous:
case WizardViewStepModel.AccountCreationOption.ImportFromBackup:
case WizardViewStepModel.AccountCreationOption.ImportFromDevice:
AccountAdapter.createJamiAccount(WizardViewStepModel.accountCreationInfo);
break;
case WizardViewStepModel.AccountCreationOption.ImportFromDevice:
AccountAdapter.startImportAccount();
break;
case WizardViewStepModel.AccountCreationOption.ConnectToAccountManager:
AccountAdapter.createJAMSAccount(WizardViewStepModel.accountCreationInfo);
break;

View file

@ -17,9 +17,13 @@
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 "../../commoncomponents"
import "../../mainview/components"
Rectangle {
id: root
@ -27,30 +31,99 @@ Rectangle {
property string errorText: ""
property int preferredHeight: importFromDevicePageColumnLayout.implicitHeight + 2 * JamiTheme.preferredMarginSize
signal showThisPage
// The token is used to generate the QR code and is also provided to the user as a backup if the QR
// code cannot be scanned. It is a URI using the scheme "jami-auth".
readonly property string tokenUri: WizardViewStepModel.deviceLinkDetails["token"] || ""
function initializeOnShowUp() {
clearAllTextFields();
property string jamiId: ""
function isPasswordWrong() {
return WizardViewStepModel.deviceLinkDetails["auth_error"] !== undefined &&
WizardViewStepModel.deviceLinkDetails["auth_error"] !== "" &&
WizardViewStepModel.deviceLinkDetails["auth_error"] !== "none"
}
function requiresPassword() {
return WizardViewStepModel.deviceLinkDetails["auth_scheme"] === "password"
}
function requiresConfirmationBeforeClosing() {
const state = WizardViewStepModel.deviceAuthState
return state !== DeviceAuthStateEnum.INIT &&
state !== DeviceAuthStateEnum.DONE
}
function isLoadingState() {
const state = WizardViewStepModel.deviceAuthState
return state === DeviceAuthStateEnum.INIT ||
state === DeviceAuthStateEnum.CONNECTING ||
state === DeviceAuthStateEnum.IN_PROGRESS
}
signal showThisPage
function clearAllTextFields() {
connectBtn.spinnerTriggered = false;
errorText = "";
}
function errorOccurred(errorMessage) {
errorText = errorMessage;
connectBtn.spinnerTriggered = false;
}
MessageDialog {
id: confirmCloseDialog
text: JamiStrings.linkDeviceCloseWarningTitle
informativeText: JamiStrings.linkDeviceCloseWarningMessage
buttons: MessageDialog.Ok | MessageDialog.Cancel
onButtonClicked: function(button) {
if (button === MessageDialog.Ok) {
AccountAdapter.cancelImportAccount();
WizardViewStepModel.previousStep();
}
}
}
Connections {
target: WizardViewStepModel
function onMainStepChanged() {
if (WizardViewStepModel.mainStep === WizardViewStepModel.MainSteps.AccountCreation && WizardViewStepModel.accountCreationOption === WizardViewStepModel.AccountCreationOption.ImportFromDevice) {
if (WizardViewStepModel.mainStep === WizardViewStepModel.MainSteps.DeviceAuthorization) {
clearAllTextFields();
root.showThisPage();
}
}
function onDeviceAuthStateChanged() {
switch (WizardViewStepModel.deviceAuthState) {
case DeviceAuthStateEnum.TOKEN_AVAILABLE:
// Token is available and displayed as QR code
clearAllTextFields();
break;
case DeviceAuthStateEnum.CONNECTING:
// P2P connection being established
clearAllTextFields();
break;
case DeviceAuthStateEnum.AUTHENTICATING:
jamiId = WizardViewStepModel.deviceLinkDetails["peer_id"] || "";
if (jamiId.length > 0) {
NameDirectory.lookupAddress(CurrentAccount.id, jamiId)
}
break;
case DeviceAuthStateEnum.IN_PROGRESS:
// Account archive is being transferred
clearAllTextFields();
break;
case DeviceAuthStateEnum.DONE:
// Final state - check for specific errors
const error = AccountAdapter.getImportErrorMessage(WizardViewStepModel.deviceLinkDetails);
if (error.length > 0) {
errorOccurred(error)
}
break;
}
}
}
color: JamiTheme.secondaryBackgroundColor
@ -65,184 +138,276 @@ Rectangle {
width: Math.max(508, root.width - 100)
Text {
text: JamiStrings.importAccountFromAnotherDevice
text: JamiStrings.importFromAnotherAccount
Layout.alignment: Qt.AlignCenter
Layout.topMargin: JamiTheme.preferredMarginSize
Layout.preferredWidth: Math.min(360, root.width - JamiTheme.preferredMarginSize * 2)
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: JamiTheme.textColor
color: JamiTheme.textColor
font.pixelSize: JamiTheme.wizardViewTitleFontPixelSize
wrapMode: Text.WordWrap
}
Text {
text: JamiStrings.importFromDeviceDescription
Layout.preferredWidth: Math.min(360, root.width - JamiTheme.preferredMarginSize * 2)
Layout.topMargin: JamiTheme.wizardViewDescriptionMarginSize
Layout.alignment: Qt.AlignCenter
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
font.weight: Font.Medium
color: JamiTheme.textColor
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
lineHeight: JamiTheme.wizardViewTextLineHeight
}
Flow {
spacing: 30
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: JamiTheme.wizardViewBlocMarginSize
Layout.preferredWidth: Math.min(step1.width * 2 + spacing, root.width - JamiTheme.preferredMarginSize * 2)
InfoBox {
id: step1
icoSource: JamiResources.settings_24dp_svg
title: JamiStrings.importStep1
description: JamiStrings.importStep1Desc
icoColor: JamiTheme.buttonTintedBlue
}
InfoBox {
id: step2
icoSource: JamiResources.person_24dp_svg
title: JamiStrings.importStep2
description: JamiStrings.importStep2Desc
icoColor: JamiTheme.buttonTintedBlue
}
InfoBox {
id: step3
icoSource: JamiResources.finger_select_svg
title: JamiStrings.importStep3
description: JamiStrings.importStep3Desc
icoColor: JamiTheme.buttonTintedBlue
}
InfoBox {
id: step4
icoSource: JamiResources.time_clock_svg
title: JamiStrings.importStep4
description: JamiStrings.importStep4Desc
icoColor: JamiTheme.buttonTintedBlue
}
}
ModalTextEdit {
id: pinFromDevice
objectName: "pinFromDevice"
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: Math.min(410, root.width - JamiTheme.preferredMarginSize * 2)
Layout.topMargin: JamiTheme.wizardViewBlocMarginSize
focus: visible
placeholderText: JamiStrings.pin
staticText: ""
KeyNavigation.up: backButton
KeyNavigation.down: passwordFromDevice
KeyNavigation.tab: KeyNavigation.down
onAccepted: passwordFromDevice.forceActiveFocus()
}
Text {
Layout.alignment: Qt.AlignCenter
Layout.topMargin: JamiTheme.wizardViewBlocMarginSize
color: JamiTheme.textColor
wrapMode: Text.WordWrap
text: JamiStrings.importPasswordDesc
Layout.maximumWidth: parent.width
horizontalAlignment: Text.AlignHCenter
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
font.weight: Font.Medium
lineHeight: JamiTheme.wizardViewTextLineHeight
text: {
switch (WizardViewStepModel.deviceAuthState) {
case DeviceAuthStateEnum.INIT:
return JamiStrings.waitingForToken;
case DeviceAuthStateEnum.TOKEN_AVAILABLE:
return JamiStrings.scanToImportAccount;
case DeviceAuthStateEnum.CONNECTING:
return JamiStrings.connectingToDevice;
case DeviceAuthStateEnum.AUTHENTICATING:
return JamiStrings.confirmAccountImport;
case DeviceAuthStateEnum.IN_PROGRESS:
return JamiStrings.transferringAccount;
case DeviceAuthStateEnum.DONE:
return errorText.length > 0 ? JamiStrings.importFailed : "";
default:
return "";
}
}
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
color: JamiTheme.textColor
}
PasswordTextEdit {
id: passwordFromDevice
// Confirmation form
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: Math.min(parent.width - 40, 400)
visible: WizardViewStepModel.deviceAuthState === DeviceAuthStateEnum.AUTHENTICATING
spacing: JamiTheme.wizardViewPageLayoutSpacing
objectName: "passwordFromDevice"
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: Math.min(410, root.width - JamiTheme.preferredMarginSize * 2)
Layout.topMargin: JamiTheme.wizardViewMarginSize
placeholderText: JamiStrings.enterPassword
KeyNavigation.up: pinFromDevice
KeyNavigation.down: {
if (connectBtn.enabled)
return connectBtn;
else if (connectBtn.spinnerTriggered)
return passwordFromDevice;
return backButton;
}
KeyNavigation.tab: KeyNavigation.down
onAccepted: pinFromDevice.forceActiveFocus()
}
SpinnerButton {
id: connectBtn
TextMetrics {
id: textSize
font.weight: Font.Bold
font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
text: connectBtn.normalText
Text {
Layout.fillWidth: true
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
lineHeight: JamiTheme.wizardViewTextLineHeight
text: JamiStrings.connectToAccount
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
horizontalAlignment: Text.AlignHCenter
color: JamiTheme.textColor
font.bold: true
}
objectName: "importFromDevicePageConnectBtn"
// Account Widget (avatar + username + ID)
Rectangle {
id: accountContainer
Layout.alignment: Qt.AlignHCenter
implicitWidth: accountLayout.implicitWidth + 40
implicitHeight: accountLayout.implicitHeight + 40
radius: 8
color: JamiTheme.primaryBackgroundColor
border.width: 1
border.color: JamiTheme.tabbarBorderColor
Layout.alignment: Qt.AlignCenter
Layout.topMargin: JamiTheme.wizardViewBlocMarginSize
Layout.bottomMargin: errorLabel.visible ? 0 : JamiTheme.wizardViewPageBackButtonMargins
RowLayout {
id: accountLayout
anchors {
centerIn: parent
}
spacing: 20
preferredWidth: textSize.width + 2 * JamiTheme.buttontextWizzardPadding + 1
primary: true
Avatar {
id: accountAvatar
showPresenceIndicator: false
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: 48
Layout.preferredHeight: 48
mode: Avatar.Mode.TemporaryAccount
imageId: name.text || jamiId
}
spinnerTriggeredtext: JamiStrings.generatingAccount
normalText: JamiStrings.importButton
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter
spacing: 4
enabled: pinFromDevice.dynamicText.length !== 0 && !spinnerTriggered
Text {
id: name
visible: text !== undefined && text !== ""
KeyNavigation.tab: backButton
KeyNavigation.up: passwordFromDevice
KeyNavigation.down: backButton
Connections {
id: registeredNameFoundConnection
target: NameDirectory
enabled: jamiId.length > 0
onClicked: {
spinnerTriggered = true;
WizardViewStepModel.accountCreationInfo = JamiQmlUtils.setUpAccountCreationInputPara({
"archivePin": pinFromDevice.dynamicText,
"password": passwordFromDevice.dynamicText
});
WizardViewStepModel.nextStep();
function onRegisteredNameFound(status, address, registeredName, requestedName) {
if (address === jamiId && status === NameDirectory.LookupStatus.SUCCESS) {
name.text = registeredName;
}
}
}
}
Text {
id: userId
text: jamiId
}
}
}
}
// Password
PasswordTextEdit {
id: passwordField
Layout.fillWidth: true
Layout.leftMargin: 10
Layout.rightMargin: 10
Layout.topMargin: 10
Layout.bottomMargin: 10
visible: requiresPassword()
placeholderText: JamiStrings.enterPassword
echoMode: TextInput.Password
onAccepted: confirmButton.clicked()
}
Text {
id: passwordErrorField
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.width - 40
visible: isPasswordWrong()
text: JamiStrings.authenticationError
font.pointSize: JamiTheme.tinyFontSize
color: JamiTheme.redColor
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 16
Layout.margins: 10
MaterialButton {
id: confirmButton
text: JamiStrings.optionConfirm
primary: true
enabled: !passwordField.visible || passwordField.dynamicText.length > 0
onClicked: {
AccountAdapter.provideAccountAuthentication(passwordField.visible ? passwordField.dynamicText : "");
}
}
}
}
Label {
id: errorLabel
// Show busy indicator when waiting for token
BusyIndicator {
Layout.alignment: Qt.AlignHCenter
visible: isLoadingState()
Layout.preferredWidth: 50
Layout.preferredHeight: 50
running: visible
}
Layout.alignment: Qt.AlignCenter
Layout.bottomMargin: JamiTheme.wizardViewPageBackButtonMargins
// QR Code container with frame
Rectangle {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: qrLoader.Layout.preferredWidth + 40
Layout.preferredHeight: qrLoader.Layout.preferredHeight + 40
visible: WizardViewStepModel.deviceAuthState === DeviceAuthStateEnum.TOKEN_AVAILABLE
color: JamiTheme.primaryBackgroundColor
radius: 8
border.width: 1
border.color: JamiTheme.tabbarBorderColor
visible: errorText.length !== 0
Loader {
id: qrLoader
anchors.centerIn: parent
active: WizardViewStepModel.deviceAuthState === DeviceAuthStateEnum.TOKEN_AVAILABLE
Layout.preferredWidth: Math.min(parent.parent.width - 60, 250)
Layout.preferredHeight: Layout.preferredWidth
text: errorText
sourceComponent: Image {
width: qrLoader.Layout.preferredWidth
height: qrLoader.Layout.preferredHeight
smooth: false
fillMode: Image.PreserveAspectFit
source: "image://qrImage/raw_" + tokenUri
}
}
}
font.pixelSize: JamiTheme.textEditError
color: JamiTheme.redColor
// Token URI backup text
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
visible: tokenUri !== ""
spacing: 8
Text {
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.parent.width - 40
horizontalAlignment: Text.AlignHCenter
text: JamiStrings.cantScanQRCode
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
lineHeight: JamiTheme.wizardViewTextLineHeight
color: JamiTheme.textColor
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
TextArea {
id: tokenUriTextArea
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.parent.width - 40
text: tokenUri
font.pointSize: JamiTheme.wizardViewDescriptionFontPixelSize
horizontalAlignment: Text.AlignHCenter
readOnly: true
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
selectByMouse: true
background: Rectangle {
color: JamiTheme.primaryBackgroundColor
radius: 5
border.width: 1
border.color: JamiTheme.tabbarBorderColor
}
}
}
// Error view
ColumnLayout {
id: errorColumn
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.width - 40
visible: errorText !== ""
spacing: 16
Text {
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.width
text: errorText
color: JamiTheme.textColor
font.pointSize: JamiTheme.mediumFontSize
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
MaterialButton {
Layout.alignment: Qt.AlignHCenter
text: JamiStrings.optionTryAgain
toolTipText: JamiStrings.optionTryAgain
primary: true
onClicked: {
AccountAdapter.cancelImportAccount();
WizardViewStepModel.previousStep();
}
}
}
}
BackButton {
// Back button
JamiPushButton {
id: backButton
QWKSetParentHitTestVisible {
}
objectName: "importFromDevicePageBackButton"
@ -250,12 +415,18 @@ Rectangle {
anchors.top: parent.top
anchors.margins: JamiTheme.wizardViewPageBackButtonMargins
visible: !connectBtn.spinnerTriggered
preferredSize: 36
imageContainerWidth: 20
source: JamiResources.ic_arrow_back_24dp_svg
KeyNavigation.tab: pinFromDevice
KeyNavigation.up: connectBtn.enabled ? connectBtn : passwordFromDevice
KeyNavigation.down: pinFromDevice
visible: WizardViewStepModel.deviceAuthState !== DeviceAuthStateEnum.IN_PROGRESS
onClicked: WizardViewStepModel.previousStep()
onClicked: {
if (requiresConfirmationBeforeClosing()) {
confirmCloseDialog.open();
} else {
WizardViewStepModel.previousStep();
}
}
}
}

View file

@ -19,6 +19,7 @@
#include "appsettingsmanager.h"
#include "lrcinstance.h"
#include "global.h"
#include "api/accountmodel.h"
@ -46,17 +47,31 @@ WizardViewStepModel::WizardViewStepModel(LRCInstance* lrcInstance,
Q_EMIT accountIsReady(accountId);
});
// Connect to account model signals to track import progress
connect(&lrcInstance_->accountModel(),
&AccountModel::deviceAuthStateChanged,
this,
[this](const QString& accountID, int state, const MapStringString& details) {
set_deviceLinkDetails(Utils::mapStringStringToVariantMap(details));
set_deviceAuthState(static_cast<lrc::api::account::DeviceAuthState>(state));
});
}
void
WizardViewStepModel::startAccountCreationFlow(AccountCreationOption accountCreationOption)
{
using namespace lrc::api::account;
set_accountCreationOption(accountCreationOption);
if (accountCreationOption == AccountCreationOption::CreateJamiAccount
|| accountCreationOption == AccountCreationOption::CreateRendezVous)
if (accountCreationOption == AccountCreationOption::ImportFromDevice) {
set_mainStep(MainSteps::DeviceAuthorization);
Q_EMIT createAccountRequested(accountCreationOption);
} else if (accountCreationOption == AccountCreationOption::CreateJamiAccount
|| accountCreationOption == AccountCreationOption::CreateRendezVous) {
set_mainStep(MainSteps::NameRegistration);
else
} else {
set_mainStep(MainSteps::AccountCreation);
}
}
void
@ -80,6 +95,10 @@ WizardViewStepModel::previousStep()
reset();
break;
}
case MainSteps::DeviceAuthorization: {
reset();
break;
}
}
}
@ -88,4 +107,6 @@ WizardViewStepModel::reset()
{
set_accountCreationOption(AccountCreationOption::None);
set_mainStep(MainSteps::Initial);
set_deviceAuthState(lrc::api::account::DeviceAuthState::INIT);
set_deviceLinkDetails({});
}

View file

@ -18,6 +18,7 @@
#pragma once
#include "qtutils.h"
#include "api/account.h" // Include for DeviceAuthState
#include <QObject>
#include <QVariant>
@ -29,6 +30,21 @@ class AccountAdapter;
class LRCInstance;
class AppSettingsManager;
class DeviceAuthStateEnum : public QObject
{
Q_OBJECT
public:
enum State {
INIT = static_cast<int>(lrc::api::account::DeviceAuthState::INIT),
TOKEN_AVAILABLE = static_cast<int>(lrc::api::account::DeviceAuthState::TOKEN_AVAILABLE),
CONNECTING = static_cast<int>(lrc::api::account::DeviceAuthState::CONNECTING),
AUTHENTICATING = static_cast<int>(lrc::api::account::DeviceAuthState::AUTHENTICATING),
IN_PROGRESS = static_cast<int>(lrc::api::account::DeviceAuthState::IN_PROGRESS),
DONE = static_cast<int>(lrc::api::account::DeviceAuthState::DONE)
};
Q_ENUM(State)
};
class WizardViewStepModel : public QObject
{
Q_OBJECT
@ -37,9 +53,10 @@ class WizardViewStepModel : public QObject
public:
enum class MainSteps {
Initial, // Initial welcome step.
AccountCreation, // General account creation step.
NameRegistration, // Name registration step : CreateJamiAccount, CreateRendezVous
Initial, // Initial welcome step.
AccountCreation, // General account creation step.
NameRegistration, // Name registration step : CreateJamiAccount, CreateRendezVous
DeviceAuthorization // Add new step for device authorization.
};
Q_ENUM(MainSteps)
@ -57,6 +74,8 @@ public:
QML_PROPERTY(MainSteps, mainStep)
QML_PROPERTY(AccountCreationOption, accountCreationOption)
QML_PROPERTY(QVariantMap, accountCreationInfo)
QML_PROPERTY(lrc::api::account::DeviceAuthState, deviceAuthState)
QML_PROPERTY(QVariantMap, deviceLinkDetails)
public:
static WizardViewStepModel* create(QQmlEngine*, QJSEngine*)

View file

@ -117,12 +117,14 @@ public Q_SLOTS:
void slotAccountStatusChanged(const QString& accountID, const api::account::Status status);
/**
* Emit exportOnRingEnded.
* Emit deviceAuthStateChanged.
* @param accountId
* @param status
* @param pin
* @param state
* @param details map
*/
void slotExportOnRingEnded(const QString& accountID, int status, const QString& pin);
void slotDeviceAuthStateChanged(const QString& accountID,
int state,
const MapStringString& details);
/**
* @param accountId
@ -282,11 +284,12 @@ AccountModel::setAlias(const QString& accountId, const QString& alias, bool save
accountInfo.profileInfo.alias = alias;
if (save)
ConfigurationManager::instance().updateProfile(accountId,
alias,
"",
"",
5);// flag out of range to avoid updating avatar
ConfigurationManager::instance()
.updateProfile(accountId,
alias,
"",
"",
5); // flag out of range to avoid updating avatar
Q_EMIT profileUpdated(accountId);
}
@ -323,9 +326,30 @@ AccountModel::exportToFile(const QString& accountId,
}
bool
AccountModel::exportOnRing(const QString& accountId, const QString& password) const
AccountModel::provideAccountAuthentication(const QString& accountId,
const QString& credentialsFromUser) const
{
return ConfigurationManager::instance().exportOnRing(accountId, password);
return ConfigurationManager::instance().provideAccountAuthentication(accountId,
credentialsFromUser,
"password");
}
int32_t
AccountModel::addDevice(const QString& accountId, const QString& token) const
{
return ConfigurationManager::instance().addDevice(accountId, token);
}
bool
AccountModel::confirmAddDevice(const QString& accountId, uint32_t operationId) const
{
return ConfigurationManager::instance().confirmAddDevice(accountId, operationId);
}
bool
AccountModel::cancelAddDevice(const QString& accountId, uint32_t operationId) const
{
return ConfigurationManager::instance().cancelAddDevice(accountId, operationId);
}
void
@ -403,9 +427,9 @@ AccountModelPimpl::AccountModelPimpl(AccountModel& linked,
this,
&AccountModelPimpl::slotVolatileAccountDetailsChanged);
connect(&callbacksHandler,
&CallbacksHandler::exportOnRingEnded,
this,
&AccountModelPimpl::slotExportOnRingEnded);
&CallbacksHandler::deviceAuthStateChanged,
&linked,
&AccountModel::deviceAuthStateChanged);
connect(&callbacksHandler,
&CallbacksHandler::nameRegistrationEnded,
this,
@ -594,23 +618,13 @@ AccountModelPimpl::slotVolatileAccountDetailsChanged(const QString& accountId,
}
void
AccountModelPimpl::slotExportOnRingEnded(const QString& accountID, int status, const QString& pin)
AccountModelPimpl::slotDeviceAuthStateChanged(const QString& accountId,
int state,
const MapStringString& details)
{
account::ExportOnRingStatus convertedStatus = account::ExportOnRingStatus::INVALID;
switch (status) {
case 0:
convertedStatus = account::ExportOnRingStatus::SUCCESS;
break;
case 1:
convertedStatus = account::ExportOnRingStatus::WRONG_PASSWORD;
break;
case 2:
convertedStatus = account::ExportOnRingStatus::NETWORK_ERROR;
break;
default:
break;
}
Q_EMIT linked.exportOnRingEnded(accountID, convertedStatus, pin);
// implement business logic here
// can be bypassed with a signal to signal
Q_EMIT linked.deviceAuthStateChanged(accountId, state, details);
}
void
@ -673,7 +687,11 @@ AccountModelPimpl::slotRegisteredNameFound(const QString& accountId,
default:
break;
}
Q_EMIT linked.registeredNameFound(accountId, requestedName, convertedStatus, address, registeredName);
Q_EMIT linked.registeredNameFound(accountId,
requestedName,
convertedStatus,
address,
registeredName);
}
void
@ -1041,32 +1059,47 @@ account::ConfProperties_t::toDetails() const
QString
AccountModel::createNewAccount(profile::Type type,
const MapStringString& config,
const QString& displayName,
const QString& archivePath,
const QString& password,
const QString& pin,
const QString& uri,
const MapStringString& config)
const QString& uri)
{
// Get the template for the account type to prefill the details
MapStringString details = type == profile::Type::SIP
? ConfigurationManager::instance().getAccountTemplate("SIP")
: ConfigurationManager::instance().getAccountTemplate("RING");
using namespace libjami::Account;
details[ConfProperties::TYPE] = type == profile::Type::SIP ? "SIP" : "RING";
details[ConfProperties::DISPLAYNAME] = displayName;
details[ConfProperties::ALIAS] = displayName;
details[ConfProperties::UPNP_ENABLED] = "true";
details[ConfProperties::ARCHIVE_PASSWORD] = password;
details[ConfProperties::ARCHIVE_PIN] = pin;
details[ConfProperties::ARCHIVE_PATH] = archivePath;
if (type == profile::Type::SIP)
details[ConfProperties::USERNAME] = uri;
// Add the supplied config to the details
if (!config.isEmpty()) {
for (MapStringString::const_iterator it = config.begin(); it != config.end(); it++) {
details[it.key()] = it.value();
}
}
using namespace libjami::Account;
// Add the rest of the details if we are not creating an ephemeral account for linking
// in which case the ARCHIVE_URL was set to "jami-auth" or the MANAGER_URI was set to
// the account manager URI in the case of a remote account manager connection
if (details[ConfProperties::ARCHIVE_URL].isEmpty()
&& details[ConfProperties::MANAGER_URI].isEmpty()) {
details[ConfProperties::TYPE] = type == profile::Type::SIP ? "SIP" : "RING";
details[ConfProperties::DISPLAYNAME] = displayName;
details[ConfProperties::ALIAS] = displayName;
details[ConfProperties::UPNP_ENABLED] = "true";
details[ConfProperties::ARCHIVE_PASSWORD] = password;
details[ConfProperties::ARCHIVE_PIN] = pin;
details[ConfProperties::ARCHIVE_PATH] = archivePath;
// Override the username with the provided URI if it's a SIP account
if (type == profile::Type::SIP) {
details[ConfProperties::USERNAME] = uri;
}
}
// Actually add the account and return the account ID
QString accountId = ConfigurationManager::instance().addAccount(details);
return accountId;
}
@ -1077,20 +1110,24 @@ AccountModel::connectToAccountManager(const QString& username,
const QString& serverUri,
const MapStringString& config)
{
MapStringString details = ConfigurationManager::instance().getAccountTemplate("RING");
MapStringString details = config;
using namespace libjami::Account;
details[ConfProperties::TYPE] = "RING";
details[ConfProperties::MANAGER_URI] = serverUri;
details[ConfProperties::MANAGER_USERNAME] = username;
details[ConfProperties::ARCHIVE_PASSWORD] = password;
if (!config.isEmpty()) {
for (MapStringString::const_iterator it = config.begin(); it != config.end(); it++) {
details[it.key()] = it.value();
}
}
return createNewAccount(profile::Type::JAMI, details);
}
QString accountId = ConfigurationManager::instance().addAccount(details);
return accountId;
QString
AccountModel::createDeviceImportAccount()
{
// auto details = ConfigurationManager::instance().getAccountTemplate("RING");
MapStringString details;
using namespace libjami::Account;
details[ConfProperties::TYPE] = "RING";
details[ConfProperties::ARCHIVE_URL] = "jami-auth";
return createNewAccount(profile::Type::JAMI, details);
}
void

View file

@ -188,9 +188,65 @@ struct ConfProperties_t
MapStringString toDetails() const;
};
// Possible account export status
enum class ExportOnRingStatus { SUCCESS = 0, WRONG_PASSWORD = 1, NETWORK_ERROR = 2, INVALID };
Q_ENUM_NS(ExportOnRingStatus)
// The following statuses are used to track the status of
// device-linking and account-import
enum class DeviceAuthState {
INIT = 0,
TOKEN_AVAILABLE = 1,
CONNECTING = 2,
AUTHENTICATING = 3,
IN_PROGRESS = 4,
DONE = 5
};
Q_ENUM_NS(DeviceAuthState)
enum class DeviceLinkError {
WRONG_PASSWORD, // auth_error, invalid_credentials
NETWORK, // network
TIMEOUT, // timeout
STATE, // state
CANCELED, // canceled
UNKNOWN // fallback
};
Q_ENUM_NS(DeviceLinkError)
inline DeviceLinkError
mapLinkDeviceError(const std::string& error)
{
if (error == "auth_error" || error == "invalid_credentials")
return DeviceLinkError::WRONG_PASSWORD;
if (error == "network")
return DeviceLinkError::NETWORK;
if (error == "timeout")
return DeviceLinkError::TIMEOUT;
if (error == "state")
return DeviceLinkError::STATE;
if (error == "canceled")
return DeviceLinkError::CANCELED;
return DeviceLinkError::UNKNOWN;
}
inline QString
getLinkDeviceString(DeviceLinkError error)
{
switch (error) {
case DeviceLinkError::WRONG_PASSWORD:
return QObject::tr(
"An authentication error occurred.\nPlease check credentials and try again.");
case DeviceLinkError::NETWORK:
return QObject::tr("A network error occurred.\nPlease verify your connection.");
case DeviceLinkError::TIMEOUT:
return QObject::tr("The operation has timed out.\nPlease try again.");
case DeviceLinkError::STATE:
return QObject::tr("An error occurred while exporting the account.\nPlease try again.");
case DeviceLinkError::CANCELED:
return QObject::tr("Operation was canceled.");
case DeviceLinkError::UNKNOWN:
default:
return QObject::tr("An unexpected error occurred.\nPlease try again.");
}
}
enum class RegisterNameStatus {
SUCCESS = 0,

View file

@ -112,13 +112,37 @@ public:
Q_INVOKABLE bool exportToFile(const QString& accountId,
const QString& path,
const QString& password = {}) const;
/**
* Call exportOnRing from the daemon
* Provide authentication for an account
* @param accountId
* @param password
* @param credentialsFromUser
* @return if the authentication is successful
*/
Q_INVOKABLE bool provideAccountAuthentication(const QString& accountId,
const QString& credentialsFromUser) const;
/**
* @param accountId
* @param uri
* @return if the export is initialized
*/
Q_INVOKABLE bool exportOnRing(const QString& accountId, const QString& password) const;
Q_INVOKABLE int32_t addDevice(const QString& accountId, const QString& token) const;
/**
* Confirm the addition of a device
* @param accountId
* @param operationId
*/
Q_INVOKABLE bool confirmAddDevice(const QString& accountId, uint32_t operationId) const;
/**
* Cancel the addition of a device
* @param accountId
* @param operationId
*/
Q_INVOKABLE bool cancelAddDevice(const QString& accountId, uint32_t operationId) const;
/**
* Call removeAccount from the daemon
* @param accountId to remove
@ -141,7 +165,7 @@ public:
* @param avatar
* @throws out_of_range exception if account is not found
*/
void setAvatar(const QString& accountId, const QString& avatar, bool save = true, int flag =0);
void setAvatar(const QString& accountId, const QString& avatar, bool save = true, int flag = 0);
/**
* Change the alias of an account
* @param accountId
@ -159,18 +183,7 @@ public:
Q_INVOKABLE bool registerName(const QString& accountId,
const QString& password,
const QString& username);
/**
* Connect to JAMS to retrieve the account
* @param username
* @param password
* @param serverUri
* @param config
* @return the account id
*/
static QString connectToAccountManager(const QString& username,
const QString& password,
const QString& serverUri,
const MapStringString& config = MapStringString());
/**
* Create a new Ring or SIP account
* @param type determine if the new account will be a Ring account or a SIP one
@ -184,12 +197,32 @@ public:
* @return the created account
*/
static QString createNewAccount(profile::Type type,
const MapStringString& config = MapStringString(),
const QString& displayName = "",
const QString& archivePath = "",
const QString& password = "",
const QString& pin = "",
const QString& uri = "",
const MapStringString& config = MapStringString());
const QString& uri = "");
/**
* Connect to JAMS to retrieve the account
* @param username
* @param password
* @param serverUri
* @param config
* @return the account id
*/
static QString connectToAccountManager(const QString& username,
const QString& password,
const QString& serverUri,
const MapStringString& config = MapStringString());
/**
* Create a simple ephemeral account from a device import
* @return the account id of the created account
*/
static QString createDeviceImportAccount();
/**
* Set an account to the first position
*/
@ -296,14 +329,24 @@ Q_SIGNALS:
void profileUpdated(const QString& accountID);
/**
* Connect this signal to know when an account is exported on the DHT
* Device authentication state has changed
* @param accountID
* @param status
* @param pin
* @param state
* @param details map
*/
void exportOnRingEnded(const QString& accountID,
account::ExportOnRingStatus status,
const QString& pin);
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

View file

@ -242,9 +242,9 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
Qt::QueuedConnection);
connect(&ConfigurationManager::instance(),
&ConfigurationManagerInterface::exportOnRingEnded,
&ConfigurationManagerInterface::deviceAuthStateChanged,
this,
&CallbacksHandler::slotExportOnRingEnded,
&CallbacksHandler::slotDeviceAuthStateChanged,
Qt::QueuedConnection);
connect(&ConfigurationManager::instance(),
@ -546,7 +546,9 @@ CallbacksHandler::slotIncomingMessage(const QString& accountId,
}
void
CallbacksHandler::slotConferenceCreated(const QString& accountId, const QString& convId, const QString& callId)
CallbacksHandler::slotConferenceCreated(const QString& accountId,
const QString& convId,
const QString& callId)
{
Q_EMIT conferenceCreated(accountId, convId, callId);
}
@ -678,9 +680,11 @@ CallbacksHandler::slotDeviceRevokationEnded(const QString& accountId,
}
void
CallbacksHandler::slotExportOnRingEnded(const QString& accountId, int status, const QString& pin)
CallbacksHandler::slotDeviceAuthStateChanged(const QString& accountId,
int state,
const MapStringString& details)
{
Q_EMIT exportOnRingEnded(accountId, status, pin);
Q_EMIT deviceAuthStateChanged(accountId, state, details);
}
void

View file

@ -171,7 +171,9 @@ Q_SIGNALS:
* Connect this signal to know when a new conference is created
* @param callId of the conference
*/
void conferenceCreated(const QString& accountId, const QString& conversationId, const QString& callId);
void conferenceCreated(const QString& accountId,
const QString& conversationId,
const QString& callId);
void conferenceChanged(const QString& accountId, const QString& confId, const QString& state);
/**
* Connect this signal to know when a conference is removed
@ -235,12 +237,12 @@ Q_SIGNALS:
const QString& userPhoto);
/**
* Emit exportOnRingEnded
* Device authentication state has changed
* @param accountId
* @param status SUCCESS = 0, WRONG_PASSWORD = 1, NETWORK_ERROR = 2
* @param pin
* @param state
* @param details map
*/
void exportOnRingEnded(const QString& accountId, int status, const QString& pin);
void deviceAuthStateChanged(const QString& accountId, int state, const MapStringString& details);
/**
* Name registration has ended
@ -504,7 +506,9 @@ private Q_SLOTS:
* @param callId of the conference
* @param conversationId of the conference
*/
void slotConferenceCreated(const QString& accountId, const QString& conversationId, const QString& callId);
void slotConferenceCreated(const QString& accountId,
const QString& conversationId,
const QString& callId);
/**
* Emit conferenceRemove
* @param accountId
@ -574,12 +578,14 @@ private Q_SLOTS:
const QString& userPhoto);
/**
* Emit exportOnRingEnded
* Device authentication state has changed
* @param accountId
* @param status SUCCESS = 0, WRONG_PASSWORD = 1, NETWORK_ERROR = 2
* @param pin
* @param state
* @param details map
*/
void slotExportOnRingEnded(const QString& accountId, int status, const QString& pin);
void slotDeviceAuthStateChanged(const QString& accountId,
int state,
const MapStringString& details);
/**
* Emit nameRegistrationEnded

View file

@ -36,11 +36,6 @@ NameDirectoryPrivate::NameDirectoryPrivate(NameDirectory* q)
this,
&NameDirectoryPrivate::slotRegisteredNameFound,
Qt::QueuedConnection);
connect(&configurationManager,
&ConfigurationManagerInterface::exportOnRingEnded,
this,
&NameDirectoryPrivate::slotExportOnRingEnded,
Qt::QueuedConnection);
}
NameDirectory::NameDirectory()
@ -100,16 +95,6 @@ NameDirectoryPrivate::slotRegisteredNameFound(const QString& accountId,
requestedName);
}
// Export account has ended with pin generated
void
NameDirectoryPrivate::slotExportOnRingEnded(const QString& accountId, int status, const QString& pin)
{
LC_DBG << "Export on ring ended for account: " << accountId << "status: " << status
<< "PIN: " << pin;
Q_EMIT q_ptr->exportOnRingEnded(static_cast<NameDirectory::ExportOnRingStatus>(status), pin);
}
// Lookup a name
bool
NameDirectory::lookupName(const QString& accountId,

View file

@ -40,15 +40,16 @@ public:
enum class LookupStatus { SUCCESS = 0, INVALID_NAME = 1, NOT_FOUND = 2, ERROR = 3 };
Q_ENUM(LookupStatus)
enum class ExportOnRingStatus { SUCCESS = 0, WRONG_PASSWORD = 1, NETWORK_ERROR = 2, INVALID };
Q_ENUM(ExportOnRingStatus)
// Singleton
static NameDirectory& instance();
// Lookup
Q_INVOKABLE bool lookupName(const QString& accountId, const QString& name, const QString& nameServiceURL = "") const;
Q_INVOKABLE bool lookupAddress(const QString& accountId, const QString& address, const QString& nameServiceURL = "") const;
Q_INVOKABLE bool lookupName(const QString& accountId,
const QString& name,
const QString& nameServiceURL = "") const;
Q_INVOKABLE bool lookupAddress(const QString& accountId,
const QString& address,
const QString& nameServiceURL = "") const;
private:
// Constructors & Destructors
@ -70,8 +71,5 @@ Q_SIGNALS:
const QString& address,
const QString& registeredName,
const QString& requestedName);
// Export account has ended with pin generated
void exportOnRingEnded(NameDirectory::ExportOnRingStatus status, const QString& pin);
};
Q_DECLARE_METATYPE(NameDirectory*)

View file

@ -37,5 +37,4 @@ public Q_SLOTS:
int status,
const QString& address,
const QString& registeredName);
void slotExportOnRingEnded(const QString& accountId, int status, const QString& pin);
};

View file

@ -150,11 +150,23 @@ public:
QString(displayName.c_str()),
QString(userPhoto.c_str()));
}),
exportable_callback<ConfigurationSignal::ExportOnRingEnded>(
[this](const std::string& accountId, int status, const std::string& pin) {
Q_EMIT this->exportOnRingEnded(QString(accountId.c_str()),
status,
QString(pin.c_str()));
exportable_callback<ConfigurationSignal::AddDeviceStateChanged>(
[this](const std::string& accountId,
uint32_t operationId,
int state,
const std::map<std::string, std::string>& details) {
Q_EMIT this->addDeviceStateChanged(QString(accountId.c_str()),
operationId,
state,
convertMap(details));
}),
exportable_callback<ConfigurationSignal::DeviceAuthStateChanged>(
[this](const std::string& accountId,
int state,
const std::map<std::string, std::string>& details) {
Q_EMIT this->deviceAuthStateChanged(QString(accountId.c_str()),
state,
convertMap(details));
}),
exportable_callback<ConfigurationSignal::NameRegistrationEnded>(
[this](const std::string& accountId, int status, const std::string& name) {
@ -431,9 +443,28 @@ public Q_SLOTS: // METHODS
path.toStdString());
}
bool exportOnRing(const QString& accountId, const QString& password)
bool provideAccountAuthentication(const QString& accountId,
const QString& credentialsFromUser,
const QString scheme = "password")
{
return libjami::exportOnRing(accountId.toStdString(), password.toStdString());
return libjami::provideAccountAuthentication(accountId.toStdString(),
credentialsFromUser.toStdString(),
scheme.toStdString());
}
int32_t addDevice(const QString& accountId, const QString& token)
{
return libjami::addDevice(accountId.toStdString(), token.toStdString());
}
bool confirmAddDevice(const QString& accountId, uint32_t operationId)
{
return libjami::confirmAddDevice(accountId.toStdString(), operationId);
}
bool cancelAddDevice(const QString& accountId, uint32_t operationId)
{
return libjami::cancelAddDevice(accountId.toStdString(), operationId);
}
bool exportToFile(const QString& accountId,
@ -498,8 +529,7 @@ public Q_SLOTS: // METHODS
displayName.toStdString(),
avatarPath.toStdString(),
fileType.toStdString(),
flag
);
flag);
}
QStringList getAccountList()
@ -1197,7 +1227,11 @@ Q_SIGNALS: // SIGNALS
const QString& certId,
const QString& status);
void knownDevicesChanged(const QString& accountId, const MapStringString& devices);
void exportOnRingEnded(const QString& accountId, int status, const QString& pin);
void addDeviceStateChanged(const QString& accountId,
uint32_t operationId,
int state,
const MapStringString& details);
void deviceAuthStateChanged(const QString& accountId, int state, const MapStringString& details);
void incomingAccountMessage(const QString& accountId,
const QString& from,
const QString msgId,