mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-09-10 12:03:18 +02:00
SIP: possibility to set custom avatar/display name
This allow users to be able to easily identify their contacts by changing the avatar/display name of a contact (for SIP and 1:1) https://git.jami.net/savoirfairelinux/jami-project/-/issues/757 Change-Id: I483a9116b78b08d43962abff982e73089bfec1d7
This commit is contained in:
parent
43dbedbe0a
commit
b41e5867c6
8 changed files with 105 additions and 33 deletions
|
@ -246,7 +246,8 @@ Rectangle {
|
|||
PushButton {
|
||||
id: detailsButton
|
||||
|
||||
visible: interactionButtonsVisibility && swarmDetailsVisibility
|
||||
visible: interactionButtonsVisibility
|
||||
&& (swarmDetailsVisibility || LRCInstance.currentAccountType === Profile.Type.SIP) // TODO if SIP not a request
|
||||
|
||||
source: JamiResources.swarm_details_panel_svg
|
||||
toolTipText: JamiStrings.details
|
||||
|
|
|
@ -34,7 +34,7 @@ StackLayout {
|
|||
|
||||
function restoreState() {
|
||||
// Only applies to Jami accounts, and we musn't be in a call.
|
||||
if (detailsShouldOpen && !inCallView && !CurrentConversation.isSip) {
|
||||
if (detailsShouldOpen && !inCallView) {
|
||||
switchToPanel(ChatView.SwarmDetailsPanel, false)
|
||||
} else {
|
||||
closePanel()
|
||||
|
@ -68,7 +68,7 @@ StackLayout {
|
|||
function closePanel() {
|
||||
// We need to close the panel, but not save it when appropriate.
|
||||
currentIndex = -1
|
||||
if (!inCallView && !CurrentConversation.isSip)
|
||||
if (!inCallView)
|
||||
detailsShouldOpen = false
|
||||
}
|
||||
|
||||
|
|
|
@ -36,10 +36,10 @@ Rectangle {
|
|||
property int tabBarItemsLength: tabBar.contentChildren.length
|
||||
|
||||
color: CurrentConversation.color
|
||||
property var isAdmin: !CurrentConversation.isCoreDialog &&
|
||||
UtilsAdapter.getParticipantRole(CurrentAccount.id,
|
||||
property var isAdmin: UtilsAdapter.getParticipantRole(CurrentAccount.id,
|
||||
CurrentConversation.id,
|
||||
CurrentAccount.uri) === Member.Role.ADMIN
|
||||
|| CurrentConversation.isCoreDialog
|
||||
|
||||
ColumnLayout {
|
||||
id: swarmProfileDetails
|
||||
|
@ -158,7 +158,7 @@ Rectangle {
|
|||
wrapMode: Text.NoWrap
|
||||
|
||||
text: formattedDescription.elidedText
|
||||
readOnly: !root.isAdmin
|
||||
readOnly: !root.isAdmin || CurrentConversation.isCoreDialog
|
||||
visible: root.isAdmin || text.length > 0
|
||||
placeholderText: JamiStrings.addADescription
|
||||
placeholderTextColor: {
|
||||
|
@ -364,6 +364,7 @@ Rectangle {
|
|||
SwarmDetailsItem {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: JamiTheme.settingsFontSize + 2 * JamiTheme.preferredMarginSize + 4
|
||||
visible: CurrentAccount.type !== Profile.Type.SIP // TODO for SIP save in VCard
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
@ -410,6 +411,7 @@ Rectangle {
|
|||
id: settingsSwarmItem
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: JamiTheme.settingsFontSize + 2 * JamiTheme.preferredMarginSize + 4
|
||||
visible: !CurrentConversation.isCoreDialog
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
@ -519,6 +521,7 @@ Rectangle {
|
|||
RowLayout {
|
||||
Layout.leftMargin: JamiTheme.preferredMarginSize
|
||||
Layout.preferredHeight: JamiTheme.settingsFontSize + 2 * JamiTheme.preferredMarginSize + 4
|
||||
visible: CurrentAccount.type !== Profile.Type.SIP
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
|
|
|
@ -87,6 +87,8 @@ public:
|
|||
const contact::Info getContact(const QString& contactUri) const;
|
||||
ContactInfoMap getSearchResults() const;
|
||||
|
||||
void updateContact(const QString& uri, const MapStringString& infos);
|
||||
|
||||
/**
|
||||
* Retrieve when a contact is added
|
||||
*/
|
||||
|
|
|
@ -65,14 +65,14 @@ getPath()
|
|||
}
|
||||
|
||||
static QString
|
||||
profileVcardPath(const QString& accountId, const QString& uri)
|
||||
profileVcardPath(const QString& accountId, const QString& uri, bool ov = false)
|
||||
{
|
||||
auto accountLocalPath = getPath() + accountId + QDir::separator();
|
||||
if (uri.isEmpty())
|
||||
return accountLocalPath + "profile.vcf";
|
||||
|
||||
auto fileName = QString(uri.toUtf8().toBase64());
|
||||
return accountLocalPath + "profiles" + QDir::separator() + fileName + ".vcf";
|
||||
return accountLocalPath + "profiles" + QDir::separator() + fileName + (ov ? "_o.vcf" : ".vcf");
|
||||
}
|
||||
|
||||
static QString
|
||||
|
@ -295,10 +295,10 @@ profileToVcard(const api::profile::Info& profileInfo, bool compressImage)
|
|||
}
|
||||
|
||||
void
|
||||
setProfile(const QString& accountId, const api::profile::Info& profileInfo, const bool isPeer)
|
||||
setProfile(const QString& accountId, const api::profile::Info& profileInfo, bool isPeer, bool ov)
|
||||
{
|
||||
auto vcard = vcard::profileToVcard(profileInfo);
|
||||
auto path = profileVcardPath(accountId, isPeer ? profileInfo.uri : "");
|
||||
auto path = profileVcardPath(accountId, isPeer ? profileInfo.uri : "", ov);
|
||||
QLockFile lf(path + ".lock");
|
||||
QFile file(path);
|
||||
QFileInfo fileInfo(path);
|
||||
|
@ -347,7 +347,8 @@ getPeerParticipantsForConversation(Database& db, const QString& conversationId)
|
|||
void
|
||||
createOrUpdateProfile(const QString& accountId,
|
||||
const api::profile::Info& profileInfo,
|
||||
const bool isPeer)
|
||||
bool isPeer,
|
||||
bool ov)
|
||||
{
|
||||
if (isPeer) {
|
||||
auto contact = storage::buildContactFromProfile(accountId,
|
||||
|
@ -357,19 +358,20 @@ createOrUpdateProfile(const QString& accountId,
|
|||
contact.profileInfo.alias = profileInfo.alias;
|
||||
if (!profileInfo.avatar.isEmpty())
|
||||
contact.profileInfo.avatar = profileInfo.avatar;
|
||||
vcard::setProfile(accountId, contact.profileInfo, isPeer);
|
||||
vcard::setProfile(accountId, contact.profileInfo, isPeer, ov);
|
||||
return;
|
||||
}
|
||||
vcard::setProfile(accountId, profileInfo, isPeer);
|
||||
vcard::setProfile(accountId, profileInfo, isPeer, ov);
|
||||
}
|
||||
|
||||
void
|
||||
removeProfile(const QString& accountId, const QString& peerUri)
|
||||
{
|
||||
auto path = profileVcardPath(accountId, peerUri);
|
||||
if (!QFile::remove(path)) {
|
||||
if (!QFile::remove(path))
|
||||
qWarning() << "Couldn't remove vcard for" << peerUri << "at" << path;
|
||||
}
|
||||
auto opath = profileVcardPath(accountId, peerUri, true);
|
||||
QFile::remove(opath);
|
||||
}
|
||||
|
||||
QString
|
||||
|
@ -388,21 +390,38 @@ getAccountAvatar(const QString& accountId)
|
|||
return photo;
|
||||
}
|
||||
|
||||
static QPair<QString, QString>
|
||||
getOverridenInfos(const QString& accountId, const QString& peerUri)
|
||||
{
|
||||
QString b64filePathOverride = profileVcardPath(accountId, peerUri, true);
|
||||
QFile fileOverride(b64filePathOverride);
|
||||
|
||||
QHash<QByteArray, QByteArray> overridenVCard;
|
||||
QString overridenAlias, overridenAvatar;
|
||||
if (fileOverride.open(QIODevice::ReadOnly)) {
|
||||
overridenVCard = lrc::vCard::utils::toHashMap(fileOverride.readAll());
|
||||
overridenAlias = overridenVCard[vCard::Property::FORMATTED_NAME];
|
||||
for (const auto& key : overridenVCard.keys())
|
||||
if (key.contains("PHOTO"))
|
||||
overridenAvatar = overridenVCard[key];
|
||||
}
|
||||
return {overridenAlias, overridenAvatar};
|
||||
}
|
||||
|
||||
api::contact::Info
|
||||
buildContactFromProfile(const QString& accountId,
|
||||
const QString& peer_uri,
|
||||
const QString& peerUri,
|
||||
const api::profile::Type& type)
|
||||
{
|
||||
lrc::api::profile::Info profileInfo;
|
||||
profileInfo.uri = peer_uri;
|
||||
profileInfo.uri = peerUri;
|
||||
profileInfo.type = type;
|
||||
auto accountLocalPath = getPath() + accountId + "/";
|
||||
QString b64filePath;
|
||||
b64filePath = profileVcardPath(accountId, peer_uri);
|
||||
QString b64filePath = profileVcardPath(accountId, peerUri);
|
||||
QFile file(b64filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
// try non-base64 path
|
||||
QString filePath = accountLocalPath + "profiles/" + peer_uri + ".vcf";
|
||||
QString filePath = accountLocalPath + "profiles/" + peerUri + ".vcf";
|
||||
file.setFileName(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
return {profileInfo, "", true, false};
|
||||
|
@ -416,30 +435,40 @@ buildContactFromProfile(const QString& accountId,
|
|||
return {profileInfo, "", true, false};
|
||||
}
|
||||
}
|
||||
|
||||
auto [overridenAlias, overridenAvatar] = getOverridenInfos(accountId, peerUri);
|
||||
|
||||
const auto vCard = lrc::vCard::utils::toHashMap(file.readAll());
|
||||
const auto alias = vCard[vCard::Property::FORMATTED_NAME];
|
||||
if (lrc::api::Lrc::cacheAvatars.load()) {
|
||||
for (const auto& key : vCard.keys()) {
|
||||
if (key.contains("PHOTO"))
|
||||
profileInfo.avatar = vCard[key];
|
||||
if (overridenAvatar.isEmpty()) {
|
||||
for (const auto& key : vCard.keys()) {
|
||||
if (key.contains("PHOTO"))
|
||||
profileInfo.avatar = vCard[key];
|
||||
}
|
||||
} else {
|
||||
profileInfo.avatar = overridenAvatar;
|
||||
}
|
||||
}
|
||||
profileInfo.alias = alias;
|
||||
profileInfo.alias = overridenAlias.isEmpty() ? alias : overridenAlias;
|
||||
return {profileInfo, "", type == api::profile::Type::JAMI, false};
|
||||
}
|
||||
|
||||
QString
|
||||
avatar(const QString& accountId, const QString& contactId)
|
||||
avatar(const QString& accountId, const QString& peerUri)
|
||||
{
|
||||
if (contactId.isEmpty())
|
||||
if (peerUri.isEmpty())
|
||||
return getAccountAvatar(accountId);
|
||||
auto accountLocalPath = getPath() + accountId + "/";
|
||||
|
||||
auto [_overridenAlias, overridenAvatar] = getOverridenInfos(accountId, peerUri);
|
||||
if (!overridenAvatar.isEmpty())
|
||||
return overridenAvatar;
|
||||
|
||||
QString b64filePath;
|
||||
b64filePath = profileVcardPath(accountId, contactId);
|
||||
b64filePath = profileVcardPath(accountId, peerUri);
|
||||
QFile file(b64filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return {};
|
||||
}
|
||||
const auto vCard = lrc::vCard::utils::toHashMap(file.readAll());
|
||||
for (const auto& key : vCard.keys()) {
|
||||
if (key.contains("PHOTO"))
|
||||
|
|
|
@ -89,10 +89,13 @@ QString profileToVcard(const api::profile::Info& profileInfo, bool compressImage
|
|||
* @param accountId
|
||||
* @param profileInfo
|
||||
* @param isPeer
|
||||
* @param ov If from daemon override must be false, if the client want to override the vcard
|
||||
* should be true
|
||||
*/
|
||||
void setProfile(const QString& accountId,
|
||||
const api::profile::Info& profileInfo,
|
||||
const bool isPeer = false);
|
||||
bool isPeer = false,
|
||||
bool ov = false);
|
||||
|
||||
} // namespace vcard
|
||||
|
||||
|
@ -121,10 +124,12 @@ VectorString getPeerParticipantsForConversation(Database& db, const QString& con
|
|||
* @param accountId
|
||||
* @param profileInfo the contact info containing peer profile information
|
||||
* @param isPeer indicates that a the profileInfo is that of a peer
|
||||
* @param ov if the client is storing a new vcard
|
||||
*/
|
||||
void createOrUpdateProfile(const QString& accountId,
|
||||
const api::profile::Info& profileInfo,
|
||||
const bool isPeer = false);
|
||||
bool isPeer = false,
|
||||
bool ov = false);
|
||||
|
||||
/**
|
||||
* Remove a profile vCard
|
||||
|
|
|
@ -376,6 +376,27 @@ ContactModel::getContact(const QString& contactUri) const
|
|||
throw std::out_of_range("Contact out of range");
|
||||
}
|
||||
|
||||
void
|
||||
ContactModel::updateContact(const QString& uri, const MapStringString& infos)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(pimpl_->contactsMtx_);
|
||||
auto ci = pimpl_->contacts.find(uri);
|
||||
if (ci != pimpl_->contacts.end()) {
|
||||
if (infos.contains("avatar")) {
|
||||
ci->profileInfo.avatar = storage::vcard::compressedAvatar(infos["avatar"]);
|
||||
} else if (!lrc::api::Lrc::cacheAvatars.load()) {
|
||||
// Else it will be reseted
|
||||
ci->profileInfo.avatar = storage::avatar(owner.id, uri);
|
||||
}
|
||||
if (infos.contains("title"))
|
||||
ci->profileInfo.alias = infos["title"];
|
||||
storage::createOrUpdateProfile(owner.id, ci->profileInfo, true, true);
|
||||
lk.unlock();
|
||||
Q_EMIT profileUpdated(uri);
|
||||
Q_EMIT modelUpdated(uri);
|
||||
}
|
||||
}
|
||||
|
||||
const QList<QString>&
|
||||
ContactModel::getBannedContacts() const
|
||||
{
|
||||
|
@ -546,7 +567,7 @@ ContactModel::avatar(const QString& uri) const
|
|||
return contact.profileInfo.avatar;
|
||||
}
|
||||
}
|
||||
// Else search in storage
|
||||
// Else search in storage (because not cached!)
|
||||
return storage::avatar(owner.id, uri);
|
||||
}
|
||||
|
||||
|
|
|
@ -1047,6 +1047,17 @@ void
|
|||
ConversationModel::updateConversationInfos(const QString& conversationId,
|
||||
const MapStringString infos)
|
||||
{
|
||||
auto conversationOpt = getConversationForUid(conversationId);
|
||||
if (!conversationOpt.has_value())
|
||||
return;
|
||||
auto& conversation = conversationOpt->get();
|
||||
if (conversation.isCoreDialog()) {
|
||||
// If 1:1, we override a profile (as the peer will send their new profiles)
|
||||
auto peer = pimpl_->peersForConversation(conversation);
|
||||
if (!peer.isEmpty())
|
||||
owner.contactModel->updateContact(peer.at(0), infos);
|
||||
return;
|
||||
}
|
||||
MapStringString newInfos = infos;
|
||||
// Compress avatar as it will be sent in the conversation's request over the DHT
|
||||
if (infos.contains("avatar"))
|
||||
|
|
Loading…
Add table
Reference in a new issue