From ad35d108f2c67355e7f7d8ff17cea3d96cecab16 Mon Sep 17 00:00:00 2001 From: cberthet <capucine.berthet@savoirfairelinux.com> Date: Wed, 20 Sep 2023 12:10:12 -0400 Subject: [PATCH] link-device: apply new design and add QR code GitLab: #1253 Change-Id: Ib47a081e4e5d714e98fb397732ff9232b62afc36 --- src/app/constant/JamiStrings.qml | 15 +- src/app/constant/JamiTheme.qml | 3 + src/app/qrimageprovider.h | 24 +- .../components/LinkDeviceDialog.qml | 262 +++++++++++++----- .../components/LinkedDevicesPage.qml | 41 +++ .../components/ManageAccountPage.qml | 2 +- src/app/utils.cpp | 4 +- src/app/utils.h | 2 +- 8 files changed, 263 insertions(+), 90 deletions(-) diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml index 961acdc64..f0dfc8bc6 100644 --- a/src/app/constant/JamiStrings.qml +++ b/src/app/constant/JamiStrings.qml @@ -436,7 +436,7 @@ Item { // LinkedDevices property string tipLinkNewDevice: qsTr("Link a new device to this account") - property string linkNewDevice: qsTr("Exporting account…") + property string linkDevice: qsTr("Exporting account…") property string removeDevice: qsTr("Remove Device") property string sureToRemoveDevice: qsTr("Are you sure you wish to remove this device?") property string yourPinIs: qsTr("Your PIN is:") @@ -575,13 +575,20 @@ Item { // LinkDevicesDialog property string pinTimerInfos: qsTr("The PIN and the account password should be entered in your device within 10 minutes.") property string close: qsTr("Close") - property string enterAccountPassword: qsTr("Enter account's password") + 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 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 QR code or manually enter the PIN.") + property string pinValidity: qsTr("The PIN code is valid for: ") // PasswordDialog - property string enterPassword: qsTr("Enter the password") + property string enterPassword: qsTr("Enter password") property string enterCurrentPassword: qsTr("Enter current password") - property string confirmRemoval: qsTr("Enter this account's password to confirm the removal of this device") + property string confirmRemoval: qsTr("Enter account password to confirm the removal of this device") property string enterNewPassword: qsTr("Enter new password") property string confirmNewPassword: qsTr("Confirm new password") property string change: qsTr("Change") diff --git a/src/app/constant/JamiTheme.qml b/src/app/constant/JamiTheme.qml index d417bcd97..7a42dd6f5 100644 --- a/src/app/constant/JamiTheme.qml +++ b/src/app/constant/JamiTheme.qml @@ -643,6 +643,9 @@ Item { property int settingsMenuHeaderButtonHeight: 50 property int settingsListViewsSpacing: 10 + // Link Device + property color pinBackgroundColor: "#D6E4EF" + // MaterialRadioButton property int radioImageSize: 30 property color radioBackgroundColor: darkTheme ? "#303030" : "#F0EFEF" diff --git a/src/app/qrimageprovider.h b/src/app/qrimageprovider.h index eba496eb0..a3e4e71d7 100644 --- a/src/app/qrimageprovider.h +++ b/src/app/qrimageprovider.h @@ -33,7 +33,7 @@ public: instance) {} - enum class QrType { Account, Contact }; + enum class QrType { Account, Contact, Raw }; /* * Id should be string like account_0 (account index), @@ -64,6 +64,8 @@ public: } catch (...) { } return {QrType::Contact, {}}; + } else if (list.contains("raw") && list.size() > 1) { + return {QrType::Raw, list[1]}; } return {QrType::Account, {}}; } @@ -73,26 +75,24 @@ public: Q_UNUSED(size); QString uri; - auto indexPair = getIndexFromID(id); + auto [type, identifier] = getIndexFromID(id); - if (indexPair.first == QrType::Contact) { - uri = indexPair.second; - } else { - if (indexPair.second.isEmpty()) + if (type == QrType::Account) { + if (identifier.isEmpty()) return QImage(); - auto accountId = indexPair.second; try { - auto& accountInfo = lrcInstance_->getAccountInfo(accountId); + auto& accountInfo = lrcInstance_->getAccountInfo(identifier); uri = accountInfo.profileInfo.uri; } catch (const std::out_of_range&) { - qWarning() << "Couldn't get account info for id:" << accountId; + qWarning() << "Couldn't get account info for id:" << identifier; return QImage(); } - } + } else + uri = identifier; if (!requestedSize.isEmpty()) - return Utils::setupQRCode(uri, 0).scaled(requestedSize, Qt::KeepAspectRatio); + return Utils::getQRCodeImage(uri, 0).scaled(requestedSize, Qt::KeepAspectRatio); else - return Utils::setupQRCode(uri, 0); + return Utils::getQRCodeImage(uri, 0); } }; diff --git a/src/app/settingsview/components/LinkDeviceDialog.qml b/src/app/settingsview/components/LinkDeviceDialog.qml index 3db9f9638..c0ad198aa 100644 --- a/src/app/settingsview/components/LinkDeviceDialog.qml +++ b/src/app/settingsview/components/LinkDeviceDialog.qml @@ -23,13 +23,16 @@ import net.jami.Models 1.1 import net.jami.Adapters 1.1 import net.jami.Constants 1.1 import "../../commoncomponents" +import "../../mainview/components" BaseModalDialog { id: root signal accepted - title: JamiStrings.addDevice + title: JamiStrings.linkNewDevice + + property bool darkTheme: UtilsAdapter.useApplicationTheme() popupContent: StackLayout { id: stackedWidget @@ -47,13 +50,11 @@ BaseModalDialog { function setExportPage(status, pin) { if (status === NameDirectory.ExportOnRingStatus.SUCCESS) { infoLabel.success = true; - yourPinLabel.visible = true; - exportedPIN.visible = true; - infoLabel.text = JamiStrings.pinTimerInfos; + pinRectangle.visible = true exportedPIN.text = pin; } else { - infoLabel.success = false; - infoLabelsRowLayout.visible = false; + pinRectangle.success = false; + infoLabel.visible = true; switch (status) { case NameDirectory.ExportOnRingStatus.WRONG_PASSWORD: infoLabel.text = JamiStrings.incorrectPassword; @@ -86,15 +87,14 @@ BaseModalDialog { function onExportOnRingEnded(status, pin) { stackedWidget.setExportPage(status, pin); + countdownTimer.start(); } } onVisibleChanged: { if (visible) { - infoLabel.text = JamiStrings.pinTimerInfos; if (CurrentAccount.hasArchivePassword) { stackedWidget.currentIndex = enterPasswordPage.pageIndex; - passwordEdit.forceActiveFocus(); } else { setGeneratingPage(); } @@ -107,14 +107,23 @@ BaseModalDialog { readonly property int pageIndex: 0 + Component.onCompleted: passwordEdit.forceActiveFocus() + + onHeightChanged: { + stackedWidget.height = passwordLayout.implicitHeight + } + ColumnLayout { + id: passwordLayout spacing: JamiTheme.preferredMarginSize anchors.centerIn: parent Label { Layout.alignment: Qt.AlignCenter + Layout.maximumWidth: root.width - 4 * JamiTheme.preferredMarginSize + wrapMode: Text.Wrap - text: JamiStrings.enterAccountPassword + text: JamiStrings.enterPasswordPinCode color: JamiTheme.textColor font.pointSize: JamiTheme.textFontSize font.kerning: true @@ -122,70 +131,48 @@ BaseModalDialog { verticalAlignment: Text.AlignVCenter } - PasswordTextEdit { - id: passwordEdit - - firstEntry: true - placeholderText: JamiStrings.password - + RowLayout { Layout.topMargin: 10 Layout.leftMargin: JamiTheme.cornerIconSize Layout.rightMargin: JamiTheme.cornerIconSize - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - - KeyNavigation.up: btnConfirm - KeyNavigation.down: KeyNavigation.up - - onDynamicTextChanged: { - btnConfirm.enabled = dynamicText.length > 0; - } - onAccepted: btnConfirm.clicked() - } - - RowLayout { - Layout.alignment: Qt.AlignCenter + spacing: JamiTheme.preferredMarginSize Layout.bottomMargin: JamiTheme.preferredMarginSize - spacing: 16 - MaterialButton { - id: btnConfirm + PasswordTextEdit { + id: passwordEdit - Layout.alignment: Qt.AlignHCenter + firstEntry: true + placeholderText: JamiStrings.password - preferredWidth: JamiTheme.preferredFieldWidth / 2 - 8 - buttontextHeightMargin: JamiTheme.buttontextHeightMargin + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true - color: enabled ? JamiTheme.buttonTintedBlack : JamiTheme.buttonTintedGrey - hoveredColor: JamiTheme.buttonTintedBlackHovered - pressedColor: JamiTheme.buttonTintedBlackPressed - secondary: true - autoAccelerator: true - enabled: false + KeyNavigation.up: btnConfirm + KeyNavigation.down: KeyNavigation.up - text: JamiStrings.exportAccount - - onClicked: stackedWidget.setGeneratingPage() + onDynamicTextChanged: { + btnConfirm.enabled = dynamicText.length > 0; + btnConfirm.hoverEnabled = dynamicText.length > 0; + } + onAccepted: btnConfirm.clicked() } - MaterialButton { - id: btnCancel + JamiPushButton { + id: btnConfirm - Layout.alignment: Qt.AlignHCenter + Layout.alignment: Qt.AlignCenter + height: 40 + width: 40 + preferredSize: 60 - preferredWidth: JamiTheme.preferredFieldWidth / 2 - 8 - buttontextHeightMargin: JamiTheme.buttontextHeightMargin + hoverEnabled: false + enabled: false - color: JamiTheme.buttonTintedBlack - hoveredColor: JamiTheme.buttonTintedBlackHovered - pressedColor: JamiTheme.buttonTintedBlackPressed - secondary: true - autoAccelerator: true - enabled: true + imageColor: JamiTheme.tintedBlue + source: JamiResources.check_box_24dp_svg - text: JamiStrings.optionCancel + onClicked: stackedWidget.setGeneratingPage() - onClicked: close() } } } @@ -197,6 +184,11 @@ BaseModalDialog { readonly property int pageIndex: 1 + onHeightChanged: { + stackedWidget.height = spinnerLayout.implicitHeight + } + + ColumnLayout { id: spinnerLayout @@ -206,7 +198,7 @@ BaseModalDialog { Label { Layout.alignment: Qt.AlignCenter - text: JamiStrings.linkNewDevice + text: JamiStrings.linkDevice color: JamiTheme.textColor font.pointSize: JamiTheme.headerFontSize font.kerning: true @@ -246,40 +238,171 @@ BaseModalDialog { ColumnLayout { id: exportingLayout + spacing: JamiTheme.preferredMarginSize + Label { - id: yourPinLabel + id: instructionLabel + Layout.maximumWidth: JamiTheme.preferredDialogWidth Layout.alignment: Qt.AlignCenter - text: JamiStrings.yourPinIs color: JamiTheme.textColor - font.pointSize: JamiTheme.headerFontSize + padding: 8 + + wrapMode: Text.Wrap + text: JamiStrings.linkingInstructions + font.pointSize: JamiTheme.textFontSize font.kerning: true horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter + + } + + Rectangle { + Layout.alignment: Qt.AlignCenter + + border.width: 3 + border.color: JamiTheme.textColor + radius: JamiTheme.primaryRadius + color: darkTheme ? JamiTheme.textColor : JamiTheme.secondaryBackgroundColor + width: 170 + height: 170 + + Image { + id: qrImage + + anchors.fill: parent + anchors.margins: 10 + + mipmap: false + smooth: false + + source: "image://qrImage/raw_" + exportedPIN.text + sourceSize.width: 150 + sourceSize.height: 150 + } + } + + Rectangle { + id: pinRectangle + + radius: 15 + color: darkTheme ? JamiTheme.tintedBlue : JamiTheme.pinBackgroundColor + + width: exportedPIN.implicitWidth + 4 * JamiTheme.preferredMarginSize + height: exportedPIN.implicitHeight + 2 * JamiTheme.preferredMarginSize + + Layout.alignment: Qt.AlignCenter + Layout.margins: JamiTheme.preferredMarginSize + + MaterialLineEdit { + id: exportedPIN + + padding: 0 + anchors.centerIn: parent + + text: JamiStrings.pin + wrapMode: Text.NoWrap + + backgroundColor: darkTheme ? JamiTheme.tintedBlue : JamiTheme.pinBackgroundColor + + color: darkTheme ? JamiTheme.textColor : JamiTheme.tintedBlue + selectByMouse: true + readOnly: true + font.pointSize: JamiTheme.headerFontSize + font.kerning: true + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } } - MaterialLineEdit { - id: exportedPIN + RowLayout { - padding: 0 Layout.alignment: Qt.AlignCenter + Layout.bottomMargin: JamiTheme.preferredMarginSize + spacing: 0 + + Label { + id: validityLabel + + Layout.alignment: Qt.AlignRight + + color: JamiTheme.textColor + text: JamiStrings.pinValidity + font.pointSize: JamiTheme.textFontSize + font.kerning: true + } + + Label { + id: countdownLabel + + color: JamiTheme.textColor + Layout.alignment: Qt.AlignLeft + font.pointSize: JamiTheme.textFontSize + font.kerning: true + + text: "10:00" + } + + Timer { + id: countdownTimer + interval: 1000 + repeat: true - text: JamiStrings.pin - wrapMode: Text.NoWrap + property int remainingTime: 600 + + onTriggered: { + remainingTime-- + + var minutes = Math.floor(remainingTime / 60) + var seconds = remainingTime % 60 + countdownLabel.text = (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds + + if (remainingTime <= 0) { + validityLabel.visible = false + countdownLabel.text = JamiStrings.pinExpired + countdownLabel.color = JamiTheme.redColor + countdownTimer.stop() + } + } + } + + } + + Label { + id: otherDeviceLabel + + Layout.alignment: Qt.AlignCenter color: JamiTheme.textColor - selectByMouse: true - readOnly: true - font.pointSize: JamiTheme.headerFontSize + text: JamiStrings.onAnotherDevice + font.pointSize: JamiTheme.smallFontSize font.kerning: true + font.bold: true + } + + Label { + id: otherInstructionLabel + + Layout.maximumWidth: JamiTheme.preferredDialogWidth + Layout.bottomMargin: JamiTheme.preferredMarginSize + Layout.alignment: Qt.AlignCenter + wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter + + color: JamiTheme.textColor + text: JamiStrings.onAnotherDeviceInstruction + font.pointSize: JamiTheme.smallFontSize + font.kerning: true } + // Displays error messages Label { id: infoLabel + visible: false + property bool success: false property int borderWidth: success ? 1 : 0 property int borderRadius: success ? 15 : 0 @@ -295,7 +418,6 @@ BaseModalDialog { padding: success ? 8 : 0 wrapMode: Text.Wrap - text: JamiStrings.pinTimerInfos font.pointSize: success ? JamiTheme.textFontSize : JamiTheme.textFontSize + 3 font.kerning: true horizontalAlignment: Text.AlignHCenter diff --git a/src/app/settingsview/components/LinkedDevicesPage.qml b/src/app/settingsview/components/LinkedDevicesPage.qml index da9c2f0da..edd1aaeb9 100644 --- a/src/app/settingsview/components/LinkedDevicesPage.qml +++ b/src/app/settingsview/components/LinkedDevicesPage.qml @@ -39,6 +39,47 @@ SettingsPageBase { anchors.left: parent.left anchors.leftMargin: JamiTheme.preferredSettingsMarginSize + + + Text { + id: linkDescription + + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + + text: JamiStrings.linkDescription + color: JamiTheme.textColor + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + + font.pixelSize: JamiTheme.settingsDescriptionPixelSize + font.kerning: true + lineHeight: JamiTheme.wizardViewTextLineHeight + } + + MaterialButton { + id: linkDeviceBtn + + TextMetrics { + id: linkDeviceBtnTextSize + font.weight: Font.Bold + font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize + text: linkDeviceBtn.text + } + + preferredWidth: linkDeviceBtnTextSize.width + 2 * JamiTheme.buttontextWizzardPadding + Layout.bottomMargin: JamiTheme.preferredMarginSize + + primary: true + Layout.alignment: Qt.AlignLeft + + toolTipText: JamiStrings.tipLinkNewDevice + text: JamiStrings.linkNewDevice + + onClicked: viewCoordinator.presentDialog(appWindow, "settingsview/components/LinkDeviceDialog.qml") + } + Text { id: linkedDevicesTitle diff --git a/src/app/settingsview/components/ManageAccountPage.qml b/src/app/settingsview/components/ManageAccountPage.qml index e5627dc6d..cfddc59f1 100644 --- a/src/app/settingsview/components/ManageAccountPage.qml +++ b/src/app/settingsview/components/ManageAccountPage.qml @@ -218,7 +218,7 @@ SettingsPageBase { Layout.alignment: Qt.AlignLeft toolTipText: JamiStrings.tipLinkNewDevice - text: JamiStrings.linkAnotherDevice + text: JamiStrings.linkNewDevice onClicked: viewCoordinator.presentDialog(appWindow, "settingsview/components/LinkDeviceDialog.qml") } diff --git a/src/app/utils.cpp b/src/app/utils.cpp index 57fe7e6aa..78f3c8af4 100644 --- a/src/app/utils.cpp +++ b/src/app/utils.cpp @@ -805,9 +805,9 @@ Utils::pixmapFromSvg(const QString& svg_resource, const QSize& size) } QImage -Utils::setupQRCode(QString ringID, int margin) +Utils::getQRCodeImage(QString data, int margin) { - auto qrcode = QRcode_encodeString(ringID.toStdString().c_str(), + auto qrcode = QRcode_encodeString(data.toStdString().c_str(), 0, // Let the version be decided by libqrencode QR_ECLEVEL_L, // Lowest level of error correction QR_MODE_8, // 8-bit data mode diff --git a/src/app/utils.h b/src/app/utils.h index 6e762be25..886d28877 100644 --- a/src/app/utils.h +++ b/src/app/utils.h @@ -119,7 +119,7 @@ QPixmap generateTintedPixmap(const QPixmap& pix, QColor color); QImage scaleAndFrame(const QImage photo, const QSize& size = defaultAvatarSize); QImage cropImage(const QImage& img); QPixmap pixmapFromSvg(const QString& svg_resource, const QSize& size); -QImage setupQRCode(QString ringID, int margin); +QImage getQRCodeImage(QString data, int margin); bool isImage(const QString& fileExt); QString generateUid(); -- GitLab