diff --git a/qml.qrc b/qml.qrc index 8e731998cfa7857d89a93263feafadfc92b4008f..36590f64ee5e9734f87e345c93798e5844f23402 100644 --- a/qml.qrc +++ b/qml.qrc @@ -98,6 +98,7 @@ <file>src/app/mainview/components/SidePanel.qml</file> <file>src/app/mainview/components/WelcomePage.qml</file> <file>src/app/mainview/components/ChatView.qml</file> + <file>src/app/mainview/components/ConversationErrorsRow.qml</file> <file>src/app/mainview/components/NewSwarmPage.qml</file> <file>src/app/mainview/components/ChatViewHeader.qml</file> <file>src/app/mainview/components/AccountComboBox.qml</file> diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml index 9d6ca31b3d85ae50424fc6b418b4a2fbbe742b4f..0c64a1e558e492c2b2fb9947a15ddf4dd0bfa8a8 100644 --- a/src/app/constant/JamiStrings.qml +++ b/src/app/constant/JamiStrings.qml @@ -301,6 +301,7 @@ Item { property string placeVideoCall: qsTr("Place video call") property string showPlugins: qsTr("Show available plugins") property string addToConversations: qsTr("Add to conversations") + property string backendError: qsTr("This is the error from the backend: %0") // Chatview footer property string jumpToLatest: qsTr("Jump to latest") diff --git a/src/app/conversationsadapter.cpp b/src/app/conversationsadapter.cpp index 5a50608d13f122d971dfdfa20afbf28c1c836205..cd1bf5def854ff6d23ec0724590d75299f2f76db 100644 --- a/src/app/conversationsadapter.cpp +++ b/src/app/conversationsadapter.cpp @@ -495,6 +495,13 @@ ConversationsAdapter::updateConversationTitle(const QString& convId, const QStri convModel->updateConversationInfos(convId, details); } +void +ConversationsAdapter::popFrontError(const QString& convId) +{ + auto convModel = lrcInstance_->getCurrentConversationModel(); + convModel->popFrontError(convId); +} + void ConversationsAdapter::updateConversationDescription(const QString& convId, const QString& newDescription) diff --git a/src/app/conversationsadapter.h b/src/app/conversationsadapter.h index de30708c36a2cc55e57a0362f11a0e013a91d30a..0319b398026c77abcd07ea1060565e2c74ec7b3e 100644 --- a/src/app/conversationsadapter.h +++ b/src/app/conversationsadapter.h @@ -57,6 +57,7 @@ public: Q_INVOKABLE QVariantMap getConvInfoMap(const QString& convId); Q_INVOKABLE void restartConversation(const QString& convId); Q_INVOKABLE void updateConversationTitle(const QString& convId, const QString& newTitle); + Q_INVOKABLE void popFrontError(const QString& convId); Q_INVOKABLE void updateConversationDescription(const QString& convId, const QString& newDescription); diff --git a/src/app/currentconversation.cpp b/src/app/currentconversation.cpp index 1ccda9c03981339703f1db583f753c4e0b45c25c..0038bc9cfa1210a8e6a81e03cd2d9fad74e81d4f 100644 --- a/src/app/currentconversation.cpp +++ b/src/app/currentconversation.cpp @@ -104,6 +104,7 @@ CurrentConversation::updateData() } catch (...) { qWarning() << "Can't update current conversation data for" << convId; } + updateErrors(convId); } void @@ -142,6 +143,11 @@ CurrentConversation::connectModel() this, &CurrentConversation::onProfileUpdated, Qt::UniqueConnection); + connect(lrcInstance_->getCurrentConversationModel(), + &ConversationModel::onConversationErrorsUpdated, + this, + &CurrentConversation::updateErrors, + Qt::UniqueConnection); } void @@ -149,3 +155,36 @@ CurrentConversation::showSwarmDetails() const { Q_EMIT showDetails(); } + +void +CurrentConversation::updateErrors(const QString& convId) +{ + if (convId != id_) + return; + try { + const auto& convModel = lrcInstance_->getCurrentConversationModel(); + if (auto optConv = convModel->getConversationForUid(convId)) { + auto& convInfo = optConv->get(); + QStringList newErrors; + QStringList newBackendErr; + for (const auto& [code, error]: convInfo.errors) { + if (code == 1) { + newErrors.append(tr("An error occurred while fetching this repository")); + } else if (code == 2) { + newErrors.append(tr("The conversation's mode is un-recognized")); + } else if (code == 3) { + newErrors.append(tr("An invalid message was detected")); + } else if (code == 4) { + newErrors.append(tr("Not enough authorization for updating conversation's infos")); + } else { + continue; + } + newBackendErr.push_back(error); + } + set_backendErrors(newBackendErr); + set_errors(newErrors); + } + } catch (...) { + + } +} diff --git a/src/app/currentconversation.h b/src/app/currentconversation.h index 72de66eda5c1c48b9d329d115b17df86862f737b..399f19dd47c5c415e49625f9f8212d965662a47c 100644 --- a/src/app/currentconversation.h +++ b/src/app/currentconversation.h @@ -48,6 +48,8 @@ class CurrentConversation final : public QObject QML_PROPERTY(bool, isContact) QML_PROPERTY(bool, allMessagesLoaded) QML_PROPERTY(QString, modeString) + QML_PROPERTY(QStringList, errors) + QML_PROPERTY(QStringList, backendErrors) public: explicit CurrentConversation(LRCInstance* lrcInstance, QObject* parent = nullptr); @@ -62,6 +64,7 @@ private Q_SLOTS: void updateData(); void onConversationUpdated(const QString& convId); void onProfileUpdated(const QString& convId); + void updateErrors(const QString& convId); private: LRCInstance* lrcInstance_; diff --git a/src/app/mainview/components/ChatView.qml b/src/app/mainview/components/ChatView.qml index 084f0019a8cf0b008ef5d8b0348904a8b3fd2a28..ee90d9251e53e6649d34496367936f0a756f54ce 100644 --- a/src/app/mainview/components/ChatView.qml +++ b/src/app/mainview/components/ChatView.qml @@ -21,6 +21,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Qt5Compat.GraphicalEffects import net.jami.Models 1.1 import net.jami.Adapters 1.1 @@ -104,6 +105,14 @@ Rectangle { } } + ConversationErrorsRow { + id: errorRect + color: JamiTheme.filterBadgeColor + Layout.fillWidth: true + Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight + visible: false + } + SplitView { id: chatViewMainRow Layout.fillWidth: true diff --git a/src/app/mainview/components/ConversationErrorsRow.qml b/src/app/mainview/components/ConversationErrorsRow.qml new file mode 100644 index 0000000000000000000000000000000000000000..7a79fc06f09aa30aced0312583450d09855e3e77 --- /dev/null +++ b/src/app/mainview/components/ConversationErrorsRow.qml @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +import net.jami.Models 1.1 +import net.jami.Adapters 1.1 +import net.jami.Constants 1.1 + +import "../../commoncomponents" + +Rectangle { + id: root + + opacity: visible + + Connections { + target: CurrentConversation + enabled: true + + onErrorsChanged: { + if (CurrentConversation.errors.length > 0) { + errorLabel.text = CurrentConversation.errors[0] + backendErrorToolTip.text = JamiStrings.backendError.arg(CurrentConversation.backendErrors[0]) + } + errorRect.visible = CurrentConversation.errors.length > 0 + } + } + + RowLayout { + anchors.fill: parent + anchors.margins: JamiTheme.preferredMarginSize + + Text { + id: errorLabel + Layout.alignment: Qt.AlignVCenter + text: CurrentConversation.errors.count > 0 ? CurrentConversation.errors[0][0] : "" + color: JamiTheme.filterBadgeTextColor + font.pixelSize: JamiTheme.headerFontSize + elide: Text.ElideRight + } + + ResponsiveImage { + id: backEndError + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + width: 30 + height: 30 + + source: JamiResources.outline_info_24dp_svg + layer { + enabled: true + effect: ColorOverlay { + color: JamiTheme.filterBadgeTextColor + } + } + + + MaterialToolTip { + id: backendErrorToolTip + text: "" + visible: parent.hovered && text !== "" + delay: Qt.styleHints.mousePressAndHoldInterval + } + } + + PushButton { + id: btnClose + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + + imageColor: JamiTheme.filterBadgeTextColor + normalColor: JamiTheme.transparentColor + + source: JamiResources.round_close_24dp_svg + + onClicked: ConversationsAdapter.popFrontError(CurrentConversation.id) + } + } + + Behavior on opacity { + NumberAnimation { + from: 0 + duration: JamiTheme.shortFadeDuration + } + } +} \ No newline at end of file diff --git a/src/libclient/api/conversation.h b/src/libclient/api/conversation.h index 086fe5ff91102e92a90b128bac1e6908ca95bded..26743e61106652402d2b8cb0c1a2baa881180e67 100644 --- a/src/libclient/api/conversation.h +++ b/src/libclient/api/conversation.h @@ -77,6 +77,7 @@ struct Info QString lastMessageUid = 0; QHash<QString, QString> parentsId; // pair messageid/parentid for messages without parent loaded unsigned int unreadMessages = 0; + QVector<QPair<int, QString>> errors; QSet<QString> typers; diff --git a/src/libclient/api/conversationmodel.h b/src/libclient/api/conversationmodel.h index 634891ded2e250d92aae7fc43c1584ff66fcb898..7da8d5aa04c3fec1a19a306b3a5e18662c8259a6 100644 --- a/src/libclient/api/conversationmodel.h +++ b/src/libclient/api/conversationmodel.h @@ -348,6 +348,11 @@ public: * @param info */ void updateConversationInfos(const QString& conversationId, MapStringString info); + /** + * Remove first error + * @param conversationId + */ + void popFrontError(const QString& conversationId); /** * @return if conversations requests exists. @@ -427,6 +432,11 @@ Q_SIGNALS: * @param uid */ void conversationUpdated(const QString& uid) const; + /** + * Emitted when a conversation detects an error + * @param uid + */ + void onConversationErrorsUpdated(const QString& uid) const; /** * Emitted when conversation's profile has been updated * @param uid diff --git a/src/libclient/callbackshandler.cpp b/src/libclient/callbackshandler.cpp index dc49c84892134cec33146d7d648649ac2727f45a..a542b718f102247f922cba95672e87de56c53279 100644 --- a/src/libclient/callbackshandler.cpp +++ b/src/libclient/callbackshandler.cpp @@ -340,6 +340,11 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent) this, &CallbacksHandler::slotConversationMemberEvent, Qt::QueuedConnection); + connect(&ConfigurationManager::instance(), + &ConfigurationManagerInterface::conversationError, + this, + &CallbacksHandler::slotOnConversationError, + Qt::QueuedConnection); } CallbacksHandler::~CallbacksHandler() {} @@ -791,4 +796,13 @@ CallbacksHandler::slotConversationMemberEvent(const QString& accountId, Q_EMIT conversationMemberEvent(accountId, conversationId, memberId, event); } +void +CallbacksHandler::slotOnConversationError(const QString& accountId, + const QString& conversationId, + int code, + const QString& what) +{ + Q_EMIT conversationError(accountId, conversationId, code, what); +} + } // namespace lrc diff --git a/src/libclient/callbackshandler.h b/src/libclient/callbackshandler.h index 133d4ca72872b2c0bf7d70412a41fd9ed9f3cb31..b39b6334ac2a22ab4d1d1b8cf9e82d9754845e42 100644 --- a/src/libclient/callbackshandler.h +++ b/src/libclient/callbackshandler.h @@ -366,6 +366,10 @@ Q_SIGNALS: const QString& conversationId, const QString& memberId, int event); + void conversationError(const QString& accountId, + const QString& conversationId, + int code, + const QString& what); private Q_SLOTS: /** @@ -677,6 +681,10 @@ private Q_SLOTS: const QString& conversationId, const QString& memberId, int event); + void slotOnConversationError(const QString& accountId, + const QString& conversationId, + int code, + const QString& what); private: const api::Lrc& parent; diff --git a/src/libclient/conversationmodel.cpp b/src/libclient/conversationmodel.cpp index 85cfa5d25ba4bd2474c57e589a728868e9369724..78ed4090ee03c00fdf4d6240e4d8ed21d9a63e78 100644 --- a/src/libclient/conversationmodel.cpp +++ b/src/libclient/conversationmodel.cpp @@ -369,6 +369,10 @@ public Q_SLOTS: const QString& conversationId, const QString& memberUri, int event); + void slotOnConversationError(const QString& accountId, + const QString& conversationId, + int code, + const QString& what); void slotConversationReady(const QString& accountId, const QString& conversationId); void slotConversationRemoved(const QString& accountId, const QString& conversationId); }; @@ -988,6 +992,18 @@ ConversationModel::updateConversationInfos(const QString& conversationId, const ConfigurationManager::instance().updateConversationInfos(owner.id, conversationId, newInfos); } +void +ConversationModel::popFrontError(const QString& conversationId) +{ + auto conversationOpt = getConversationForUid(conversationId); + if (!conversationOpt.has_value()) + return; + + auto& conversation = conversationOpt->get(); + conversation.errors.pop_front(); + Q_EMIT onConversationErrorsUpdated(conversationId); +} + bool ConversationModel::hasPendingRequests() const { @@ -1830,6 +1846,10 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked, &CallbacksHandler::conversationMemberEvent, this, &ConversationModelPimpl::slotConversationMemberEvent); + connect(&callbacksHandler, + &CallbacksHandler::conversationError, + this, + &ConversationModelPimpl::slotOnConversationError); } ConversationModelPimpl::~ConversationModelPimpl() @@ -1970,6 +1990,10 @@ ConversationModelPimpl::~ConversationModelPimpl() &CallbacksHandler::conversationMemberEvent, this, &ConversationModelPimpl::slotConversationMemberEvent); + disconnect(&callbacksHandler, + &CallbacksHandler::conversationError, + this, + &ConversationModelPimpl::slotOnConversationError); } void @@ -2661,6 +2685,22 @@ ConversationModelPimpl::slotConversationMemberEvent(const QString& accountId, Q_EMIT linked.dataChanged(indexOf(conversationId)); } +void +ConversationModelPimpl::slotOnConversationError(const QString& accountId, + const QString& conversationId, + int code, + const QString& what) +{ + if (accountId != linked.owner.id || indexOf(conversationId) < 0) { + return; + } + try { + auto& conversation = getConversationForUid(conversationId).get(); + conversation.errors.push_back({code, what}); + Q_EMIT linked.onConversationErrorsUpdated(conversationId); + } catch (...) {} +} + void ConversationModelPimpl::slotIncomingContactRequest(const QString& contactUri) { diff --git a/src/libclient/qtwrapper/configurationmanager_wrap.h b/src/libclient/qtwrapper/configurationmanager_wrap.h index c586c6e844f61922a47c82cb9e8b0b9112d63b48..fc63cb076ad327bc0713f634b2fc593d61702de8 100644 --- a/src/libclient/qtwrapper/configurationmanager_wrap.h +++ b/src/libclient/qtwrapper/configurationmanager_wrap.h @@ -328,6 +328,16 @@ public: QString(conversationId.c_str()), QString(memberId.c_str()), event); + }), + exportable_callback<ConversationSignal::OnConversationError>( + [this](const std::string& accountId, + const std::string& conversationId, + int code, + const std::string& what) { + Q_EMIT conversationError(QString(accountId.c_str()), + QString(conversationId.c_str()), + code, + QString(what.c_str())); })}; } @@ -1190,6 +1200,10 @@ Q_SIGNALS: // SIGNALS const QString& conversationId, const QString& memberId, int event); + void conversationError(const QString& accountId, + const QString& conversationId, + int code, + const QString& what); }; namespace org {