Skip to content
Snippets Groups Projects
Commit a45754ed authored by Andreas Traczyk's avatar Andreas Traczyk
Browse files

conversations: provide descriptive help banner for read-only convs

Adds a new chat view footer to describe for read-only conversations
to describe the concept and provide options to remove the contact
or restart a new conversation with the contact.

Gitlab: #475
Change-Id: I6ca0e8dfbf49f17a6ff93a2744c552b76d923773
parent 6d0c1255
Branches
No related tags found
No related merge requests found
Showing
with 180 additions and 42 deletions
...@@ -161,5 +161,6 @@ ...@@ -161,5 +161,6 @@
<file>src/commoncomponents/BubbleLabel.qml</file> <file>src/commoncomponents/BubbleLabel.qml</file>
<file>src/commoncomponents/BackButton.qml</file> <file>src/commoncomponents/BackButton.qml</file>
<file>src/commoncomponents/JamiSwitch.qml</file> <file>src/commoncomponents/JamiSwitch.qml</file>
<file>src/mainview/components/ReadOnlyFooter.qml</file>
</qresource> </qresource>
</RCC> </RCC>
...@@ -40,6 +40,8 @@ Item { ...@@ -40,6 +40,8 @@ Item {
property string contactSearchInvitations: qsTr("Search your invitations") property string contactSearchInvitations: qsTr("Search your invitations")
property string invitations: qsTr("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 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 // AboutPopUp
property string version: qsTr("Version") + (UpdateManager.isCurrentVersionBeta() ? " (BETA)" : "") property string version: qsTr("Version") + (UpdateManager.isCurrentVersionBeta() ? " (BETA)" : "")
...@@ -222,6 +224,7 @@ Item { ...@@ -222,6 +224,7 @@ Item {
property string startVideoCall: qsTr("Start video call") property string startVideoCall: qsTr("Start video call")
property string startAudioCall: qsTr("Start audio call") property string startAudioCall: qsTr("Start audio call")
property string clearConversation: qsTr("Clear conversation") property string clearConversation: qsTr("Clear conversation")
property string removeConversation: qsTr("Remove conversation")
property string removeContact: qsTr("Remove contact") property string removeContact: qsTr("Remove contact")
property string blockContact: qsTr("Block contact") property string blockContact: qsTr("Block contact")
property string contactDetails: qsTr("Contact details") property string contactDetails: qsTr("Contact details")
......
...@@ -311,7 +311,7 @@ Item { ...@@ -311,7 +311,7 @@ Item {
property real wizardButtonWidth: 400 property real wizardButtonWidth: 400
// Main application spec // Main application spec
property real mainViewMinWidth: 300 property real mainViewMinWidth: 332
property real mainViewMinHeight: 500 property real mainViewMinHeight: 500
property real wizardViewMinWidth: 500 property real wizardViewMinWidth: 500
......
...@@ -433,6 +433,53 @@ ConversationsAdapter::getConvInfoMap(const QString& convId) ...@@ -433,6 +433,53 @@ ConversationsAdapter::getConvInfoMap(const QString& convId)
{"readOnly", convInfo.readOnly}}; {"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 bool
ConversationsAdapter::connectConversationModel() ConversationsAdapter::connectConversationModel()
{ {
......
...@@ -50,6 +50,7 @@ public: ...@@ -50,6 +50,7 @@ public:
Q_INVOKABLE bool connectConversationModel(); Q_INVOKABLE bool connectConversationModel();
Q_INVOKABLE void setFilter(const QString& filterString); Q_INVOKABLE void setFilter(const QString& filterString);
Q_INVOKABLE QVariantMap getConvInfoMap(const QString& convId); Q_INVOKABLE QVariantMap getConvInfoMap(const QString& convId);
Q_INVOKABLE void restartConversation(const QString& convId);
Q_SIGNALS: Q_SIGNALS:
void showConversation(const QString& accountId, const QString& convUid); void showConversation(const QString& accountId, const QString& convUid);
......
...@@ -175,7 +175,7 @@ ListView { ...@@ -175,7 +175,7 @@ ListView {
enabled: root.visible enabled: root.visible
onActivated: MessagesAdapter.clearConversationHistory( onActivated: MessagesAdapter.clearConversationHistory(
LRCInstance.currentAccountId, LRCInstance.currentAccountId,
UtilsAdapter.getCurrConvId()) LRCInstance.selectedConvUid)
} }
Shortcut { Shortcut {
...@@ -183,7 +183,8 @@ ListView { ...@@ -183,7 +183,8 @@ ListView {
context: Qt.ApplicationShortcut context: Qt.ApplicationShortcut
enabled: root.visible enabled: root.visible
onActivated: { onActivated: {
MessagesAdapter.blockConversation(UtilsAdapter.getCurrConvId()) MessagesAdapter.blockConversation(
LRCInstance.selectedConvUid)
} }
} }
...@@ -192,9 +193,7 @@ ListView { ...@@ -192,9 +193,7 @@ ListView {
context: Qt.ApplicationShortcut context: Qt.ApplicationShortcut
enabled: root.visible enabled: root.visible
onActivated: MessagesAdapter.removeConversation( onActivated: MessagesAdapter.removeConversation(
LRCInstance.currentAccountId, LRCInstance.selectedConvUid)
UtilsAdapter.getCurrConvId(),
false)
} }
Shortcut { Shortcut {
......
...@@ -77,6 +77,15 @@ ContextMenuAutoLoader { ...@@ -77,6 +77,15 @@ ContextMenuAutoLoader {
responsibleAccountId, responsibleAccountId,
responsibleConvUid) responsibleConvUid)
}, },
GeneralMenuItem {
id: removeConversation
canTrigger: isSwarm && !hasCall
itemName: JamiStrings.removeConversation
iconSource: JamiResources.delete_24dp_svg
onClicked: MessagesAdapter.removeConversation(
responsibleConvUid)
},
GeneralMenuItem { GeneralMenuItem {
id: removeContact id: removeContact
...@@ -84,8 +93,7 @@ ContextMenuAutoLoader { ...@@ -84,8 +93,7 @@ ContextMenuAutoLoader {
|| contactType === Profile.Type.SIP) || contactType === Profile.Type.SIP)
itemName: JamiStrings.removeContact itemName: JamiStrings.removeContact
iconSource: JamiResources.ic_hangup_participant_24dp_svg iconSource: JamiResources.ic_hangup_participant_24dp_svg
onClicked: MessagesAdapter.removeConversation(responsibleAccountId, onClicked: MessagesAdapter.removeContact(responsibleConvUid)
responsibleConvUid)
}, },
GeneralMenuItem { GeneralMenuItem {
id: hangup id: hangup
......
...@@ -257,6 +257,11 @@ Rectangle { ...@@ -257,6 +257,11 @@ Rectangle {
} }
} }
ReadOnlyFooter {
visible: CurrentConversation.readOnly
Layout.fillWidth: true
}
MessageWebViewFooter { MessageWebViewFooter {
id: messageWebViewFooter id: messageWebViewFooter
......
...@@ -88,7 +88,7 @@ Rectangle { ...@@ -88,7 +88,7 @@ Rectangle {
function handleParticipantsInfo(infos) { function handleParticipantsInfo(infos) {
if (infos.length === 0) { if (infos.length === 0) {
bestName = UtilsAdapter.getBestName(LRCInstance.currentAccountId, bestName = UtilsAdapter.getBestName(LRCInstance.currentAccountId,
UtilsAdapter.getCurrConvId()) LRCInstance.selectedConvUid)
} else { } else {
bestName = "" bestName = ""
} }
......
...@@ -66,7 +66,7 @@ Popup { ...@@ -66,7 +66,7 @@ Popup {
} else { } else {
// Reset the model on each show. // Reset the model on each show.
var accountId = LRCInstance.currentAccountId var accountId = LRCInstance.currentAccountId
var peerId = UtilsAdapter.getPeerUri(accountId, UtilsAdapter.getCurrConvId()) var peerId = UtilsAdapter.getPeerUri(accountId, LRCInstance.selectedConvUid)
pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(accountId, peerId) pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(accountId, peerId)
} }
} }
...@@ -80,7 +80,7 @@ Popup { ...@@ -80,7 +80,7 @@ Popup {
pluginhandlerPickerListView.model = PluginAdapter.getMediaHandlerSelectableModel(callId) pluginhandlerPickerListView.model = PluginAdapter.getMediaHandlerSelectableModel(callId)
} else { } else {
var accountId = LRCInstance.currentAccountId var accountId = LRCInstance.currentAccountId
var peerId = UtilsAdapter.getPeerUri(accountId, UtilsAdapter.getCurrConvId()) var peerId = UtilsAdapter.getPeerUri(accountId, LRCInstance.selectedConvUid)
PluginModel.toggleChatHandler(handlerId, accountId, peerId, !isLoaded) PluginModel.toggleChatHandler(handlerId, accountId, peerId, !isLoaded)
pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(accountId, peerId) pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(accountId, peerId)
} }
...@@ -139,7 +139,7 @@ Popup { ...@@ -139,7 +139,7 @@ Popup {
return PluginAdapter.getMediaHandlerSelectableModel(callId) return PluginAdapter.getMediaHandlerSelectableModel(callId)
} else { } else {
var accountId = LRCInstance.currentAccountId var accountId = LRCInstance.currentAccountId
var peerId = UtilsAdapter.getPeerUri(accountId, UtilsAdapter.getCurrConvId()) var peerId = UtilsAdapter.getPeerUri(accountId, LRCInstance.selectedConvUid)
return PluginAdapter.getChatHandlerSelectableModel(accountId, peerId) return PluginAdapter.getChatHandlerSelectableModel(accountId, peerId)
} }
} }
......
/*
* Copyright (C) 2021 by Savoir-faire Linux
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* 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 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)
}
}
}
}
...@@ -543,20 +543,27 @@ MessagesAdapter::clearConversationHistory(const QString& accountId, const QStrin ...@@ -543,20 +543,27 @@ MessagesAdapter::clearConversationHistory(const QString& accountId, const QStrin
} }
void void
MessagesAdapter::removeConversation(const QString& accountId, MessagesAdapter::removeConversation(const QString& convUid)
const QString& convUid,
bool banContact)
{ {
QStringList list = lrcInstance_->accountModel().getDefaultModerators(accountId); auto& accInfo = lrcInstance_->getCurrentAccountInfo();
const auto& convInfo = lrcInstance_->getConversationFromConvUid(convUid, accountId); accInfo.conversationModel->removeConversation(convUid);
const auto contactURI = convInfo.participants.front(); }
void
MessagesAdapter::removeContact(const QString& convUid, bool banContact)
{
auto& accInfo = lrcInstance_->getCurrentAccountInfo();
if (!contactURI.isEmpty() && list.contains(contactURI)) { // remove the uri from the default moderators list
lrcInstance_->accountModel().setDefaultModerator(accountId, contactURI, false); // 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, // actually remove the contact
banContact); accInfo.contactModel->removeContact(contactUri, banContact);
} }
void void
......
...@@ -44,9 +44,8 @@ protected: ...@@ -44,9 +44,8 @@ protected:
Q_INVOKABLE void setupChatView(const QVariantMap& convInfo); Q_INVOKABLE void setupChatView(const QVariantMap& convInfo);
Q_INVOKABLE void connectConversationModel(); Q_INVOKABLE void connectConversationModel();
Q_INVOKABLE void sendConversationRequest(); Q_INVOKABLE void sendConversationRequest();
Q_INVOKABLE void removeConversation(const QString& accountId, Q_INVOKABLE void removeConversation(const QString& convUid);
const QString& convUid, Q_INVOKABLE void removeContact(const QString& convUid, bool banContact = false);
bool banContact = false);
Q_INVOKABLE void clearConversationHistory(const QString& accountId, const QString& convUid); Q_INVOKABLE void clearConversationHistory(const QString& accountId, const QString& convUid);
Q_INVOKABLE void acceptInvitation(const QString& convId = {}); Q_INVOKABLE void acceptInvitation(const QString& convId = {});
Q_INVOKABLE void refuseInvitation(const QString& convUid = ""); Q_INVOKABLE void refuseInvitation(const QString& convUid = "");
......
...@@ -77,35 +77,34 @@ BaseDialog { ...@@ -77,35 +77,34 @@ BaseDialog {
Description: qsTr("Fullscreen") Description: qsTr("Fullscreen")
KeyLength: 1 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 { ListModel {
id: keyboardConversationShortcutsModel id: keyboardConversationShortcutsModel
ListElement { ListElement {
Shortcut: "Shift+Ctrl+C" Shortcut: "Ctrl+Shift+C"
Description: qsTr("Start an audio call") Description: qsTr("Start an audio call")
KeyLength: 3 KeyLength: 3
} }
ListElement { ListElement {
Shortcut: "Shift+Ctrl+X" Shortcut: "Ctrl+Shift+X"
Description: qsTr("Start a video call") Description: qsTr("Start a video call")
KeyLength: 3 KeyLength: 3
} }
ListElement { ListElement {
Shortcut: "Shift+Ctrl+L" Shortcut: "Ctrl+Shift+L"
Description: qsTr("Clear history") Description: qsTr("Clear history")
KeyLength: 3 KeyLength: 3
} }
ListElement { ListElement {
Shortcut: "Shift+Ctrl+B" Shortcut: "Ctrl+Shift+B"
Description: qsTr("Block contact") Description: qsTr("Block contact")
KeyLength: 3 KeyLength: 3
} }
ListElement {
Shortcut: "Ctrl+Shift+Delete"
Description: qsTr("Remove conversation")
KeyLength: 3
}
ListElement { ListElement {
Shortcut: "Shift+Ctrl+A" Shortcut: "Shift+Ctrl+A"
Description: qsTr("Accept contact request") Description: qsTr("Accept contact request")
......
...@@ -150,12 +150,6 @@ UtilsAdapter::setConversationFilter(const QString& filter) ...@@ -150,12 +150,6 @@ UtilsAdapter::setConversationFilter(const QString& filter)
lrcInstance_->getCurrentConversationModel()->setFilter(filter); lrcInstance_->getCurrentConversationModel()->setFilter(filter);
} }
const QString
UtilsAdapter::getCurrConvId()
{
return lrcInstance_->get_selectedConvUid();
}
const QStringList const QStringList
UtilsAdapter::getCurrAccList() UtilsAdapter::getCurrAccList()
{ {
......
...@@ -53,7 +53,6 @@ public: ...@@ -53,7 +53,6 @@ public:
Q_INVOKABLE const QString getPeerUri(const QString& accountId, const QString& uid); Q_INVOKABLE const QString getPeerUri(const QString& accountId, const QString& uid);
Q_INVOKABLE QString getBestId(const QString& accountId); Q_INVOKABLE QString getBestId(const QString& accountId);
Q_INVOKABLE const QString getBestId(const QString& accountId, const QString& uid); Q_INVOKABLE const QString getBestId(const QString& accountId, const QString& uid);
Q_INVOKABLE const QString getCurrConvId();
Q_INVOKABLE const QStringList getCurrAccList(); Q_INVOKABLE const QStringList getCurrAccList();
Q_INVOKABLE int getAccountListSize(); Q_INVOKABLE int getAccountListSize();
Q_INVOKABLE bool hasCall(const QString& accountId); Q_INVOKABLE bool hasCall(const QString& accountId);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment