1
0
Fork 0
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:
Sébastien Blin 2023-03-19 11:36:12 -04:00 committed by Andreas Traczyk
parent 43dbedbe0a
commit b41e5867c6
8 changed files with 105 additions and 33 deletions

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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
*/

View file

@ -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"))

View file

@ -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

View file

@ -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);
}

View file

@ -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"))