diff --git a/qml.qrc b/qml.qrc index 8f5835d1..d1218958 100644 --- a/qml.qrc +++ b/qml.qrc @@ -161,5 +161,6 @@ src/commoncomponents/BubbleLabel.qml src/commoncomponents/BackButton.qml src/commoncomponents/JamiSwitch.qml + src/mainview/components/ReadOnlyFooter.qml diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml index d94a7ab3..7bb9e5e9 100644 --- a/src/constant/JamiStrings.qml +++ b/src/constant/JamiStrings.qml @@ -40,6 +40,8 @@ Item { property string contactSearchInvitations: qsTr("Search your invitations") property string invitations: qsTr("Invitations") property string description: qsTr("Jami is free software for universal communication which respects the freedoms and the privacy of its users.") + property string contactLeft: qsTr("You are viewing a conversation where all participants other than you have left. New interactions will not be possible.") + property string newConversation: qsTr("Start new conversation") // AboutPopUp property string version: qsTr("Version") + (UpdateManager.isCurrentVersionBeta() ? " (BETA)" : "") @@ -222,6 +224,7 @@ Item { property string startVideoCall: qsTr("Start video call") property string startAudioCall: qsTr("Start audio call") property string clearConversation: qsTr("Clear conversation") + property string removeConversation: qsTr("Remove conversation") property string removeContact: qsTr("Remove contact") property string blockContact: qsTr("Block contact") property string contactDetails: qsTr("Contact details") diff --git a/src/constant/JamiTheme.qml b/src/constant/JamiTheme.qml index 24812918..91de9fe2 100644 --- a/src/constant/JamiTheme.qml +++ b/src/constant/JamiTheme.qml @@ -311,7 +311,7 @@ Item { property real wizardButtonWidth: 400 // Main application spec - property real mainViewMinWidth: 300 + property real mainViewMinWidth: 332 property real mainViewMinHeight: 500 property real wizardViewMinWidth: 500 diff --git a/src/conversationsadapter.cpp b/src/conversationsadapter.cpp index 18673100..34edae18 100644 --- a/src/conversationsadapter.cpp +++ b/src/conversationsadapter.cpp @@ -433,6 +433,53 @@ ConversationsAdapter::getConvInfoMap(const QString& convId) {"readOnly", convInfo.readOnly}}; } +void +ConversationsAdapter::restartConversation(const QString& convId) +{ + // make sure this conversation meets the criteria of a "restartable" conv + // 'readOnly' implies 'isSwarm' + auto& accInfo = lrcInstance_->getCurrentAccountInfo(); + const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId); + if (convInfo.uid.isEmpty() || !convInfo.isCoreDialog() || !convInfo.readOnly) { + return; + } + + // get the ONE_TO_ONE conv's peer uri + auto peerUri = accInfo.conversationModel->peersForConversation(convId).at(0); + + // store a copy of the original contact so we can re-add them + // Note: we set the profile::Type to TEMPORARY to invoke a full add + // when calling ContactModel::addContact + auto contactInfo = accInfo.contactModel->getContact(peerUri); + contactInfo.profileInfo.type = profile::Type::TEMPORARY; + + Utils::oneShotConnect( + accInfo.contactModel.get(), + &ContactModel::contactRemoved, + [this, &accInfo, contactInfo](const QString& peerUri) { + // setup a callback to select another ONE_TO_ONE conversation for this peer + // once the new conversation becomes ready + Utils::oneShotConnect( + accInfo.conversationModel.get(), + &ConversationModel::conversationReady, + [this, peerUri, &accInfo](const QString& convId) { + const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId); + // 3. filter for the correct contact-conversation and select it + if (!convInfo.uid.isEmpty() && convInfo.isCoreDialog() && !convInfo.readOnly + && peerUri + == accInfo.conversationModel->peersForConversation(convId).at(0)) { + lrcInstance_->selectConversation(convId); + } + }); + + // 2. add the contact and await the conversationReady signal + accInfo.contactModel->addContact(contactInfo); + }); + + // 1. remove the contact and await the contactRemoved signal + accInfo.contactModel->removeContact(peerUri); +} + bool ConversationsAdapter::connectConversationModel() { diff --git a/src/conversationsadapter.h b/src/conversationsadapter.h index aefc70ca..48098611 100644 --- a/src/conversationsadapter.h +++ b/src/conversationsadapter.h @@ -50,6 +50,7 @@ public: Q_INVOKABLE bool connectConversationModel(); Q_INVOKABLE void setFilter(const QString& filterString); Q_INVOKABLE QVariantMap getConvInfoMap(const QString& convId); + Q_INVOKABLE void restartConversation(const QString& convId); Q_SIGNALS: void showConversation(const QString& accountId, const QString& convUid); diff --git a/src/mainview/components/ConversationListView.qml b/src/mainview/components/ConversationListView.qml index bf981eb1..d7f1d4d7 100644 --- a/src/mainview/components/ConversationListView.qml +++ b/src/mainview/components/ConversationListView.qml @@ -175,7 +175,7 @@ ListView { enabled: root.visible onActivated: MessagesAdapter.clearConversationHistory( LRCInstance.currentAccountId, - UtilsAdapter.getCurrConvId()) + LRCInstance.selectedConvUid) } Shortcut { @@ -183,7 +183,8 @@ ListView { context: Qt.ApplicationShortcut enabled: root.visible onActivated: { - MessagesAdapter.blockConversation(UtilsAdapter.getCurrConvId()) + MessagesAdapter.blockConversation( + LRCInstance.selectedConvUid) } } @@ -192,9 +193,7 @@ ListView { context: Qt.ApplicationShortcut enabled: root.visible onActivated: MessagesAdapter.removeConversation( - LRCInstance.currentAccountId, - UtilsAdapter.getCurrConvId(), - false) + LRCInstance.selectedConvUid) } Shortcut { diff --git a/src/mainview/components/ConversationSmartListContextMenu.qml b/src/mainview/components/ConversationSmartListContextMenu.qml index e2f463ec..2880eb72 100644 --- a/src/mainview/components/ConversationSmartListContextMenu.qml +++ b/src/mainview/components/ConversationSmartListContextMenu.qml @@ -77,6 +77,15 @@ ContextMenuAutoLoader { responsibleAccountId, responsibleConvUid) }, + GeneralMenuItem { + id: removeConversation + + canTrigger: isSwarm && !hasCall + itemName: JamiStrings.removeConversation + iconSource: JamiResources.delete_24dp_svg + onClicked: MessagesAdapter.removeConversation( + responsibleConvUid) + }, GeneralMenuItem { id: removeContact @@ -84,8 +93,7 @@ ContextMenuAutoLoader { || contactType === Profile.Type.SIP) itemName: JamiStrings.removeContact iconSource: JamiResources.ic_hangup_participant_24dp_svg - onClicked: MessagesAdapter.removeConversation(responsibleAccountId, - responsibleConvUid) + onClicked: MessagesAdapter.removeContact(responsibleConvUid) }, GeneralMenuItem { id: hangup diff --git a/src/mainview/components/MessageWebView.qml b/src/mainview/components/MessageWebView.qml index 095e7caf..d3b69439 100644 --- a/src/mainview/components/MessageWebView.qml +++ b/src/mainview/components/MessageWebView.qml @@ -257,6 +257,11 @@ Rectangle { } } + ReadOnlyFooter { + visible: CurrentConversation.readOnly + Layout.fillWidth: true + } + MessageWebViewFooter { id: messageWebViewFooter diff --git a/src/mainview/components/OngoingCallPage.qml b/src/mainview/components/OngoingCallPage.qml index 21531002..58421abc 100644 --- a/src/mainview/components/OngoingCallPage.qml +++ b/src/mainview/components/OngoingCallPage.qml @@ -88,7 +88,7 @@ Rectangle { function handleParticipantsInfo(infos) { if (infos.length === 0) { bestName = UtilsAdapter.getBestName(LRCInstance.currentAccountId, - UtilsAdapter.getCurrConvId()) + LRCInstance.selectedConvUid) } else { bestName = "" } diff --git a/src/mainview/components/PluginHandlerPicker.qml b/src/mainview/components/PluginHandlerPicker.qml index 75a399c2..337a2111 100644 --- a/src/mainview/components/PluginHandlerPicker.qml +++ b/src/mainview/components/PluginHandlerPicker.qml @@ -66,7 +66,7 @@ Popup { } else { // Reset the model on each show. var accountId = LRCInstance.currentAccountId - var peerId = UtilsAdapter.getPeerUri(accountId, UtilsAdapter.getCurrConvId()) + var peerId = UtilsAdapter.getPeerUri(accountId, LRCInstance.selectedConvUid) pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(accountId, peerId) } } @@ -80,7 +80,7 @@ Popup { pluginhandlerPickerListView.model = PluginAdapter.getMediaHandlerSelectableModel(callId) } else { var accountId = LRCInstance.currentAccountId - var peerId = UtilsAdapter.getPeerUri(accountId, UtilsAdapter.getCurrConvId()) + var peerId = UtilsAdapter.getPeerUri(accountId, LRCInstance.selectedConvUid) PluginModel.toggleChatHandler(handlerId, accountId, peerId, !isLoaded) pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(accountId, peerId) } @@ -139,7 +139,7 @@ Popup { return PluginAdapter.getMediaHandlerSelectableModel(callId) } else { var accountId = LRCInstance.currentAccountId - var peerId = UtilsAdapter.getPeerUri(accountId, UtilsAdapter.getCurrConvId()) + var peerId = UtilsAdapter.getPeerUri(accountId, LRCInstance.selectedConvUid) return PluginAdapter.getChatHandlerSelectableModel(accountId, peerId) } } diff --git a/src/mainview/components/ReadOnlyFooter.qml b/src/mainview/components/ReadOnlyFooter.qml new file mode 100644 index 00000000..a6ad9b58 --- /dev/null +++ b/src/mainview/components/ReadOnlyFooter.qml @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 by Savoir-faire Linux + * Author: Andreas Traczyk + * + * 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.14 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.14 + +import net.jami.Adapters 1.0 +import net.jami.Constants 1.0 + +import "../../commoncomponents" + +Control { + padding: 12 + + background: Rectangle { + anchors.fill: parent + color: JamiTheme.primaryBackgroundColor + + Rectangle { + anchors.top: parent.top + height: JamiTheme.messageWebViewHairLineSize + width: parent.width + color: JamiTheme.tabbarBorderColor + } + } + + contentItem: ColumnLayout { + spacing: 12 + Text { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.fillWidth: true + + text: JamiStrings.contactLeft + font.pointSize: JamiTheme.textFontSize + 2 + color: JamiTheme.textColor + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.fillWidth: true + spacing: 12 + + MaterialButton { + text: JamiStrings.removeContact + font.pointSize: JamiTheme.textFontSize + 2 + onClicked: MessagesAdapter.removeContact( + LRCInstance.selectedConvUid) + } + + MaterialButton { + text: JamiStrings.newConversation + font.pointSize: JamiTheme.textFontSize + 2 + onClicked: ConversationsAdapter.restartConversation( + LRCInstance.selectedConvUid) + } + } + } +} diff --git a/src/messagesadapter.cpp b/src/messagesadapter.cpp index 6f5b3d5f..93b7e6fd 100644 --- a/src/messagesadapter.cpp +++ b/src/messagesadapter.cpp @@ -543,20 +543,27 @@ MessagesAdapter::clearConversationHistory(const QString& accountId, const QStrin } void -MessagesAdapter::removeConversation(const QString& accountId, - const QString& convUid, - bool banContact) +MessagesAdapter::removeConversation(const QString& convUid) { - QStringList list = lrcInstance_->accountModel().getDefaultModerators(accountId); - const auto& convInfo = lrcInstance_->getConversationFromConvUid(convUid, accountId); - const auto contactURI = convInfo.participants.front(); + auto& accInfo = lrcInstance_->getCurrentAccountInfo(); + accInfo.conversationModel->removeConversation(convUid); +} - if (!contactURI.isEmpty() && list.contains(contactURI)) { - lrcInstance_->accountModel().setDefaultModerator(accountId, contactURI, false); +void +MessagesAdapter::removeContact(const QString& convUid, bool banContact) +{ + auto& accInfo = lrcInstance_->getCurrentAccountInfo(); + + // remove the uri from the default moderators list + // TODO: seems like this should be done in libringclient + QStringList list = lrcInstance_->accountModel().getDefaultModerators(accInfo.id); + const auto contactUri = accInfo.conversationModel->peersForConversation(convUid).at(0); + if (!contactUri.isEmpty() && list.contains(contactUri)) { + lrcInstance_->accountModel().setDefaultModerator(accInfo.id, contactUri, false); } - lrcInstance_->getAccountInfo(accountId).conversationModel->removeConversation(convUid, - banContact); + // actually remove the contact + accInfo.contactModel->removeContact(contactUri, banContact); } void diff --git a/src/messagesadapter.h b/src/messagesadapter.h index 0c97c931..6673651b 100644 --- a/src/messagesadapter.h +++ b/src/messagesadapter.h @@ -44,9 +44,8 @@ protected: Q_INVOKABLE void setupChatView(const QVariantMap& convInfo); Q_INVOKABLE void connectConversationModel(); Q_INVOKABLE void sendConversationRequest(); - Q_INVOKABLE void removeConversation(const QString& accountId, - const QString& convUid, - bool banContact = false); + Q_INVOKABLE void removeConversation(const QString& convUid); + Q_INVOKABLE void removeContact(const QString& convUid, bool banContact = false); Q_INVOKABLE void clearConversationHistory(const QString& accountId, const QString& convUid); Q_INVOKABLE void acceptInvitation(const QString& convId = {}); Q_INVOKABLE void refuseInvitation(const QString& convUid = ""); diff --git a/src/settingsview/components/KeyBoardShortcutTable.qml b/src/settingsview/components/KeyBoardShortcutTable.qml index 4eb49f72..a2ef4713 100644 --- a/src/settingsview/components/KeyBoardShortcutTable.qml +++ b/src/settingsview/components/KeyBoardShortcutTable.qml @@ -77,35 +77,34 @@ BaseDialog { Description: qsTr("Fullscreen") KeyLength: 1 } - // TODO: add the following after redesign - // ListElement { - // Shortcut: Qt.platform.os !== "windows" ? "Ctrl+Q" : "Alt+F4" - // Description: Qt.platform.os !== "windows" ? qsTr("Quit") : qsTr("Exit") - // KeyLength: 2 - // } } ListModel { id: keyboardConversationShortcutsModel ListElement { - Shortcut: "Shift+Ctrl+C" + Shortcut: "Ctrl+Shift+C" Description: qsTr("Start an audio call") KeyLength: 3 } ListElement { - Shortcut: "Shift+Ctrl+X" + Shortcut: "Ctrl+Shift+X" Description: qsTr("Start a video call") KeyLength: 3 } ListElement { - Shortcut: "Shift+Ctrl+L" + Shortcut: "Ctrl+Shift+L" Description: qsTr("Clear history") KeyLength: 3 } ListElement { - Shortcut: "Shift+Ctrl+B" + Shortcut: "Ctrl+Shift+B" Description: qsTr("Block contact") KeyLength: 3 } + ListElement { + Shortcut: "Ctrl+Shift+Delete" + Description: qsTr("Remove conversation") + KeyLength: 3 + } ListElement { Shortcut: "Shift+Ctrl+A" Description: qsTr("Accept contact request") diff --git a/src/utilsadapter.cpp b/src/utilsadapter.cpp index 95a56680..a417e17b 100644 --- a/src/utilsadapter.cpp +++ b/src/utilsadapter.cpp @@ -150,12 +150,6 @@ UtilsAdapter::setConversationFilter(const QString& filter) lrcInstance_->getCurrentConversationModel()->setFilter(filter); } -const QString -UtilsAdapter::getCurrConvId() -{ - return lrcInstance_->get_selectedConvUid(); -} - const QStringList UtilsAdapter::getCurrAccList() { diff --git a/src/utilsadapter.h b/src/utilsadapter.h index 11a53f7c..e80fee7d 100644 --- a/src/utilsadapter.h +++ b/src/utilsadapter.h @@ -53,7 +53,6 @@ public: Q_INVOKABLE const QString getPeerUri(const QString& accountId, const QString& uid); Q_INVOKABLE QString getBestId(const QString& accountId); Q_INVOKABLE const QString getBestId(const QString& accountId, const QString& uid); - Q_INVOKABLE const QString getCurrConvId(); Q_INVOKABLE const QStringList getCurrAccList(); Q_INVOKABLE int getAccountListSize(); Q_INVOKABLE bool hasCall(const QString& accountId);