diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d5fc332..66dfd55f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ cmake_minimum_required( VERSION 3.1 ) #set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version") if(UNIX AND APPLE) - project(Nunchuk VERSION 1.9.39) + project(Nunchuk VERSION 1.9.40) else() - project(nunchuk-qt VERSION 1.9.39) + project(nunchuk-qt VERSION 1.9.40) endif() set( CMAKE_CXX_STANDARD 20 ) diff --git a/Models/Chats/ClientController.cpp b/Models/Chats/ClientController.cpp index d5c87d82..c3e21205 100755 --- a/Models/Chats/ClientController.cpp +++ b/Models/Chats/ClientController.cpp @@ -98,8 +98,12 @@ void ClientController::setConnection(Connection *c) } emit connectionChanged(); connectSingleShot(m_connection.data(), &Connection::connected, this, [this]{ - connection()->loadState(); - connection()->sync(); + try { + connection()->loadState(); + connection()->sync(); + } catch (const std::exception& e) { + DBG_INFO << "ClientController::setConnection::connected" << e.what(); + } connect(m_connection->user(), &User::defaultAvatarChanged, this, &ClientController::onUserAvatarChanged ); connect(m_connection->user(), &User::defaultNameChanged, this, &ClientController::onUserDisplaynameChanged ); connectSingleShot(m_connection.data(), &Connection::syncDone, this, [this] { diff --git a/Models/Chats/QNunchukRoomModel.cpp b/Models/Chats/QNunchukRoomModel.cpp index 4d26becb..99428bb6 100755 --- a/Models/Chats/QNunchukRoomModel.cpp +++ b/Models/Chats/QNunchukRoomModel.cpp @@ -346,15 +346,15 @@ void QNunchukRoom::slotFinishedDownloadTransaction(nunchuk::RoomTransaction room rawtx.data()->setRoomId(id()); rawtx.data()->setInitEventId(QString::fromStdString(room_tx.get_init_event_id())); target.data()->setTransaction(rawtx); + if(conversation()){ + conversation()->updateTransaction(cons, target); + setPinTransaction(conversation()->pinTransaction()); + } } } if(!isDownloaded()){ startGetPendingTxs(); } - if(conversation()){ - conversation()->updateTransaction(cons, target); - setPinTransaction(conversation()->pinTransaction()); - } if(AppModel::instance()->transactionInfo() && target.data()->transaction() && (qUtils::strCompare(QString::fromStdString(room_tx.get_init_event_id()), target.data()->transaction()->initEventId()))) { AppModel::instance()->setTransactionInfo(target.data()->transactionPtr()); @@ -889,7 +889,9 @@ void QNunchukRoom::downloadTransactionThread(Conversation cons, const QString &r nunchuk::Transaction tx = bridge::nunchukGetOriginTransaction(QString::fromStdString(room_tx.get_wallet_id()), QString::fromStdString(room_tx.get_tx_id()), txWarning); - emit signalFinishedDownloadTransaction(room_tx, tx, cons); + if((int)EWARNING::WarningType::NONE_MSG == txWarning.type() && tx.get_txid() != ""){ + emit signalFinishedDownloadTransaction(room_tx, tx, cons); + } } } }); diff --git a/Models/Premiums/QAssistedDraftWallets.cpp b/Models/Premiums/QAssistedDraftWallets.cpp index e7b97001..a9419dec 100644 --- a/Models/Premiums/QAssistedDraftWallets.cpp +++ b/Models/Premiums/QAssistedDraftWallets.cpp @@ -145,6 +145,21 @@ bool QAssistedDraftWallets::RequestAddOrUpdateAKeyToDraftWallet(StructAddHardwar data["xpub"] = single->xpub(); data["pubkey"] = single->publickey(); data["type"] = qUtils::GetSignerTypeString(single->singleSigner().get_type()); + auto dash = QGroupWallets::instance()->GetDashboard(hardware.mGroupId); + if (dash) { + if (dash->allowInheritance()) { // Request from Alert DashBoard + if (dash->nInfo() == 4 && hardware.mKeyIndex == 0) { + tags.append("INHERITANCE"); + } + else if (dash->nInfo() == 5 && (hardware.mKeyIndex == 0 || hardware.mKeyIndex == 1)) { + tags.append("INHERITANCE"); + } + } + } + if (hardware.mTags.contains("INHERITANCE") && !tags.contains("INHERITANCE")) { // Request receive from Mobile Banner + tags.append("INHERITANCE"); + } + data["name"] = tags.contains("INHERITANCE") ? data["name"].toString() + "(inh.)" : data["name"].toString(); data["tags"] = tags; data["tapsigner"] = {}; if (hardware.mKeyIndex >= 0) { @@ -153,19 +168,19 @@ bool QAssistedDraftWallets::RequestAddOrUpdateAKeyToDraftWallet(StructAddHardwar else { data["key_index"] = {}; } + DBG_INFO << data; bool isDuplicateKey {false}; bool ret {false}; QString error_msg; if (hardware.mGroupId.isEmpty()) { ret = Draco::instance()->assistedWalletAddKey(hardware.mRequestId, data, isDuplicateKey, error_msg); - DBG_INFO << data << ret << isDuplicateKey; + DBG_INFO << ret << isDuplicateKey; } else { - auto dash = QGroupWallets::instance()->GetDashboard(hardware.mGroupId); if (dash && dash->isDraftWallet()) { ret = Draco::instance()->assistedWalletAddKey(hardware.mRequestId, data, isDuplicateKey, error_msg); } else { ret = Byzantine::instance()->DraftWalletAddKey(hardware.mGroupId, hardware.mRequestId, data, isDuplicateKey, error_msg); - DBG_INFO << data << ret << error_msg << isDuplicateKey; + DBG_INFO << ret << error_msg << isDuplicateKey; } if (ret) { if (auto dashboard = QGroupWallets::instance()->dashboardInfoPtr()) { @@ -211,6 +226,21 @@ bool QAssistedDraftWallets::RequestAddOrUpdateReuseKeyToDraftWallet(StructAddHar data["xpub"] = QString::fromStdString(keyresued.get_xpub()); data["pubkey"] = QString::fromStdString(keyresued.get_public_key()); data["type"] = qUtils::GetSignerTypeString(keyresued.get_type()); + auto dash = QGroupWallets::instance()->GetDashboard(hardware.mGroupId); + if (dash) { + if (dash->allowInheritance()) { // Request from Alert Dasboard + if (dash->nInfo() == 4 && hardware.mKeyIndex == 0) { + tags.append("INHERITANCE"); + } + else if (dash->nInfo() == 5 && (hardware.mKeyIndex == 0 || hardware.mKeyIndex == 1)) { + tags.append("INHERITANCE"); + } + } + } + if (hardware.mTags.contains("INHERITANCE") && !tags.contains("INHERITANCE")) { // Request receive from Mobile Banner + tags.append("INHERITANCE"); + } + data["name"] = tags.contains("INHERITANCE") ? data["name"].toString() + "(inh.)" : data["name"].toString(); data["tags"] = tags; data["tapsigner"] = {}; if (hardware.mKeyIndex >= 0) { @@ -219,20 +249,21 @@ bool QAssistedDraftWallets::RequestAddOrUpdateReuseKeyToDraftWallet(StructAddHar else { data["key_index"] = {}; } + DBG_INFO << data; bool isDuplicateKey {false}; bool ret {false}; QString error_msg; if (hardware.mGroupId.isEmpty()) { ret = Draco::instance()->assistedWalletAddKey(hardware.mRequestId, data, isDuplicateKey, error_msg); - DBG_INFO << data << ret << isDuplicateKey; + DBG_INFO << ret << isDuplicateKey << "Honybadger/IronHand"; } else { - auto dash = QGroupWallets::instance()->GetDashboard(hardware.mGroupId); if (dash && dash->isDraftWallet()) { ret = Draco::instance()->assistedWalletAddKey(hardware.mRequestId, data, isDuplicateKey, error_msg); + DBG_INFO << ret << isDuplicateKey << "Honybadger/IronHand"; } else { ret = Byzantine::instance()->DraftWalletAddKey(hardware.mGroupId, hardware.mRequestId, data, isDuplicateKey, error_msg); - DBG_INFO << data << ret << error_msg << isDuplicateKey; + DBG_INFO << ret << error_msg << isDuplicateKey << "Not Honybadger/IronHand"; } if (ret) { if (auto dashboard = QGroupWallets::instance()->dashboardInfoPtr()) { @@ -270,6 +301,10 @@ void QAssistedDraftWallets::addRequest(const QJsonArray &requests, const QString hardware.mGroupId = group_id; hardware.mRequestId = request_id; hardware.mKeyIndex = key_index; + hardware.mTags.clear(); + for (auto tag: tags) { + hardware.mTags.append(tag.toString()); + } m_requests.insert(key, hardware); } } @@ -424,16 +459,25 @@ bool QAssistedDraftWallets::requestKeyReplacement(QSingleSignerPtr signer) DBG_INFO << single->tag() << "tag:" << single->tag() << m_mode << hardware.mGroupId; if (warningmsg.type() == (int)EWARNING::WarningType::NONE_MSG) { QJsonArray tags; - if (!single->tag().isEmpty()) { - tags.append(single->tag()); + if (!single->tags().isEmpty()) { + QStringList list = single->tags(); + list.removeAll("INHERITANCE"); + for (auto tag : list) { + tags.append(tag); + } } - data["name"] = single->name() != "" ? single->name() : titleCase(single->tag()); data["xfp"] = single->masterFingerPrint(); data["derivation_path"] = single->derivationPath(); data["xpub"] = single->xpub(); data["pubkey"] = single->publickey(); data["type"] = qUtils::GetSignerTypeString(single->singleSigner().get_type()); + auto dashboard = QGroupWallets::instance()->dashboardInfoPtr(); + if (dashboard.isNull()) return false; + if (dashboard->isInheritance() || hardware.mTags.contains("INHERITANCE")) { // Request receive from Mobile Banner + tags.append("INHERITANCE"); + } + data["name"] = tags.contains("INHERITANCE") ? data["name"].toString() + "(inh.)" : data["name"].toString(); data["tags"] = tags; data["tapsigner"] = {}; if (hardware.mKeyIndex >= 0) { @@ -442,10 +486,7 @@ bool QAssistedDraftWallets::requestKeyReplacement(QSingleSignerPtr signer) else { data["key_index"] = {}; } - bool ret {false}; - if (auto dashboard = QGroupWallets::instance()->dashboardInfoPtr()) { - ret = dashboard->FinishKeyReplacement(data); - } + bool ret = dashboard->FinishKeyReplacement(data); if (ret) { AppModel::instance()->setAddSignerWizard(3); return true; diff --git a/Models/Premiums/QAssistedDraftWallets.h b/Models/Premiums/QAssistedDraftWallets.h index 85f347cf..5f73d999 100644 --- a/Models/Premiums/QAssistedDraftWallets.h +++ b/Models/Premiums/QAssistedDraftWallets.h @@ -40,6 +40,7 @@ struct StructAddHardware QString mGroupId {}; QString mRequestId {}; int mKeyIndex {-1}; + QStringList mTags {}; }; class QAssistedDraftWallets : public QSwitchAPI { diff --git a/Models/Premiums/QGroupDashboard.cpp b/Models/Premiums/QGroupDashboard.cpp index bd02d3c6..6eab5900 100644 --- a/Models/Premiums/QGroupDashboard.cpp +++ b/Models/Premiums/QGroupDashboard.cpp @@ -371,9 +371,11 @@ void QGroupDashboard::UpdateKeys(const QJsonObject &data) obj["ourAccount"] = false; if (our_id == added_by_user_id) { obj["ourAccount"] = true; + WalletsMng->UpdateSigner(obj); } signers.append(obj); } + emit WalletsMng->signalUpdateSigner(); for (QJsonValue js : signers) { QJsonObject signer = js.toObject(); @@ -626,10 +628,11 @@ bool QGroupDashboard::canEntryClickAlert() payload["keyname"] = signer["name"].toString(); bool is_inheritance = signer["tapsigner"].toObject()["is_inheritance"].toBool(); payload["is_inheritance"] = is_inheritance; + payload["key_index"] = signer["key_index"].toInt(); auto alert = alertJson(); alert["payload"] = payload; setAlertId(alert); - return !is_inheritance; + return true; } default: break; @@ -884,6 +887,12 @@ void QGroupDashboard::requestShowLetAddYourKeys() QEventProcessor::instance()->sendEvent(E::EVT_SHOW_GROUP_WALLET_CONFIG_REQUEST); } +void QGroupDashboard::requestShowReplacementKey() +{ + setFlow((int)AlertEnum::E_Alert_t::KEY_REPLACEMENT_PENDING); + QEventProcessor::instance()->sendEvent(E::EVT_SHOW_GROUP_WALLET_CONFIG_REQUEST); +} + bool QGroupDashboard::isDowngrade(QString email_or_username, QString roleNew) { QString roleOld = ""; @@ -1166,8 +1175,8 @@ bool QGroupDashboard::FinishKeyReplacement(const QJsonObject &requestBody) else { ret = Byzantine::instance()->ReplaceKey(groupId(), wallet_id(), xfp, verifyToken, requestBody, error_msg); } + DBG_INFO << ret << error_msg << requestBody; if(ret){ - DBG_INFO << ret << error_msg; // Handle preparing model here. GetAlertsInfo(); GetHealthCheckInfo(); @@ -1181,7 +1190,6 @@ bool QGroupDashboard::FinishKeyReplacement(const QJsonObject &requestBody) bool QGroupDashboard::canReplaceKey() { auto type = static_cast(alertJson()["type"].toInt()); - DBG_INFO << alertJson(); if (type == AlertEnum::E_Alert_t::KEY_REPLACEMENT_PENDING) { QJsonObject payload = alertJson()["payload"].toObject(); return payload["can_replace"].toBool(); @@ -1189,6 +1197,16 @@ bool QGroupDashboard::canReplaceKey() return false; } +bool QGroupDashboard::isInheritance() +{ + auto type = static_cast(alertJson()["type"].toInt()); + if (type == AlertEnum::E_Alert_t::KEY_REPLACEMENT_PENDING) { + QJsonObject payload = alertJson()["payload"].toObject(); + return payload["is_inheritance"].toBool(); + } + return false; +} + QVariantList QGroupDashboard::keys() const { return m_keys; diff --git a/Models/Premiums/QGroupDashboard.h b/Models/Premiums/QGroupDashboard.h index fb4b3ac0..9f151fe4 100644 --- a/Models/Premiums/QGroupDashboard.h +++ b/Models/Premiums/QGroupDashboard.h @@ -194,6 +194,7 @@ class QGroupDashboard : public QBasePremium bool FinishKeyReplacement(const QJsonObject &requestBody); bool canReplaceKey(); + bool isInheritance(); bool canEntryClickAlert(); @@ -231,6 +232,7 @@ public slots: void byzantineRoomDeleted(QString room_id, QString group_id); void requestShowLetAddYourKeys(); + void requestShowReplacementKey(); bool isDowngrade(QString email_or_username, QString roleNew); private: bool deviceExport(const QStringList tags, nunchuk::SignerType type); diff --git a/Models/Premiums/QUserWallets.cpp b/Models/Premiums/QUserWallets.cpp index 1429fd8f..843a9b07 100644 --- a/Models/Premiums/QUserWallets.cpp +++ b/Models/Premiums/QUserWallets.cpp @@ -58,6 +58,12 @@ void QUserWallets::GetDraftWallet() dashboard->setGroupInfo(info); DBG_INFO << info; mDashboard = dashboard; + QJsonArray signers = draft_wallet["signers"].toArray(); + for (auto js : signers) { + QJsonObject signer = js.toObject(); + WalletsMng->UpdateSigner(signer); + } + emit WalletsMng->signalUpdateSigner(); } } } diff --git a/Models/Premiums/QWalletManagement.cpp b/Models/Premiums/QWalletManagement.cpp index 8879e682..4ab05b27 100644 --- a/Models/Premiums/QWalletManagement.cpp +++ b/Models/Premiums/QWalletManagement.cpp @@ -31,6 +31,7 @@ QWalletManagement::QWalletManagement() { QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); connect(this, &QWalletManagement::getListWalletFinish, this, &QWalletManagement::slotGetListWalletFinish, Qt::QueuedConnection); + connect(this, &QWalletManagement::signalUpdateSigner, this, &QWalletManagement::slotUpdateSigner, Qt::QueuedConnection); } QWalletManagement::~QWalletManagement() @@ -270,6 +271,30 @@ void QWalletManagement::GetListWallet(int mode) UpdateSyncWalletFlows(); } +void QWalletManagement::UpdateSigner(const QJsonObject &signer) +{ + QJsonObject js_signer = signer; + QString xfp = js_signer["xfp"].toString(); + QString name = js_signer["name"].toString(); + bool is_visible = js_signer["is_visible"].toBool(); + QJsonArray wtags = js_signer["tags"].toArray(); + QWarningMessage msgIn; + nunchuk::MasterSigner master_signer = bridge::nunchukGetOriginMasterSigner(xfp, msgIn); + if (msgIn.type() == (int)EWARNING::WarningType::NONE_MSG) { + std::vector tags; // get tags from api signer.tags + for (QJsonValue tag : wtags) { + QString js_tag = tag.toString(); + tags.push_back(SignerTagFromStr(js_tag.toStdString())); + } + // Do update + master_signer.set_name(name.toStdString()); + master_signer.set_tags(tags); + master_signer.set_visible(is_visible); + msgIn.resetWarningMessage(); + bridge::UpdateMasterSigner(master_signer, msgIn); + } +} + void QWalletManagement::UpdateSyncWalletFlows() { QMap server_signers; @@ -723,3 +748,11 @@ void QWalletManagement::slotGetListWalletFinish() QGroupWallets::instance()->findPermissionAccount(); AppModel::instance()->requestOnboarding(); } + +void QWalletManagement::slotUpdateSigner() +{ + QMasterSignerListModelPtr mastersigners = bridge::nunchukGetMasterSigners(); + if(mastersigners){ + AppModel::instance()->setMasterSignerList(mastersigners); + } +} diff --git a/Models/Premiums/QWalletManagement.h b/Models/Premiums/QWalletManagement.h index e5d4c298..12bbe206 100644 --- a/Models/Premiums/QWalletManagement.h +++ b/Models/Premiums/QWalletManagement.h @@ -16,6 +16,7 @@ class QWalletManagement : public QObject explicit QWalletManagement(); virtual ~QWalletManagement(); void GetListWallet(int mode); + void UpdateSigner(const QJsonObject &signer); void UpdateSyncWalletFlows(); QString UpdateSyncWalletFlows(bool yes, bool no); WalletIdList wallets() const; @@ -38,8 +39,10 @@ class QWalletManagement : public QObject int activeSize() const; public slots: void slotGetListWalletFinish(); + void slotUpdateSigner(); signals: void getListWalletFinish(); + void signalUpdateSigner(); private: QMap mWallets; QMap mWalletsInfo; diff --git a/Models/Premiums/QWalletServicesTag.cpp b/Models/Premiums/QWalletServicesTag.cpp index 4ae2b670..408c0af1 100644 --- a/Models/Premiums/QWalletServicesTag.cpp +++ b/Models/Premiums/QWalletServicesTag.cpp @@ -411,7 +411,7 @@ int QWalletServicesTag::inheritanceDownloadBackup(const QString &magic, const QS std::vector base64vec(base64bin.begin(), base64bin.end()); QString key_name = key["key_name"].toString(); QString derivation_path = key["derivation_path"].toString(); - auto master_signer_ptr = bridge::ImportTapsignerMasterSigner(base64vec, backup_key, key_name, false, msg); + auto master_signer_ptr = bridge::ImportBackupKey(base64vec, backup_key, key_name, false, msg); if (master_signer_ptr) { msg.resetWarningMessage(); auto master_signer = master_signer_ptr->originMasterSigner(); diff --git a/Models/Worker.cpp b/Models/Worker.cpp index 90ae31d0..8fbe231e 100644 --- a/Models/Worker.cpp +++ b/Models/Worker.cpp @@ -1181,7 +1181,12 @@ void Controller::slotFinishBackupWallet(QString what, void Controller::slotFinishBalanceChanged(const QString &id, const qint64 balance) { - startSyncWalletDb(id); + if(AppModel::instance()->walletInfo() && qUtils::strCompare(id, AppModel::instance()->walletInfo()->id())){ + startGetTransactionHistory(id); + } + if(AppModel::instance()->walletList()){ + AppModel::instance()->walletList()->updateBalance(id, balance); + } } void Controller::slotFinishTransactionChanged(const QString &tx_id, diff --git a/Qml/Components/customizes/Buttons/QRadioButtonTypeA.qml b/Qml/Components/customizes/Buttons/QRadioButtonTypeA.qml index c0952050..c4f5ae6a 100644 --- a/Qml/Components/customizes/Buttons/QRadioButtonTypeA.qml +++ b/Qml/Components/customizes/Buttons/QRadioButtonTypeA.qml @@ -18,6 +18,8 @@ * * **************************************************************************/ import QtQuick 2.4 +import QtGraphicalEffects 1.0 +import QtQuick.Controls 2.0 import "../../origins" import "../../customizes/Texts" @@ -36,15 +38,20 @@ Row { font.family: fontFamily font.pixelSize: fontPixelSize font.weight: fontWeight - color: "#031F2B" + color: radioRoot.enabled ? "#031F2B" : "#666666" anchors.verticalCenter: parent.verticalCenter width: parent.width - icon.width - 8 } - QIcon { - id: icon - iconSize: 24 - source: radioRoot.selected ? "qrc:/Images/Images/radio-selected-dark.svg" : "qrc:/Images/Images/radio-dark.svg" + ColorOverlay { anchors.verticalCenter: parent.verticalCenter + width: 24 + height: 24 + source: QIcon { + id: icon + iconSize: 24 + source: radioRoot.selected ? "qrc:/Images/Images/radio-selected-dark.svg" : "qrc:/Images/Images/radio-dark.svg" + } + color: radioRoot.enabled ? "#031F2B" : "#666666" MouseArea { id: mouse anchors.fill: parent diff --git a/Qml/Components/customizes/Chats/QConversationPage.qml b/Qml/Components/customizes/Chats/QConversationPage.qml index 022c882a..86bea31d 100644 --- a/Qml/Components/customizes/Chats/QConversationPage.qml +++ b/Qml/Components/customizes/Chats/QConversationPage.qml @@ -576,138 +576,122 @@ Row { id: conversationfooter width: parent.width height: 80 - Rectangle { - id: bgfooter - width: parent.width - 4 - height: parent.height - 4 - anchors.centerIn: parent - color: "#FFFFFF" - visible: false - } - DropShadow { - anchors.fill: bgfooter - verticalOffset: 2 - cached: true - radius: 8 - samples: 16 - color: Qt.rgba(0, 0, 0, 0.15) - source: bgfooter - QTextField { - id: messageField - width: parent.width - (!RoomWalletData.isIgnoredCollabWallet ? 108 : 48) - height: 48 - anchors.left: parent.left - anchors.leftMargin: 24 - anchors.verticalCenter: parent.verticalCenter - placeholderText: "Type your message..." - Keys.onReturnPressed: { conversationfooter.sendMessage() } - Keys.onEnterPressed: { conversationfooter.sendMessage() } - clip: true - color: "#031F2B" - font.pixelSize: 16 - selectByMouse: true - background: Rectangle { - anchors.fill: parent - radius: 8 - border.color: attachedFile.containsDrag ? "red" : "#DEDEDE" - color: "#FFFFFF" - } - onActiveFocusChanged:{ if(activeFocus){RoomWalletData.currentRoom.markFiveMessagesAsRead()}} - DropArea { - id: attachedFile - property string fileLocalPath: "" - property int file_mimType: filePlaceHolder._FILE_OTHER - enabled: attachmentSupported - anchors.fill: parent - onDropped: { - attachedFile.fileLocalPath = "" - if(drop.urls.length > 1){ AppModel.showToast(-696, STR.STR_QML_696, EWARNING.EXCEPTION_MSG); } - else{ - var fileDropped = drop.urls[0] - attachedFile.validateFileExtension(fileDropped) - attachedFile.fileLocalPath = fileDropped + Item { + anchors.fill: parent + anchors.margins: 15 + Row { + anchors.fill: parent + spacing: 12 + Item { + width: parent.width - (selectFileBtn.visible ? (selectFileBtn.width+12) : 0) - (collabWalletBtn.visible ? (collabWalletBtn.width+12) : 0) + height: parent.height + QTextField { + id: messageField + anchors.fill: parent + placeholderText: "Type your message..." + Keys.onReturnPressed: { conversationfooter.sendMessage() } + Keys.onEnterPressed: { conversationfooter.sendMessage() } + clip: true + color: "#031F2B" + font.pixelSize: 16 + selectByMouse: true + background: Rectangle { + anchors.fill: parent + radius: 8 + border.color: attachedFile.containsDrag ? "red" : "#DEDEDE" + color: "#FFFFFF" } - } - - function validateFileExtension(filePath) { - const filters_image = ['png', 'jpg', 'jpeg']; - const filters_video = ['mp4']; - const filters_other = ['mp4']; - var extension = filePath.split('.').pop(); - if(filters_image.includes(extension.toLowerCase())) { attachedFile.file_mimType = filePlaceHolder._FILE_IMAGE} - else if(filters_video.includes(extension.toLowerCase())) { attachedFile.file_mimType = filePlaceHolder._FILE_VIDEO} - else{ attachedFile.file_mimType = filePlaceHolder._FILE_OTHER } - } - } - } - - Rectangle { - width: 60 - height: 60 - radius: 4 - anchors.right: messageField.right - anchors.rightMargin: messageField.leftPadding - anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: -height/2 - visible: attachmentSupported && (attachedFile.fileLocalPath !== "") - QAttachment { - id: filePlaceHolder - width: 60 - height: 60 - sourceSize.width: filePlaceHolder.width - sourceSize.height: filePlaceHolder.height - fillMode: Image.PreserveAspectCrop - file_mimeType: attachedFile.file_mimType - file_path: attachedFile.fileLocalPath - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: 60 - height: 60 - radius: 4 + onActiveFocusChanged:{ if(activeFocus){RoomWalletData.currentRoom.markFiveMessagesAsRead()}} + DropArea { + id: attachedFile + property string fileLocalPath: "" + property int file_mimType: filePlaceHolder._FILE_OTHER + enabled: attachmentSupported + anchors.fill: parent + onDropped: { + attachedFile.fileLocalPath = "" + if(drop.urls.length > 1){ AppModel.showToast(-696, STR.STR_QML_696, EWARNING.EXCEPTION_MSG); } + else{ + var fileDropped = drop.urls[0] + attachedFile.validateFileExtension(fileDropped) + attachedFile.fileLocalPath = fileDropped + } + } + function validateFileExtension(filePath) { + const filters_image = ['png', 'jpg', 'jpeg']; + const filters_video = ['mp4']; + const filters_other = ['mp4']; + var extension = filePath.split('.').pop(); + if(filters_image.includes(extension.toLowerCase())) { attachedFile.file_mimType = filePlaceHolder._FILE_IMAGE} + else if(filters_video.includes(extension.toLowerCase())) { attachedFile.file_mimType = filePlaceHolder._FILE_VIDEO} + else{ attachedFile.file_mimType = filePlaceHolder._FILE_OTHER } + } } } Rectangle { - anchors.fill: parent + width: 60 + height: 60 radius: 4 - opacity: 0.6 - visible: attachMouse.containsMouse || removeAttachMouse.containsMouse - } - } - MouseArea { - id: attachMouse - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - } - QImage { - width: 32 - height: 32 - sourceSize.width: width - sourceSize.height: height - source: "qrc:/Images/Images/OnlineMode/remove-button.png" - anchors.left: parent.right - anchors.leftMargin: -16 - anchors.bottom: parent.top - anchors.bottomMargin: -16 - visible: attachMouse.containsMouse || removeAttachMouse.containsMouse - MouseArea { - id: removeAttachMouse - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked:{ attachedFile.fileLocalPath = ""} + anchors.right: messageField.right + anchors.rightMargin: messageField.leftPadding + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -height/2 + visible: attachmentSupported && (attachedFile.fileLocalPath !== "") + z: 200 + QAttachment { + id: filePlaceHolder + width: 60 + height: 60 + sourceSize.width: filePlaceHolder.width + sourceSize.height: filePlaceHolder.height + fillMode: Image.PreserveAspectCrop + file_mimeType: attachedFile.file_mimType + file_path: attachedFile.fileLocalPath + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: 60 + height: 60 + radius: 4 + } + } + Rectangle { + anchors.fill: parent + radius: 4 + color: "transparent" + border.color: "#000000" + } + opacity: (attachMouse.containsMouse || removeAttachMouse.containsMouse) ? 0.6 : 1 + } + MouseArea { + id: attachMouse + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + } + QImage { + width: 32 + height: 32 + sourceSize.width: width + sourceSize.height: height + source: "qrc:/Images/Images/OnlineMode/remove-button.png" + anchors.left: parent.right + anchors.leftMargin: -16 + anchors.bottom: parent.top + anchors.bottomMargin: -16 + visible: attachMouse.containsMouse || removeAttachMouse.containsMouse + MouseArea { + id: removeAttachMouse + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked:{ + attachedFile.fileLocalPath = "" + } + } + } } } - } - - Row { - spacing: 4 - anchors { - left: messageField.right - leftMargin: 12 - verticalCenter: messageField.verticalCenter - } MouseArea { id: selectFileBtn hoverEnabled: true @@ -761,10 +745,12 @@ Row { } } Loader { - sourceComponent: RoomWalletData.roomWalletCreated ? btnCreateTransaction : (RoomWalletData.isIgnoredCollabWallet ? null :btnCreateSharedWallet) + id: collabWalletBtn + width: childrenRect.width + height: childrenRect.height + sourceComponent: RoomWalletData.roomWalletCreated ? btnCreateTransaction : (AppSetting.enableColab ? btnCreateSharedWallet : null) anchors.verticalCenter: parent.verticalCenter - visible: RoomWalletData.roomWalletCreated ? (RoomWalletData.currentRoom && (RoomWalletData.currentRoom.roomType !== NUNCHUCKTYPE.SUPPORT_ROOM)) - : RoomWalletData.isIgnoredCollabWallet + visible: RoomWalletData.roomWalletCreated ? (RoomWalletData.currentRoom && (RoomWalletData.currentRoom.roomType !== NUNCHUCKTYPE.SUPPORT_ROOM)) : AppSetting.enableColab } } } diff --git a/Qml/Components/customizes/QHomePendingWallet.qml b/Qml/Components/customizes/QHomePendingWallet.qml index ed1f15ed..e7b589a2 100644 --- a/Qml/Components/customizes/QHomePendingWallet.qml +++ b/Qml/Components/customizes/QHomePendingWallet.qml @@ -288,13 +288,6 @@ Rectangle { } else if (modelData.type === AlertType.TRANSFER_FUNDS) { _info1.open() _info1.contentText = STR.STR_QML_1346 - } else if (modelData.type === AlertType.KEY_REPLACEMENT_PENDING) { - var alert = GroupWallet.dashboardInfo.alert - var is_inheritance = alert.payload.is_inheritance - if (is_inheritance) { - _info1.open() - _info1.contentText = STR.STR_QML_1353 - } } else if (modelData.type === AlertType.SETUP_INHERITANCE_PLAN) { _info1.open() _info1.contentText = STR.STR_QML_1355 diff --git a/Qml/Components/customizes/QSendDelegate.qml b/Qml/Components/customizes/QSendDelegate.qml index 62be8551..74f84cca 100644 --- a/Qml/Components/customizes/QSendDelegate.qml +++ b/Qml/Components/customizes/QSendDelegate.qml @@ -53,6 +53,7 @@ Rectangle { signal qrCodeRequest() signal removeItemRequest() signal favoriteRequest() + signal sendAllRemainingRequest() function setFavorite(fav) { if ((fav.toType === "Address") || (fav.toType === "Wallet")){ @@ -212,6 +213,17 @@ Rectangle { } } } + QButtonTextLink { + height: 24 + label: "Send all remaining" + displayIcon: false + btnText.font.underline: true + anchors.top: amountInput.top + anchors.right: amountInput.right + onButtonClicked: { + sendAllRemainingRequest() + } + } } } RegExpValidator { id: intvalidator; regExp: /^[1-9][0-9]*$/ } @@ -253,7 +265,7 @@ Rectangle { "toType": "Input", "toAddress": "", "toAddressDisplay": "", - "toAmount": "" + "toAmount": toAmount }) setFavoriteInput(inputObject) } diff --git a/Qml/Components/customizes/QWarningBgMulti.qml b/Qml/Components/customizes/QWarningBgMulti.qml new file mode 100644 index 00000000..db58ed0c --- /dev/null +++ b/Qml/Components/customizes/QWarningBgMulti.qml @@ -0,0 +1,69 @@ +/************************************************************************** + * This file is part of the Nunchuk software (https://nunchuk.io/) * + * Copyright (C) 2020-2022 Enigmo * + * Copyright (C) 2022 Nunchuk * + * * + * 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 . * + * * + **************************************************************************/ +import QtQuick 2.12 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Styles 1.4 +import QtGraphicalEffects 1.0 +import HMIEVENTS 1.0 +import EWARNING 1.0 +import NUNCHUCKTYPE 1.0 +import DataPool 1.0 +import "../../Components/origins" +import "../../Components/customizes/Texts" +import "../../../localization/STR_QML.js" as STR + +Rectangle { + id: _root + width: 528 + height: 60 + color: "#EAEAEA" + radius: 12 + property string icon: "" + property alias txt: _txt + property int iSize: 24 + + Row { + anchors.fill: parent + anchors.margins: 12 + spacing: 12 + QIcon { + iconSize: iSize + source: icon + anchors.verticalCenter: parent.verticalCenter + } + QLato { + id: _txt + width: _root.width - 2*12 - iSize - 12 + height: 60 - 2*12 + wrapMode: Text.WordWrap + lineHeight: 28 + lineHeightMode: Text.FixedHeight + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + anchors.verticalCenter: parent.verticalCenter + onLinkActivated: Qt.openUrlExternally(link) + MouseArea { + anchors.fill: parent + cursorShape: _txt.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + acceptedButtons: Qt.NoButton + } + } + } +} diff --git a/Qml/Components/customizes/Signers/QAddRequestKey.qml b/Qml/Components/customizes/Signers/QAddRequestKey.qml index 46e62fbc..c39e3410 100644 --- a/Qml/Components/customizes/Signers/QAddRequestKey.qml +++ b/Qml/Components/customizes/Signers/QAddRequestKey.qml @@ -42,7 +42,7 @@ Item { anchors.fill: parent sourceComponent: { if(modelData.type === "NFC") { - return modelData.has ? tapsignerAdded : tapsignerAdd + return modelData.has ? inheritanceAdded : inheritanceAdd } else if (modelData.type === "SERVER") { return modelData.has ? serverAdded : serverAdd @@ -61,12 +61,13 @@ Item { } Component { - id: tapsignerAdd + id: inheritanceAdd QDashRectangle { anchors.fill: parent radius: 8 borderWitdh: 2 borderColor: "#031F2B" + Row { anchors { fill: parent @@ -81,12 +82,25 @@ Item { anchors.verticalCenter: parent.verticalCenter color: "#F5F5F5" } - QLato { + Column { + height: childrenRect.height width: 150 - text: STR.STR_QML_956 anchors.verticalCenter: parent.verticalCenter - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter + spacing: 4 + QLato { + width: 150 + height: 28 + text: STR.STR_QML_954.arg(modelData.key_index + 1) + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + QBadge { + width: 77 + height: 16 + fontSize: 10 + text: STR.STR_QML_1600 + color: "#EAEAEA" + } } } QTextButton { @@ -107,7 +121,7 @@ Item { } } Component { - id: tapsignerAdded + id: inheritanceAdded Rectangle { anchors.fill: parent color: "#A7F0BA" diff --git a/Qml/Screens/LocalMode/SCR_SEND_ADD_DESTINATION.qml b/Qml/Screens/LocalMode/SCR_SEND_ADD_DESTINATION.qml index 05fb9b66..257f973c 100644 --- a/Qml/Screens/LocalMode/SCR_SEND_ADD_DESTINATION.qml +++ b/Qml/Screens/LocalMode/SCR_SEND_ADD_DESTINATION.qml @@ -216,6 +216,15 @@ QScreen { requestFilterOutAddress() requestFilterOutWallet() } + onSendAllRemainingRequest: { + var remaining = AppModel.walletInfo.walletBalance + for(var i = 0; i < destination.model; i++){ + if(i !== index){ + remaining -= destination.itemAt(i).toAmount + } + } + destination.itemAt(index).toAmount = remaining + } Component.onCompleted: { if(index < destinations.length){ @@ -614,7 +623,7 @@ QScreen { "toType": "Address", "toAddress": savedAddressItem.dataValue, "toAddressDisplay": savedAddressItem.dataLabel, - "toAmount": "" + "toAmount": destination.itemAt(favoritesPopup.addressRequestIndex).toAmount }) destination.itemAt(favoritesPopup.addressRequestIndex).setFavoriteSelected(inputObject) } @@ -690,7 +699,7 @@ QScreen { "toType": "Wallet", "toAddress": modelData.wallet_Address, "toAddressDisplay": modelData.wallet_name, - "toAmount": "" + "toAmount": destination.itemAt(favoritesPopup.addressRequestIndex).toAmount }) destination.itemAt(favoritesPopup.addressRequestIndex).setFavoriteSelected(inputObject) } @@ -907,6 +916,7 @@ QScreen { label: STR.STR_QML_059 onButtonClicked: { addNewAddress.close() + requestFilterOutAddress() } } bottomRight: Row { @@ -944,6 +954,7 @@ QScreen { favValueEdit.isValid = true favValueEdit.errorText = "" AppSetting.addFavoriteAddress(favLabelEdit.textInputted, favValueEdit.textInputted) + requestFilterOutAddress() addNewAddress.close() } } @@ -952,6 +963,7 @@ QScreen { } onCloseClicked: { addNewAddress.close() + requestFilterOutAddress() } Column { width: 540 @@ -1079,6 +1091,7 @@ QScreen { onConfirmNo: close() onConfirmYes: { AppSetting.removeFavoriteAddress(addNewAddress.dataLabel, addNewAddress.dataValue) + requestFilterOutAddress() addNewAddress.close() savedAddress.close() close() diff --git a/Qml/Screens/OnlineMode/AddHardwareKeys/QImportantNoticeAboutPassphrase.qml b/Qml/Screens/OnlineMode/AddHardwareKeys/QImportantNoticeAboutPassphrase.qml new file mode 100644 index 00000000..c1c0577c --- /dev/null +++ b/Qml/Screens/OnlineMode/AddHardwareKeys/QImportantNoticeAboutPassphrase.qml @@ -0,0 +1,85 @@ +/************************************************************************** + * This file is part of the Nunchuk software (https://nunchuk.io/) * + * Copyright (C) 2020-2022 Enigmo * + * Copyright (C) 2022 Nunchuk * + * * + * 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 . * + * * + **************************************************************************/ +import QtQuick 2.4 +import QtQuick.Controls 2.3 +import QtGraphicalEffects 1.12 +import HMIEVENTS 1.0 +import EWARNING 1.0 +import NUNCHUCKTYPE 1.0 +import DataPool 1.0 +import "../../../Components/origins" +import "../../../Components/customizes" +import "../../../Components/customizes/Chats" +import "../../../Components/customizes/Texts" +import "../../../Components/customizes/Buttons" +import "../../../../localization/STR_QML.js" as STR + +QOnScreenContentTypeA { + width: popupWidth + height: popupHeight + anchors.centerIn: parent + label.text: STR.STR_QML_1608 + extraHeader: Item {} + onCloseClicked: closeTo(NUNCHUCKTYPE.CURRENT_TAB) + signal requestBack() + signal requestNext() + signal requestWithout() + content: Column { + width: 539 + height: 196 + spacing: 24 + QLato { + width: 539 + height: paintedHeight + text: STR.STR_QML_1609 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + lineHeight: 28 + lineHeightMode: Text.FixedHeight + } + } + + onPrevClicked: requestBack() + bottomRight: Row { + spacing: 12 + QTextButton { + width: 261 + height: 48 + label.text: STR.STR_QML_1610 + label.font.pixelSize: 16 + type: eTypeB + onButtonClicked: { + requestNext() + } + } + QTextButton { + width: 300 + height: 48 + label.text: STR.STR_QML_1611 + label.font.pixelSize: 16 + type: eTypeE + visible: GroupWallet.dashboardInfo !== null && GroupWallet.dashboardInfo.isShowDashBoard + onButtonClicked: { + requestWithout() + } + } + } +} diff --git a/Qml/Screens/OnlineMode/AddHardwareKeys/QPassphraseBackupReminder.qml b/Qml/Screens/OnlineMode/AddHardwareKeys/QPassphraseBackupReminder.qml new file mode 100644 index 00000000..4c191123 --- /dev/null +++ b/Qml/Screens/OnlineMode/AddHardwareKeys/QPassphraseBackupReminder.qml @@ -0,0 +1,84 @@ +/************************************************************************** + * This file is part of the Nunchuk software (https://nunchuk.io/) * + * Copyright (C) 2020-2022 Enigmo * + * Copyright (C) 2022 Nunchuk * + * * + * 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 . * + * * + **************************************************************************/ +import QtQuick 2.4 +import QtQuick.Controls 2.3 +import QtGraphicalEffects 1.12 +import HMIEVENTS 1.0 +import EWARNING 1.0 +import NUNCHUCKTYPE 1.0 +import DataPool 1.0 +import "../../../Components/origins" +import "../../../Components/customizes" +import "../../../Components/customizes/Chats" +import "../../../Components/customizes/Texts" +import "../../../Components/customizes/Buttons" +import "../../../../localization/STR_QML.js" as STR + +QOnScreenContentTypeA { + width: popupWidth + height: popupHeight + anchors.centerIn: parent + label.text: STR.STR_QML_1612 + extraHeader: Item {} + onCloseClicked: closeTo(NUNCHUCKTYPE.CURRENT_TAB) + signal requestBack() + signal requestNext() + content: Column { + width: 539 + height: 196 + spacing: 24 + QLato { + width: 539 + height: paintedHeight + text: STR.STR_QML_1613 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + lineHeight: 28 + lineHeightMode: Text.FixedHeight + } + QLato { + width: 539 + height: paintedHeight + text: STR.STR_QML_1614 + font.weight: Font.Bold + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + lineHeight: 28 + lineHeightMode: Text.FixedHeight + } + } + + onPrevClicked: closeTo(NUNCHUCKTYPE.CURRENT_TAB) + bottomRight: Row { + spacing: 12 + QTextButton { + width: 137 + height: 48 + label.text: STR.STR_QML_572 + label.font.pixelSize: 16 + type: eTypeB + onButtonClicked: { + requestNext() + } + } + } +} diff --git a/Qml/Screens/OnlineMode/AddHardwareKeys/QSelectPassPhraseQuestion.qml b/Qml/Screens/OnlineMode/AddHardwareKeys/QSelectPassPhraseQuestion.qml new file mode 100644 index 00000000..1015b3a8 --- /dev/null +++ b/Qml/Screens/OnlineMode/AddHardwareKeys/QSelectPassPhraseQuestion.qml @@ -0,0 +1,105 @@ +/************************************************************************** + * This file is part of the Nunchuk software (https://nunchuk.io/) * + * Copyright (C) 2020-2022 Enigmo * + * Copyright (C) 2022 Nunchuk * + * * + * 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 . * + * * + **************************************************************************/ +import QtQuick 2.4 +import QtQuick.Controls 2.3 +import QtGraphicalEffects 1.12 +import Qt.labs.platform 1.1 +import HMIEVENTS 1.0 +import EWARNING 1.0 +import NUNCHUCKTYPE 1.0 +import DataPool 1.0 +import "../../../Components/origins" +import "../../../Components/customizes" +import "../../../Components/customizes/Chats" +import "../../../Components/customizes/Texts" +import "../../../Components/customizes/Buttons" +import "../../../Components/customizes/QRCodes" +import "../../../Components/customizes/Popups" +import "../../../Components/customizes/services" +import "../../../../localization/STR_QML.js" as STR + +QOnScreenContentTypeA { + id:_content + width: popupWidth + height: popupHeight + anchors.centerIn: parent + label.text: STR.STR_QML_1606 + extraHeader: Item {} + property string option: "" + onCloseClicked: closeTo(NUNCHUCKTYPE.WALLET_TAB) + property var formats: [ + {id: "not-have-a-passphrase", description: STR.STR_QML_1604}, + {id: "have-a-passphrase", description: STR.STR_QML_1605}, + ] + signal requestBack() + signal requestNext() + content: Item { + Column { + anchors { + top: parent.top + topMargin: 0 + } + spacing: 24 + QLato { + width: 539 + height: 28 + text: STR.STR_QML_1607 + wrapMode: Text.WordWrap + lineHeight: 28 + lineHeightMode: Text.FixedHeight + anchors.left: parent.left + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + Column { + id: _colum + spacing: 16 + Repeater { + model: formats + Rectangle { + width: 573 + height: 60 + border.width: 2 + border.color: _radio.selected ? "#000000" : "#DEDEDE" + radius: 12 + QRadioButtonTypeA { + anchors { + left: parent.left + leftMargin: 18 + } + id: _radio + width: 573 + height: 60 + layoutDirection: Qt.RightToLeft + label: modelData.description + selected: option === modelData.id + onButtonClicked: { option = modelData.id; } + } + } + } + } + } + } + onPrevClicked: requestBack() + nextEnable: option !== "" + onNextClicked: { + requestNext() + } +} diff --git a/Qml/Screens/OnlineMode/SCR_ADD_HARDWARE.qml b/Qml/Screens/OnlineMode/SCR_ADD_HARDWARE.qml index 57a4ea3a..03797b7c 100644 --- a/Qml/Screens/OnlineMode/SCR_ADD_HARDWARE.qml +++ b/Qml/Screens/OnlineMode/SCR_ADD_HARDWARE.qml @@ -29,6 +29,11 @@ import "../../../localization/STR_QML.js" as STR QScreen { readonly property int hardwareType: GroupWallet.qIsByzantine ? GroupWallet.qAddHardware : UserWallet.qAddHardware + readonly property int _ASK_PASSPHRASE: 1 + readonly property int _IMPORTANT_NOTICE: 2 + readonly property int _BACKUP_PASSPHRASE: 3 + readonly property int _PASSPHRASE_DONE: 4 + property int _passPhrase: _ASK_PASSPHRASE Loader { width: popupWidth height: popupHeight @@ -37,7 +42,15 @@ QScreen { switch(hardwareType) { case NUNCHUCKTYPE.ADD_LEDGER: return _Ledger case NUNCHUCKTYPE.ADD_TREZOR: return _Trezor - case NUNCHUCKTYPE.ADD_COLDCARD: return _Coldcard + case NUNCHUCKTYPE.ADD_COLDCARD: return function() { + switch(_passPhrase) { + case _ASK_PASSPHRASE: return _passPhraseSelect + case _IMPORTANT_NOTICE: return _importantNotice + case _BACKUP_PASSPHRASE: return _passPhraseBackup + case _PASSPHRASE_DONE: return _Coldcard + default: return null + } + }() case NUNCHUCKTYPE.ADD_BITBOX: return _BitBox default: return null } @@ -59,6 +72,53 @@ QScreen { id: _BitBox QScreenAddBitBox {} } + Component { + id: _passPhraseSelect + QSelectPassPhraseQuestion { + onRequestBack: { + closeTo(NUNCHUCKTYPE.WALLET_TAB) + } + onRequestNext: { + if (option === "not-have-a-passphrase") { + _passPhrase = _PASSPHRASE_DONE + } else { + _passPhrase = _IMPORTANT_NOTICE + } + } + } + } + Component { + id: _importantNotice + QImportantNoticeAboutPassphrase { + onRequestBack: { + _passPhrase = _ASK_PASSPHRASE + } + onRequestNext: { + _passPhrase = _BACKUP_PASSPHRASE + } + onRequestWithout: { + var alert = GroupWallet.dashboardInfo.alert + var can_replace = alert.payload.can_replace + if (can_replace) { + GroupWallet.dashboardInfo.requestShowReplacementKey(); + } else { + GroupWallet.dashboardInfo.requestShowLetAddYourKeys(); + } + } + } + } + Component { + id: _passPhraseBackup + QPassphraseBackupReminder { + onRequestBack: { + _passPhrase = _IMPORTANT_NOTICE + } + onRequestNext: { + _passPhrase = _PASSPHRASE_DONE + } + } + } + function doneOrTryAgainAddHardwareKey(isSuccess) { if (isSuccess) { AppModel.showToast(0, STR.STR_QML_1392, EWARNING.SUCCESS_MSG); diff --git a/Qml/Screens/OnlineMode/SCR_REPLACE_SELECT_KEY.qml b/Qml/Screens/OnlineMode/SCR_REPLACE_SELECT_KEY.qml index b749435c..6c2a8879 100644 --- a/Qml/Screens/OnlineMode/SCR_REPLACE_SELECT_KEY.qml +++ b/Qml/Screens/OnlineMode/SCR_REPLACE_SELECT_KEY.qml @@ -33,7 +33,7 @@ import "../../../localization/STR_QML.js" as STR QScreen { property var dashInfo: GroupWallet.dashboardInfo - property int key_index: -1 + property var alert: dashInfo.alert property bool isKeyHolderLimited: dashInfo.myRole === "KEYHOLDER_LIMITED" property string hardware: "" QOnScreenContentTypeB { @@ -76,12 +76,20 @@ QScreen { Repeater { model: { var ls = [] + var is_inheritance = alert.payload.is_inheritance if (!isKeyHolderLimited) { - ls.push({add_type: NUNCHUCKTYPE.ADD_BITBOX, txt: "BitBox" , type: "bitbox02", tag: "BITBOX" }) + if (!is_inheritance) { + ls.push({add_type: NUNCHUCKTYPE.ADD_BITBOX, txt: "BitBox" , type: "bitbox02", tag: "BITBOX" }) + } ls.push({add_type: NUNCHUCKTYPE.ADD_COLDCARD, txt: "COLDCARD" , type: "coldcard", tag: "COLDCARD"}) } - ls.push({add_type: NUNCHUCKTYPE.ADD_LEDGER, txt: "Ledger" , type: "ledger" , tag: "LEDGER" }) - ls.push({add_type: NUNCHUCKTYPE.ADD_TREZOR, txt: "Trezor" , type: "trezor" , tag: "TREZOR" }) + if (!is_inheritance) { + ls.push({add_type: NUNCHUCKTYPE.ADD_LEDGER, txt: "Ledger" , type: "ledger" , tag: "LEDGER" }) + ls.push({add_type: NUNCHUCKTYPE.ADD_TREZOR, txt: "Trezor" , type: "trezor" , tag: "TREZOR" }) + } + if (is_inheritance) { + ls.push({add_type: NUNCHUCKTYPE.ADD_TAPSIGNER, txt: "TAPSIGNER" , type: "TAPSIGNER" , tag: "INHERITANCE" }) + } return ls } QRadioButtonTypeA { @@ -93,8 +101,10 @@ QScreen { fontFamily: "Lato" fontPixelSize: 16 fontWeight: Font.Normal + enabled: !(modelData.add_type === NUNCHUCKTYPE.ADD_TAPSIGNER) selected: GroupWallet.qAddHardware === modelData.add_type && hardware === modelData.tag onButtonClicked: { + var key_index = alert.payload.key_index GroupWallet.addHardwareFromConfig(modelData.add_type, GroupWallet.dashboardInfo.groupId, key_index) hardware = modelData.tag } @@ -102,7 +112,19 @@ QScreen { } } } + QWarningBgMulti { + width: 528 + visible: alert.payload.is_inheritance + height: 108 + icon: "qrc:/Images/Images/info-60px.png" + txt.text: STR.STR_QML_1603 + anchors.bottom: parent.bottom + } + QWarningBg { + width: 528 + visible: !alert.payload.is_inheritance + height: 60 icon: "qrc:/Images/Images/info-60px.png" txt.text: STR.STR_QML_943 anchors.bottom: parent.bottom diff --git a/Qml/Screens/OnlineMode/SetupWallets/QWalletCreationPending.qml b/Qml/Screens/OnlineMode/SetupWallets/QWalletCreationPending.qml index e5182d16..4358016f 100644 --- a/Qml/Screens/OnlineMode/SetupWallets/QWalletCreationPending.qml +++ b/Qml/Screens/OnlineMode/SetupWallets/QWalletCreationPending.qml @@ -180,12 +180,16 @@ Item { model: GroupWallet.dashboardInfo.keys QAddRequestKey { onTapsignerClicked: { - _info.contentText = STR.STR_QML_961 - _info.open() + // _info.contentText = STR.STR_QML_961 + // _info.open() + key_index = modelData.key_index + _hardwareAddKey.isInheritance = true + _hardwareAddKey.open() } onHardwareClicked: { key_index = modelData.key_index - _Security.open() + _hardwareAddKey.isInheritance = false + _hardwareAddKey.open() } onSerkeyClicked: { _info.contentText = STR.STR_QML_962 @@ -205,19 +209,20 @@ Item { property string hardware: "" QPopupEmpty { - id: _Security + id: _hardwareAddKey + property bool isInheritance: false onOpened: { GroupWallet.addHardwareFromConfig(-1, "", -1) hardware = "" } - content: QOnScreenContentTypeB { width: 600 height: 516 anchors.centerIn: parent - label.text: STR.STR_QML_106 + label.text: _hardwareAddKey.isInheritance ? STR.STR_QML_1601 : STR.STR_QML_1602 + label.width: 600 extraHeader: Item {} - onCloseClicked: { _Security.close() } + onCloseClicked: { _hardwareAddKey.close() } content: Item { Column { anchors.fill: parent @@ -249,11 +254,18 @@ Item { model: { var ls = [] if (!isKeyHolderLimited) { - ls.push({add_type: NUNCHUCKTYPE.ADD_BITBOX, txt: "BitBox" , type: "bitbox02", tag: "BITBOX" }) + if (!_hardwareAddKey.isInheritance) { + ls.push({add_type: NUNCHUCKTYPE.ADD_BITBOX, txt: "BitBox" , type: "bitbox02", tag: "BITBOX" }) + } ls.push({add_type: NUNCHUCKTYPE.ADD_COLDCARD, txt: "COLDCARD" , type: "coldcard", tag: "COLDCARD"}) } - ls.push({add_type: NUNCHUCKTYPE.ADD_LEDGER, txt: "Ledger" , type: "ledger" , tag: "LEDGER" }) - ls.push({add_type: NUNCHUCKTYPE.ADD_TREZOR, txt: "Trezor" , type: "trezor" , tag: "TREZOR" }) + if (!_hardwareAddKey.isInheritance) { + ls.push({add_type: NUNCHUCKTYPE.ADD_LEDGER, txt: "Ledger" , type: "ledger" , tag: "LEDGER" }) + ls.push({add_type: NUNCHUCKTYPE.ADD_TREZOR, txt: "Trezor" , type: "trezor" , tag: "TREZOR" }) + } + if (_hardwareAddKey.isInheritance) { + ls.push({add_type: NUNCHUCKTYPE.ADD_TAPSIGNER, txt: "TAPSIGNER" , type: "TAPSIGNER" , tag: "INHERITANCE" }) + } return ls } QRadioButtonTypeA { @@ -265,6 +277,7 @@ Item { fontFamily: "Lato" fontPixelSize: 16 fontWeight: Font.Normal + enabled: !(modelData.add_type === NUNCHUCKTYPE.ADD_TAPSIGNER) selected: GroupWallet.qAddHardware === modelData.add_type onButtonClicked: { GroupWallet.addHardwareFromConfig(modelData.add_type, GroupWallet.dashboardInfo.groupId, key_index) @@ -274,7 +287,19 @@ Item { } } } + QWarningBgMulti { + width: 528 + visible: _hardwareAddKey.isInheritance + height: 108 + icon: "qrc:/Images/Images/info-60px.png" + txt.text: STR.STR_QML_1603 + anchors.bottom: parent.bottom + } + QWarningBg { + width: 528 + visible: !_hardwareAddKey.isInheritance + height: 60 icon: "qrc:/Images/Images/info-60px.png" txt.text: STR.STR_QML_943 anchors.bottom: parent.bottom diff --git a/Views/STATE_ID_SCR_HOME.cpp b/Views/STATE_ID_SCR_HOME.cpp index 0256f3ab..bb9f6f0c 100644 --- a/Views/STATE_ID_SCR_HOME.cpp +++ b/Views/STATE_ID_SCR_HOME.cpp @@ -288,6 +288,7 @@ void EVT_HOME_COLDCARD_NFC_SIGNER_INFO_REQUEST_HANDLER(QVariant msg) { } void EVT_ASK_HARDWARE_REQ_HANDLER(QVariant msg) { + QGroupWallets::instance()->setDashboardInfo(""); } void EVT_EXIST_HARDWARE_REQ_HANDLER(QVariant msg) { diff --git a/contrib/libnunchuk b/contrib/libnunchuk index 0bec88ea..ca2b370b 160000 --- a/contrib/libnunchuk +++ b/contrib/libnunchuk @@ -1 +1 @@ -Subproject commit 0bec88ea07080eb1f32cd53481e3632ceeb14d1b +Subproject commit ca2b370b97a3f2e3825351009d2b4f9d9de730ec diff --git a/ifaces/bridgeifaces.cpp b/ifaces/bridgeifaces.cpp index f9c8aad2..c334f4e0 100644 --- a/ifaces/bridgeifaces.cpp +++ b/ifaces/bridgeifaces.cpp @@ -2168,3 +2168,22 @@ nunchuk::MasterSigner bridge::CreateSoftwareSignerFromMasterXprv(const QString & replace, msg); } + +QMasterSignerPtr bridge::ImportBackupKey(const std::vector &data, const QString &backup_key, const QString &name, bool is_primary, QWarningMessage &msg) +{ + nunchuk::MasterSigner it = nunchukiface::instance()->ImportBackupKey(data, + backup_key.toStdString(), + name.toStdString(), + is_primary, + msg); + if((int)EWARNING::WarningType::NONE_MSG == msg.type()){ + QMasterSignerPtr signer = QMasterSignerPtr(new QMasterSigner(it)); + QWarningMessage msgGetTap; + nunchuk::TapsignerStatus tapsigner = nunchukiface::instance()->GetTapsignerStatusFromMasterSigner(it.get_device().get_master_fingerprint(), msgGetTap); + if((int)EWARNING::WarningType::NONE_MSG == msgGetTap.type()){ + signer.data()->device()->setCardId(QString::fromStdString(tapsigner.get_card_ident())); + } + return signer; + } + return NULL; +} diff --git a/ifaces/bridgeifaces.h b/ifaces/bridgeifaces.h index b2c954a6..ac298ba8 100644 --- a/ifaces/bridgeifaces.h +++ b/ifaces/bridgeifaces.h @@ -69,6 +69,7 @@ class ENUNCHUCK: public QObject ADD_TREZOR = (int)nunchuk::SignerTag::TREZOR, ADD_LEDGER = (int)nunchuk::SignerTag::LEDGER, ADD_BITBOX = (int)nunchuk::SignerTag::BITBOX, + ADD_TAPSIGNER, }; enum class TabSelection { @@ -708,6 +709,12 @@ QString GetSignerAddress(const nunchuk::SingleSigner& signer, QString GetHotWalletMnemonic(const QString& wallet_id, const QString& passphrase); QWalletPtr nunchukCreateHotWallet(const QString &mnemonic, const QString& passphrase, bool need_backup, bool replace, QWarningMessage &msg); + +QMasterSignerPtr ImportBackupKey( const std::vector& data, + const QString& backup_key, + const QString& name, + bool is_primary, + QWarningMessage& msg); } #endif // BRIDGEINTERFACE_H diff --git a/ifaces/nunchuckiface.cpp b/ifaces/nunchuckiface.cpp index ca9f9bbe..31fe9d45 100644 --- a/ifaces/nunchuckiface.cpp +++ b/ifaces/nunchuckiface.cpp @@ -2491,3 +2491,55 @@ nunchuk::Wallet nunchukiface::CreateHotWallet(const std::string& mnemonic, const } return ret; } + +void nunchukiface::VerifyColdcardBackup(const std::vector &data, const std::string &backup_key, const std::string &xfp, QWarningMessage &msg) +{ + try { + if(nunchuk_instance_[nunchukMode()]){ + nunchuk_instance_[nunchukMode()]->VerifyColdcardBackup(data, backup_key, xfp); + } + } + catch (const nunchuk::BaseException &ex) { + DBG_INFO << "exception nunchuk::BaseException" << ex.code() << ex.what(); + msg.setWarningMessage(ex.code(), ex.what(), EWARNING::WarningType::EXCEPTION_MSG); + } + catch (std::exception &e) { + DBG_INFO << "THROW EXCEPTION" << e.what(); msg.setWarningMessage(-1, e.what(), EWARNING::WarningType::EXCEPTION_MSG); + } +} + +nunchuk::MasterSigner nunchukiface::ImportColdcardBackup(const std::vector &data, const std::string &backup_key, const std::string &name, std::function progress, bool is_primary, QWarningMessage &msg) +{ + nunchuk::MasterSigner ret; + try { + if(nunchuk_instance_[nunchukMode()]){ + ret = nunchuk_instance_[nunchukMode()]->ImportColdcardBackup(data, backup_key, name, progress, is_primary); + } + } + catch (const nunchuk::BaseException &ex) { + DBG_INFO << "exception nunchuk::BaseException" << ex.code() << ex.what(); + msg.setWarningMessage(ex.code(), ex.what(), EWARNING::WarningType::EXCEPTION_MSG); + } + catch (std::exception &e) { + DBG_INFO << "THROW EXCEPTION" << e.what(); msg.setWarningMessage(-1, e.what(), EWARNING::WarningType::EXCEPTION_MSG); + } + return ret; +} + +nunchuk::MasterSigner nunchukiface::ImportBackupKey(const std::vector &data, const std::string &backup_key, const std::string &name, bool is_primary, QWarningMessage &msg) +{ + nunchuk::MasterSigner ret; + try { + if(nunchuk_instance_[nunchukMode()]){ + ret = nunchuk_instance_[nunchukMode()]->ImportBackupKey(data, backup_key, name, ImportTapsignerMasterSignerProgress, is_primary); + } + } + catch (const nunchuk::BaseException &ex) { + DBG_INFO << "exception nunchuk::BaseException" << ex.code() << ex.what(); + msg.setWarningMessage(ex.code(), ex.what(), EWARNING::WarningType::EXCEPTION_MSG); + } + catch (std::exception &e) { + DBG_INFO << "THROW EXCEPTION" << e.what(); msg.setWarningMessage(-1, e.what(), EWARNING::WarningType::EXCEPTION_MSG); + } + return ret; +} diff --git a/ifaces/nunchuckiface.h b/ifaces/nunchuckiface.h index 15a98e4e..c77e3950 100644 --- a/ifaces/nunchuckiface.h +++ b/ifaces/nunchuckiface.h @@ -527,6 +527,24 @@ class nunchukiface std::string GetHotWalletMnemonic(const std::string& wallet_id, const std::string& passphrase, QWarningMessage& msg); nunchuk::Wallet CreateHotWallet(const std::string& mnemonic, const std::string& passphraser, bool need_backup, bool replace, QWarningMessage &msg); + + void VerifyColdcardBackup(const std::vector& data, + const std::string& backup_key, + const std::string& xfp, + QWarningMessage &msg); + + nunchuk::MasterSigner ImportColdcardBackup(const std::vector& data, + const std::string& backup_key, + const std::string& name, + std::function progress, + bool is_primary, + QWarningMessage &msg); + + nunchuk::MasterSigner ImportBackupKey(const std::vector& data, + const std::string& backup_key, + const std::string& name, + bool is_primary, + QWarningMessage &msg); private: nunchukiface(); ~nunchukiface(); diff --git a/localization/STR_QML.js b/localization/STR_QML.js index fdbdc15a..ad626e4c 100644 --- a/localization/STR_QML.js +++ b/localization/STR_QML.js @@ -1158,8 +1158,8 @@ Pull to refresh the key statuses.") var STR_QML_940_without = qsTr("Pull to refresh the key statuses.") var STR_QML_941 = qsTr("Add") -var STR_QML_942 = qsTr("What type of key do you want to add?") -var STR_QML_943 = qsTr("To add other types of keys, please use the mobile app.") +var STR_QML_942 = qsTr("Which type of key would you like to add?") +var STR_QML_943 = qsTr("To add other types of hardware keys, please use the mobile app.") var STR_QML_944 = qsTr("Pending wallet") var STR_QML_945 = qsTr("%1 (%2) invited you to join a group wallet.") var STR_QML_946 = qsTr("Deny") @@ -1172,7 +1172,7 @@ var STR_QML_951 = qsTr("Keyholder") var STR_QML_952 = qsTr("Keyholder (limited)") var STR_QML_953 = qsTr("Observer") -var STR_QML_954 = qsTr("Key #%1") +var STR_QML_954 = qsTr("Hardware key #%1") var STR_QML_955 = qsTr("Dismiss") var STR_QML_956 = qsTr("TAPSIGNER (inh.)") var STR_QML_957 = qsTr("Platform key") @@ -1720,3 +1720,20 @@ var STR_QML_1500 = qsTr("Enter your email") var STR_QML_1501 = qsTr("Enter your password") var STR_QML_1502 = qsTr("Enter your name") var STR_QML_1503 = qsTr("Honey Badger PLUS") + + +var STR_QML_1600 = qsTr("Inheritance") +var STR_QML_1601 = qsTr("Add inheritance key") +var STR_QML_1602 = qsTr("Add hardware key") +var STR_QML_1603 = qsTr("Currently, COLDCARD and TAPSIGNER are supported for the inheritance key. To add TAPSIGNER as the inheritance key, please use the mobile app.") +var STR_QML_1604 = qsTr("I don’t have a passphrase") +var STR_QML_1605 = qsTr("I have a passphrase") +var STR_QML_1606 = qsTr("Are you using a passphrase with your COLDCARD?") +var STR_QML_1607 = qsTr("A BIP39 passphrase is an extra word added to the seed phrase as another layer of security.") +var STR_QML_1608 = qsTr("Important notice about passphrase") +var STR_QML_1609 = qsTr("Using a passphrase for the inheritance key greatly complicates the setup and increases the risk of errors, both for you and the Beneficiary. We strongly recommend using an inheritance key without a passphrase.") +var STR_QML_1610 = qsTr("Continue, I know what I’m doing") +var STR_QML_1611 = qsTr("Use another key (without passphrase)") +var STR_QML_1612 = qsTr("Passphrase backup reminder") +var STR_QML_1613 = qsTr("If you use a key with a passphrase, ensure you export the correct file when backing up the inheritance key from the COLDCARD in the next step.") +var STR_QML_1614 = qsTr("The encrypted backup file must include the passphrase for the inheritance protocol to function correctly.") diff --git a/main.cpp b/main.cpp index c245045f..4ce2a962 100644 --- a/main.cpp +++ b/main.cpp @@ -124,7 +124,7 @@ int main(int argc, char* argv[]) app.setOrganizationName("nunchuk"); app.setOrganizationDomain("nunchuk.io"); app.setApplicationName("NunchukClient"); - app.setApplicationVersion("1.9.39"); + app.setApplicationVersion("1.9.40"); app.setApplicationDisplayName(QString("%1 %2").arg("Nunchuk").arg(app.applicationVersion())); AppModel::instance(); Draco::instance(); diff --git a/qml.qrc b/qml.qrc index ad27883a..52f3e5c0 100644 --- a/qml.qrc +++ b/qml.qrc @@ -472,6 +472,10 @@ Qml/Screens/OnlineMode/EditMembers/QEditMemberSuccess.qml Qml/Screens/OnlineMode/EditMembers/QEditMemberConfirmEmail.qml Qml/Components/LeftPannel/Service/Byzantine/QServiceFacilitatorAdminSubscriberLeftPannel.qml + Qml/Components/customizes/QWarningBgMulti.qml + Qml/Screens/OnlineMode/AddHardwareKeys/QSelectPassPhraseQuestion.qml + Qml/Screens/OnlineMode/AddHardwareKeys/QImportantNoticeAboutPassphrase.qml + Qml/Screens/OnlineMode/AddHardwareKeys/QPassphraseBackupReminder.qml Images/services-light.svg