Fix reentrency issues with magic search :

- store search parameters into Core.
- add search limitation to avoid 300 useless items.
- retrieve old parameters on proxy when changing list.
- store parent proxy to avoid MOC warnings.

Fix contacts search views:
- add a loading state for buzy indicators.
- limit results on suggestions.
- avoid to create MagicSearchProxy if not needed.
- add a status to know if friend is stored or not.
- propagate invalidateFilter.
- delay search while typing.

Fix margins and participants selection.
Do not search contacts when contact panel is not shown.

Avoid search on empty magicbar.
Avoid repeating section on object that disappeared from cache.
Focus on new contact after creation.

Avoid changing maxresult if not needed.

Redirect only if friend is not ldap

Fix empty display name on making favorite a ldap contact.

Fix focus and positions on favorites.
This commit is contained in:
Julien Wadel 2024-11-15 11:16:55 +01:00
parent 97c2c1e214
commit a6561ccb19
22 changed files with 1158 additions and 883 deletions

View file

@ -824,8 +824,6 @@ void CallCore::findRemoteLdapFriend(QSharedPointer<CallCore> me) {
auto linphoneSearch = CoreModel::getInstance()->getCore()->createMagicSearch();
linphoneSearch->setLimitedSearch(true);
mLdapMagicSearchModel = Utils::makeQObject_ptr<MagicSearchModel>(linphoneSearch);
mLdapMagicSearchModel->setSourceFlags((int)LinphoneEnums::MagicSearchSource::LdapServers);
mLdapMagicSearchModel->setAggregationFlag(LinphoneEnums::MagicSearchAggregation::Friend);
mLdapMagicSearchModel->setSelf(mLdapMagicSearchModel);
mLdapMagicSearchModelConnection = QSharedPointer<SafeConnection<CallCore, MagicSearchModel>>(
new SafeConnection<CallCore, MagicSearchModel>(me, mLdapMagicSearchModel), &QObject::deleteLater);
@ -845,5 +843,6 @@ void CallCore::findRemoteLdapFriend(QSharedPointer<CallCore> me) {
});
}
});
mLdapMagicSearchModel->search(mRemoteUsername);
mLdapMagicSearchModel->search(mRemoteAddress, (int)LinphoneEnums::MagicSearchSource::LdapServers,
LinphoneEnums::MagicSearchAggregation::Friend, -1);
}

View file

@ -44,14 +44,14 @@ QVariant createFriendDevice(const QString &name, const QString &address, Linphon
return map;
}
QSharedPointer<FriendCore> FriendCore::create(const std::shared_ptr<linphone::Friend> &contact) {
auto sharedPointer = QSharedPointer<FriendCore>(new FriendCore(contact), &QObject::deleteLater);
QSharedPointer<FriendCore> FriendCore::create(const std::shared_ptr<linphone::Friend> &contact, bool isStored) {
auto sharedPointer = QSharedPointer<FriendCore>(new FriendCore(contact, isStored), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObject(nullptr) {
FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact, bool isStored) : QObject(nullptr) {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
if (contact) {
mustBeInLinphoneThread(getClassName());
@ -60,6 +60,7 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObje
mConsolidatedPresence = LinphoneEnums::fromLinphone(contact->getConsolidatedPresence());
mPresenceTimestamp = mFriendModel->getPresenceTimestamp();
mPictureUri = Utils::coreStringToAppString(contact->getPhoto());
auto defaultAddress = contact->getAddress();
auto vcard = contact->getVcard();
if (vcard) {
mOrganization = Utils::coreStringToAppString(vcard->getOrganization());
@ -69,13 +70,16 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObje
mFullName = Utils::coreStringToAppString(vcard->getFullName());
mVCardString = Utils::coreStringToAppString(vcard->asVcard4String());
}
if (defaultAddress) {
if (mGivenName.isEmpty()) mGivenName = Utils::coreStringToAppString(defaultAddress->getUsername());
}
auto addresses = contact->getAddresses();
for (auto &address : addresses) {
mAddressList.append(
createFriendAddressVariant(_addressLabel, Utils::coreStringToAppString(address->asStringUriOnly())));
}
mDefaultAddress =
contact->getAddress() ? Utils::coreStringToAppString(contact->getAddress()->asStringUriOnly()) : QString();
defaultAddress ? Utils::coreStringToAppString(contact->getAddress()->asStringUriOnly()) : QString();
auto phoneNumbers = contact->getPhoneNumbersWithLabel();
for (auto &phoneNumber : phoneNumbers) {
mPhoneNumberList.append(
@ -94,9 +98,11 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObje
mStarred = contact->getStarred();
mIsSaved = true;
mIsStored = isStored;
} else {
mIsSaved = false;
mStarred = false;
mIsStored = false;
}
mIsLdap = ToolModel::friendIsInFriendList(ToolModel::getLdapFriendList(), contact);
@ -502,15 +508,28 @@ bool FriendCore::getIsSaved() const {
void FriendCore::setIsSaved(bool data) {
if (mIsSaved != data) {
mIsSaved = data;
if (mIsSaved) setIsStored(true);
emit isSavedChanged(mIsSaved);
}
}
bool FriendCore::getIsStored() const {
return mIsStored;
}
void FriendCore::setIsStored(bool data) {
if (mIsStored != data) {
mIsStored = data;
emit isStoredChanged();
}
}
void FriendCore::writeIntoModel(std::shared_ptr<FriendModel> model) const {
mustBeInLinphoneThread(QString("[") + gClassName + "] " + Q_FUNC_INFO);
model->getFriend()->edit();
// needed to create the vcard if not created yet
model->setName(mGivenName + (mFamilyName.isEmpty() || mGivenName.isEmpty() ? "" : " ") + mFamilyName);
model->setName(mFullName.isEmpty()
? mGivenName + (mFamilyName.isEmpty() || mGivenName.isEmpty() ? "" : " ") + mFamilyName
: mFullName);
auto core = CoreModel::getInstance()->getCore();
std::list<std::shared_ptr<linphone::Address>> addresses;

View file

@ -65,6 +65,7 @@ class FriendCore : public QObject, public AbstractObject {
Q_PROPERTY(LinphoneEnums::ConsolidatedPresence consolidatedPresence READ getConsolidatedPresence NOTIFY
consolidatedPresenceChanged)
Q_PROPERTY(bool isSaved READ getIsSaved NOTIFY isSavedChanged)
Q_PROPERTY(bool isStored READ getIsStored NOTIFY isStoredChanged)
Q_PROPERTY(QString pictureUri READ getPictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
Q_PROPERTY(bool starred READ getStarred WRITE lSetStarred NOTIFY starredChanged)
Q_PROPERTY(bool readOnly READ getReadOnly CONSTANT)
@ -72,8 +73,8 @@ class FriendCore : public QObject, public AbstractObject {
public:
// Should be call from model Thread. Will be automatically in App thread after initialization
static QSharedPointer<FriendCore> create(const std::shared_ptr<linphone::Friend> &contact);
FriendCore(const std::shared_ptr<linphone::Friend> &contact);
static QSharedPointer<FriendCore> create(const std::shared_ptr<linphone::Friend> &contact, bool isStored = true);
FriendCore(const std::shared_ptr<linphone::Friend> &contact, bool isStored = true);
FriendCore(const FriendCore &friendCore);
~FriendCore();
void setSelf(QSharedPointer<FriendCore> me);
@ -81,7 +82,6 @@ public:
void reset(const FriendCore &contact);
QString getDisplayName() const;
void setDisplayName(const QString &name);
QString getFamilyName() const;
void setFamilyName(const QString &name);
@ -131,6 +131,9 @@ public:
bool getIsSaved() const;
void setIsSaved(bool isSaved);
bool getIsStored() const; // Exist in DB
void setIsStored(bool isStored);
QString getPictureUri() const;
void setPictureUri(const QString &uri);
void onPictureUriChanged(QString uri);
@ -163,6 +166,7 @@ signals:
void pictureUriChanged();
void saved();
void isSavedChanged(bool isSaved);
void isStoredChanged();
void removed(FriendCore *contact);
void defaultAddressChanged();
void allAddressesChanged();
@ -189,6 +193,7 @@ protected:
QString mDefaultAddress;
QString mPictureUri;
bool mIsSaved;
bool mIsStored;
QString mVCardString;
bool mIsLdap;
std::shared_ptr<FriendModel> mFriendModel;

View file

@ -26,11 +26,13 @@ DEFINE_ABSTRACT_OBJECT(FriendGui)
FriendGui::FriendGui(QObject *parent) : QObject(parent) {
mustBeInMainThread(getClassName());
mCore = FriendCore::create(nullptr);
connect(mCore.get(), &FriendCore::isStoredChanged, this, &FriendGui::isStoredChanged);
}
FriendGui::FriendGui(QSharedPointer<FriendCore> core) {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
mCore = core;
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
connect(mCore.get(), &FriendCore::isStoredChanged, this, &FriendGui::isStoredChanged);
}
FriendGui::~FriendGui() {
@ -43,3 +45,7 @@ void FriendGui::createContact(const QString &address) {
FriendCore *FriendGui::getCore() const {
return mCore.get();
}
bool FriendGui::getIsStored() {
return mCore ? mCore->getIsStored() : false;
}

View file

@ -29,6 +29,7 @@ class FriendGui : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(FriendCore *core READ getCore CONSTANT)
Q_PROPERTY(bool isStored READ getIsStored NOTIFY isStoredChanged)
public:
FriendGui(QObject *parent = nullptr);
@ -37,6 +38,12 @@ public:
FriendCore *getCore() const;
Q_INVOKABLE void createContact(const QString &address);
QSharedPointer<FriendCore> mCore;
bool getIsStored();
signals:
void isStoredChanged();
public:
DECLARE_ABSTRACT_OBJECT
};

View file

@ -88,3 +88,7 @@ void SortFilterProxy::setSortOrder(const Qt::SortOrder &order) {
void SortFilterProxy::remove(int index, int count) {
QSortFilterProxyModel::removeRows(index, count);
}
void SortFilterProxy::invalidateFilter() {
QSortFilterProxyModel::invalidateFilter();
}

View file

@ -69,6 +69,7 @@ public:
void setFilterText(const QString &filter);
Q_INVOKABLE void remove(int index, int count = 1);
void invalidateFilter();
signals:
void countChanged();

View file

@ -21,7 +21,6 @@
#include "MagicSearchList.hpp"
#include "core/App.hpp"
#include "core/friend/FriendCore.hpp"
#include "core/friend/FriendGui.hpp"
#include "tool/Utils.hpp"
#include <QSharedPointer>
#include <linphone++/linphone.hh>
@ -62,7 +61,7 @@ void MagicSearchList::setSelf(QSharedPointer<MagicSearchList> me) {
if (haveContact == mList.end()) {
connect(friendCore.get(), &FriendCore::removed, this, qOverload<QObject *>(&MagicSearchList::remove));
add(friendCore);
emit friendCreated(getCount() - 1);
emit friendCreated(getCount() - 1, new FriendGui(friendCore));
}
});
mCoreModelConnection->invokeToModel([this] {
@ -71,34 +70,17 @@ void MagicSearchList::setSelf(QSharedPointer<MagicSearchList> me) {
auto magicSearch = Utils::makeQObject_ptr<MagicSearchModel>(linphoneSearch);
mCoreModelConnection->invokeToCore([this, magicSearch] {
mMagicSearch = magicSearch;
mMagicSearch->mSourceFlags = mSourceFlags;
mMagicSearch->mAggregationFlag = mAggregationFlag;
mMagicSearch->setSelf(mMagicSearch);
mModelConnection = QSharedPointer<SafeConnection<MagicSearchList, MagicSearchModel>>(
new SafeConnection<MagicSearchList, MagicSearchModel>(mCoreModelConnection->mCore.mQData, mMagicSearch),
&QObject::deleteLater);
mModelConnection->makeConnectToCore(&MagicSearchList::lSearch, [this](QString filter) {
mModelConnection->invokeToModel([this, filter]() { mMagicSearch->search(filter); });
});
mModelConnection->makeConnectToCore(&MagicSearchList::lSetSourceFlags, [this](int flags) {
mModelConnection->invokeToModel([this, flags]() { mMagicSearch->setSourceFlags(flags); });
});
mModelConnection->makeConnectToCore(&MagicSearchList::lSetAggregationFlag,
[this](LinphoneEnums::MagicSearchAggregation aggregation) {
mModelConnection->invokeToModel([this, aggregation]() {
mMagicSearch->setAggregationFlag(aggregation);
});
});
mModelConnection->makeConnectToCore(
&MagicSearchList::lSetAggregationFlag, [this](LinphoneEnums::MagicSearchAggregation flag) {
mModelConnection->invokeToModel([this, flag]() { mMagicSearch->setAggregationFlag(flag); });
});
mModelConnection->makeConnectToModel(&MagicSearchModel::sourceFlagsChanged, [this](int flags) {
mModelConnection->invokeToCore([this, flags]() { setSourceFlags(flags); });
});
mModelConnection->makeConnectToModel(
&MagicSearchModel::aggregationFlagChanged, [this](LinphoneEnums::MagicSearchAggregation flag) {
mModelConnection->invokeToCore([this, flag]() { setAggregationFlag(flag); });
&MagicSearchList::lSearch,
[this](QString filter, int sourceFlags, LinphoneEnums::MagicSearchAggregation aggregationFlag,
int maxResults) {
mModelConnection->invokeToModel([this, filter, sourceFlags, aggregationFlag, maxResults]() {
mMagicSearch->search(filter, sourceFlags, aggregationFlag, maxResults);
});
});
mModelConnection->makeConnectToModel(
&MagicSearchModel::searchResultsReceived,
@ -106,26 +88,30 @@ void MagicSearchList::setSelf(QSharedPointer<MagicSearchList> me) {
auto *contacts = new QList<QSharedPointer<FriendCore>>();
for (auto it : results) {
QSharedPointer<FriendCore> contact;
if (it->getFriend()) {
contact = FriendCore::create(it->getFriend());
auto linphoneFriend = it->getFriend();
// Considered LDAP results as stored.
bool isStored = (it->getSourceFlags() & (int)linphone::MagicSearch::Source::LdapServers) > 0;
if (linphoneFriend) {
contact = FriendCore::create(linphoneFriend);
contacts->append(contact);
} else if (auto address = it->getAddress()) {
auto linphoneFriend = CoreModel::getInstance()->getCore()->createFriend();
contact = FriendCore::create(linphoneFriend);
auto displayname = Utils::coreStringToAppString(address->getDisplayName());
auto splitted = displayname.split(" ");
if (splitted.size() > 0) {
linphoneFriend->setAddress(address);
contact = FriendCore::create(linphoneFriend, isStored);
auto displayName = Utils::coreStringToAppString(address->getDisplayName());
auto splitted = displayName.split(" ");
if (!displayName.isEmpty() && splitted.size() > 0) {
contact->setGivenName(splitted[0]);
splitted.removeFirst();
contact->setFamilyName(splitted.join(" "));
} else {
contact->setGivenName(Utils::coreStringToAppString(address->getUsername()));
}
contact->appendAddress(Utils::coreStringToAppString(address->asStringUriOnly()));
contacts->append(contact);
} else if (!it->getPhoneNumber().empty()) {
auto linphoneFriend = CoreModel::getInstance()->getCore()->createFriend();
contact = FriendCore::create(linphoneFriend);
linphoneFriend = CoreModel::getInstance()->getCore()->createFriend();
linphoneFriend->setAddress(address);
contact = FriendCore::create(linphoneFriend, isStored);
contact->setGivenName(Utils::coreStringToAppString(it->getPhoneNumber()));
contact->appendPhoneNumber(tr("Phone"), Utils::coreStringToAppString(it->getPhoneNumber()));
contacts->append(contact);
@ -148,10 +134,11 @@ void MagicSearchList::setResults(const QList<QSharedPointer<FriendCore>> &contac
if (!isFriendCore) continue;
disconnect(isFriendCore.get());
}
qDebug() << log().arg("SetResults: %1").arg(contacts.size());
resetData<FriendCore>(contacts);
for (auto it : contacts) {
connect(it.get(), &FriendCore::removed, this, qOverload<QObject *>(&MagicSearchList::remove));
connect(it.get(), &FriendCore::starredChanged, this, [this] { lSearch(mSearchFilter); });
connect(it.get(), &FriendCore::starredChanged, this, &MagicSearchList::friendStarredChanged);
}
}
@ -161,7 +148,7 @@ void MagicSearchList::addResult(const QSharedPointer<FriendCore> &contact) {
void MagicSearchList::setSearch(const QString &search) {
mSearchFilter = search;
if (!search.isEmpty()) {
lSearch(search);
emit lSearch(search, mSourceFlags, mAggregationFlag, mMaxResults);
} else {
beginResetModel();
mList.clear();
@ -180,6 +167,17 @@ void MagicSearchList::setSourceFlags(int flags) {
}
}
int MagicSearchList::getMaxResults() const {
return mMaxResults;
}
void MagicSearchList::setMaxResults(int maxResults) {
if (mMaxResults != maxResults) {
mMaxResults = maxResults;
emit maxResultsChanged(mMaxResults);
}
}
LinphoneEnums::MagicSearchAggregation MagicSearchList::getAggregationFlag() const {
return mAggregationFlag;
}
@ -191,11 +189,20 @@ void MagicSearchList::setAggregationFlag(LinphoneEnums::MagicSearchAggregation f
}
}
QHash<int, QByteArray> MagicSearchList::roleNames() const {
QHash<int, QByteArray> roles;
roles[Qt::DisplayRole] = "$modelData";
roles[Qt::DisplayRole + 1] = "isStored";
return roles;
}
QVariant MagicSearchList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();
if (role == Qt::DisplayRole) {
return QVariant::fromValue(new FriendGui(mList[row].objectCast<FriendCore>()));
} else if (role == Qt::DisplayRole + 1) {
return mList[row].objectCast<FriendCore>()->getIsStored();
}
return QVariant();
}

View file

@ -22,6 +22,7 @@
#define MAGIC_SEARCH_LIST_H_
#include "../proxy/ListProxy.hpp"
#include "core/friend/FriendGui.hpp"
#include "model/search/MagicSearchModel.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
@ -50,19 +51,24 @@ public:
LinphoneEnums::MagicSearchAggregation getAggregationFlag() const;
void setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag);
int getMaxResults() const;
void setMaxResults(int maxResults);
virtual QHash<int, QByteArray> roleNames() const override;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int findFriendIndexByAddress(const QString &address);
signals:
void lSearch(QString filter);
void lSetSourceFlags(int sourceFlags);
void lSetAggregationFlag(LinphoneEnums::MagicSearchAggregation aggregationFlag);
void
lSearch(QString filter, int sourceFlags, LinphoneEnums::MagicSearchAggregation aggregationFlag, int maxResults);
void sourceFlagsChanged(int sourceFlags);
void aggregationFlagChanged(LinphoneEnums::MagicSearchAggregation flag);
void maxResultsChanged(int maxResults);
void friendCreated(int index);
void friendCreated(int index, FriendGui *data);
void friendStarredChanged();
void initialized();
@ -70,6 +76,7 @@ private:
int mSourceFlags;
LinphoneEnums::MagicSearchAggregation mAggregationFlag;
QString mSearchFilter;
int mMaxResults = -1;
std::shared_ptr<MagicSearchModel> mMagicSearch;
QSharedPointer<SafeConnection<MagicSearchList, MagicSearchModel>> mModelConnection;

View file

@ -20,16 +20,14 @@
#include "MagicSearchProxy.hpp"
#include "MagicSearchList.hpp"
#include "core/App.hpp"
#include "core/friend/FriendGui.hpp"
#include "core/friend/FriendCore.hpp"
MagicSearchProxy::MagicSearchProxy(QObject *parent) : LimitProxy(parent) {
mSourceFlags = (int)LinphoneEnums::MagicSearchSource::Friends | (int)LinphoneEnums::MagicSearchSource::LdapServers;
mAggregationFlag = LinphoneEnums::MagicSearchAggregation::Friend;
setList(MagicSearchList::create());
sort(0);
connect(this, &MagicSearchProxy::forceUpdate, [this] {
if (mList) emit mList->lSearch(mSearchText);
if (mList) emit mList->lSearch(mSearchText, getSourceFlags(), getAggregationFlag(), getMaxResults());
});
connect(App::getInstance(), &App::currentDateChanged, this, &MagicSearchProxy::forceUpdate);
}
@ -50,28 +48,28 @@ void MagicSearchProxy::setList(QSharedPointer<MagicSearchList> newList) {
Qt::QueuedConnection);
connect(mList.get(), &MagicSearchList::aggregationFlagChanged, this, &MagicSearchProxy::aggregationFlagChanged,
Qt::QueuedConnection);
connect(
mList.get(), &MagicSearchList::friendCreated, this,
[this](int index) {
auto proxyIndex =
dynamic_cast<SortFilterList *>(sourceModel())->mapFromSource(mList->index(index, 0)).row();
// auto proxyIndex = mapFromSource(sourceModel()->index(index, 0)); // OLD (keep for checking new proxy
// behavior)
emit friendCreated(proxyIndex);
[this](int index, FriendGui *data) {
auto sortModel = dynamic_cast<SortFilterList *>(sourceModel());
sortModel->invalidate();
if (!data->mCore->isLdap()) {
auto proxyIndex = sortModel->mapFromSource(mList->index(index, 0)).row();
// auto proxyIndex = mapFromSource(sourceModel()->index(index, 0)); // OLD (keep for checking new
// proxy behavior)
emit localFriendCreated(proxyIndex);
}
},
Qt::QueuedConnection);
connect(
mList.get(), &MagicSearchList::initialized, this,
[this, newList = mList.get()] {
emit newList->lSetSourceFlags(mSourceFlags);
emit newList->lSetAggregationFlag(mAggregationFlag);
emit initialized();
},
mList.get(), &MagicSearchList::initialized, this, [this, newList = mList.get()] { emit initialized(); },
Qt::QueuedConnection);
}
auto sortFilterList = new SortFilterList(mList.get(), Qt::AscendingOrder);
if (oldModel) {
sortFilterList->mShowFavoritesOnly = oldModel->mShowFavoritesOnly;
sortFilterList->mHideSuggestions = oldModel->mHideSuggestions;
sortFilterList->mShowLdapContacts = oldModel->mShowLdapContacts;
sortFilterList->mHideListProxy = oldModel->mHideListProxy;
if (sortFilterList->mHideListProxy) {
@ -81,7 +79,14 @@ void MagicSearchProxy::setList(QSharedPointer<MagicSearchList> newList) {
[this, sortFilterList]() { sortFilterList->invalidate(); });
}
}
connect(
mList.get(), &MagicSearchList::friendStarredChanged, this,
[this, sortFilterList]() {
if (showFavoritesOnly()) sortFilterList->invalidate();
},
Qt::QueuedConnection);
setSourceModels(sortFilterList);
sort(0);
}
int MagicSearchProxy::findFriendIndexByAddress(const QString &address) {
@ -105,14 +110,19 @@ void MagicSearchProxy::setSearchText(const QString &search) {
}
int MagicSearchProxy::getSourceFlags() const {
return mSourceFlags;
return mList->getSourceFlags();
}
void MagicSearchProxy::setSourceFlags(int flags) {
if (flags != mSourceFlags) {
mSourceFlags = flags;
emit mList->lSetSourceFlags(flags);
}
mList->setSourceFlags(flags);
}
int MagicSearchProxy::getMaxResults() const {
return mList->getMaxResults();
}
void MagicSearchProxy::setMaxResults(int flags) {
mList->setMaxResults(flags);
}
bool MagicSearchProxy::showFavoritesOnly() const {
@ -128,9 +138,28 @@ void MagicSearchProxy::setShowFavoritesOnly(bool show) {
}
}
void MagicSearchProxy::setParentProxy(MagicSearchProxy *proxy) {
setList(proxy->mList);
emit parentProxyChanged();
bool MagicSearchProxy::getHideSuggestions() const {
return dynamic_cast<SortFilterList *>(sourceModel())->mHideSuggestions;
}
void MagicSearchProxy::setHideSuggestions(bool data) {
auto list = dynamic_cast<SortFilterList *>(sourceModel());
if (list->mHideSuggestions != data) {
list->mHideSuggestions = data;
list->invalidate();
emit hideSuggestionsChanged();
}
}
MagicSearchProxy *MagicSearchProxy::getParentProxy() const {
return mParentProxy;
}
void MagicSearchProxy::setParentProxy(MagicSearchProxy *parentProxy) {
if (parentProxy && parentProxy->mList) {
mParentProxy = parentProxy;
setList(parentProxy->mList);
emit parentProxyChanged();
}
}
MagicSearchProxy *MagicSearchProxy::getHideListProxy() const {
@ -138,36 +167,34 @@ MagicSearchProxy *MagicSearchProxy::getHideListProxy() const {
return list ? list->mHideListProxy : nullptr;
}
void MagicSearchProxy::setHideListProxy(MagicSearchProxy *proxy) {
void MagicSearchProxy::setHideListProxy(MagicSearchProxy *hideListProxy) {
auto list = dynamic_cast<SortFilterList *>(sourceModel());
if (list && list->mHideListProxy != proxy) {
if (list && list->mHideListProxy != hideListProxy) {
if (list->mHideListProxy) list->disconnect(list->mHideListProxy);
list->mHideListProxy = proxy;
list->mHideListProxy = hideListProxy;
list->invalidate();
if (proxy) {
connect(proxy, &MagicSearchProxy::countChanged, list, [this, list]() { list->invalidate(); });
connect(proxy, &MagicSearchProxy::modelReset, list, [this, list]() { list->invalidate(); });
if (hideListProxy) {
connect(hideListProxy, &MagicSearchProxy::countChanged, list, [this, list]() { list->invalidateFilter(); });
connect(hideListProxy, &MagicSearchProxy::modelReset, list, [this, list]() { list->invalidateFilter(); });
}
emit hideListProxyChanged();
}
}
LinphoneEnums::MagicSearchAggregation MagicSearchProxy::getAggregationFlag() const {
return mAggregationFlag;
return mList->getAggregationFlag();
}
void MagicSearchProxy::setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag) {
if (flag != mAggregationFlag) {
mAggregationFlag = flag;
emit mList->lSetAggregationFlag(flag);
}
mList->setAggregationFlag(flag);
}
bool MagicSearchProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
auto friendCore = getItemAtSource<MagicSearchList, FriendCore>(sourceRow);
auto toShow = false;
if (friendCore) {
toShow = (!mShowFavoritesOnly || friendCore->getStarred()) && (mShowLdapContacts || !friendCore->isLdap());
toShow = (!mHideSuggestions || friendCore->getIsStored()) &&
(!mShowFavoritesOnly || friendCore->getStarred()) && (mShowLdapContacts || !friendCore->isLdap());
if (toShow && mHideListProxy) {
for (auto &friendAddress : friendCore->getAllAddresses()) {
toShow = mHideListProxy->findFriendIndexByAddress(friendAddress.toMap()["address"].toString()) == -1;
@ -184,6 +211,10 @@ bool MagicSearchProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft, c
auto r = getItemAtSource<MagicSearchList, FriendCore>(sourceRight.row());
if (l && r) {
bool lIsStored = l->getIsStored();
bool rIsStored = r->getIsStored();
if (lIsStored && !rIsStored) return true;
else if (!lIsStored && rIsStored) return false;
auto lName = l->getDisplayName().toLower();
auto rName = r->getDisplayName().toLower();
return lName < rName;

View file

@ -22,6 +22,7 @@
#define MAGIC_SEARCH_PROXY_H_
#include "../proxy/LimitProxy.hpp"
#include "core/friend/FriendGui.hpp"
#include "core/search/MagicSearchList.hpp"
#include "tool/LinphoneEnums.hpp"
@ -32,18 +33,21 @@ class MagicSearchProxy : public LimitProxy {
Q_PROPERTY(QString searchText READ getSearchText WRITE setSearchText NOTIFY searchTextChanged)
Q_PROPERTY(int sourceFlags READ getSourceFlags WRITE setSourceFlags NOTIFY sourceFlagsChanged)
Q_PROPERTY(int maxResults READ getMaxResults WRITE setMaxResults NOTIFY maxResultsChanged)
Q_PROPERTY(LinphoneEnums::MagicSearchAggregation aggregationFlag READ getAggregationFlag WRITE setAggregationFlag
NOTIFY aggregationFlagChanged)
Q_PROPERTY(bool showFavoritesOnly READ showFavoritesOnly WRITE setShowFavoritesOnly NOTIFY showFavoriteOnlyChanged)
Q_PROPERTY(MagicSearchProxy *parentProxy WRITE setParentProxy NOTIFY parentProxyChanged)
Q_PROPERTY(MagicSearchProxy *parentProxy READ getParentProxy WRITE setParentProxy NOTIFY parentProxyChanged)
Q_PROPERTY(MagicSearchProxy *hideListProxy READ getHideListProxy WRITE setHideListProxy NOTIFY hideListProxyChanged)
Q_PROPERTY(bool showLdapContacts READ showLdapContacts WRITE setShowLdapContacts CONSTANT)
Q_PROPERTY(bool hideSuggestions READ getHideSuggestions WRITE setHideSuggestions NOTIFY hideSuggestionsChanged)
public:
DECLARE_SORTFILTER_CLASS(bool mShowFavoritesOnly = false; bool mShowLdapContacts = false;
DECLARE_SORTFILTER_CLASS(bool mShowFavoritesOnly = false; bool mShowLdapContacts = true;
bool mHideSuggestions = false;
MagicSearchProxy *mHideListProxy = nullptr;)
MagicSearchProxy(QObject *parent = Q_NULLPTR);
~MagicSearchProxy();
@ -56,12 +60,19 @@ public:
LinphoneEnums::MagicSearchAggregation getAggregationFlag() const;
void setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag);
int getMaxResults() const;
void setMaxResults(int maxResults);
bool showFavoritesOnly() const;
void setShowFavoritesOnly(bool show);
bool showLdapContacts() const;
void setShowLdapContacts(bool show);
bool getHideSuggestions() const;
void setHideSuggestions(bool data);
MagicSearchProxy *getParentProxy() const;
void setList(QSharedPointer<MagicSearchList> list);
Q_INVOKABLE void setParentProxy(MagicSearchProxy *proxy);
@ -75,17 +86,18 @@ signals:
void searchTextChanged();
void sourceFlagsChanged(int sourceFlags);
void aggregationFlagChanged(LinphoneEnums::MagicSearchAggregation aggregationFlag);
void maxResultsChanged(int maxResults);
void forceUpdate();
void friendCreated(int index);
void localFriendCreated(int index);
void showFavoriteOnlyChanged();
void hideSuggestionsChanged();
void parentProxyChanged();
void hideListProxyChanged();
void initialized();
protected:
MagicSearchProxy *mParentProxy = nullptr;
QString mSearchText;
int mSourceFlags;
LinphoneEnums::MagicSearchAggregation mAggregationFlag;
QSharedPointer<MagicSearchList> mList;
};

View file

@ -23,6 +23,7 @@
#include <QDebug>
#include "model/core/CoreModel.hpp"
#include "model/setting/SettingsModel.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/Utils.hpp"
@ -37,35 +38,43 @@ MagicSearchModel::~MagicSearchModel() {
mustBeInLinphoneThread("~" + getClassName());
}
void MagicSearchModel::search(QString filter) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
void MagicSearchModel::search(QString filter,
int sourceFlags,
LinphoneEnums::MagicSearchAggregation aggregation,
int maxResults) {
mLastSearch = filter;
mMonitor->getContactsListAsync(filter != "*" ? Utils::appStringToCoreString(filter) : "", "", mSourceFlags,
LinphoneEnums::toLinphone(mAggregationFlag));
}
void MagicSearchModel::setSourceFlags(int flags) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
if (mSourceFlags != flags) {
mSourceFlags = flags;
emit sourceFlagsChanged(mSourceFlags);
setMaxResults(maxResults);
if ((filter == "" || filter == "*") && ((sourceFlags & (int)LinphoneEnums::MagicSearchSource::LdapServers) > 0) &&
!SettingsModel::getInstance()->getSyncLdapContacts()) {
sourceFlags &= ~(int)LinphoneEnums::MagicSearchSource::LdapServers;
}
qInfo() << log().arg("Searching ") << filter << " from " << sourceFlags << " with limit " << maxResults;
mMonitor->getContactsListAsync(filter != "*" ? Utils::appStringToCoreString(filter) : "", "", sourceFlags,
LinphoneEnums::toLinphone(aggregation));
}
void MagicSearchModel::setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
if (mAggregationFlag != flag) {
mAggregationFlag = flag;
emit aggregationFlagChanged(mAggregationFlag);
int MagicSearchModel::getMaxResults() const {
if (!mMonitor->getLimitedSearch()) return -1;
else return mMonitor->getSearchLimit();
}
void MagicSearchModel::setMaxResults(int maxResults) {
if (maxResults <= 0 && mMonitor->getLimitedSearch() ||
maxResults > 0 && (!mMonitor->getLimitedSearch() || maxResults != mMonitor->getSearchLimit())) {
mMonitor->setLimitedSearch(maxResults > 0);
if (maxResults > 0) mMonitor->setSearchLimit(maxResults);
emit maxResultsChanged(maxResults);
}
}
void MagicSearchModel::onSearchResultsReceived(const std::shared_ptr<linphone::MagicSearch> &magicSearch) {
for (auto it : magicSearch->getLastSearch()) {
qDebug() << log().arg("SDK send callback: onSearchResultsReceived");
auto results = magicSearch->getLastSearch();
for (auto it : results) {
bool isLdap = (it->getSourceFlags() & (int)LinphoneEnums::MagicSearchSource::LdapServers) != 0;
if (isLdap && it->getFriend()) updateLdapFriendListWithFriend(it->getFriend());
}
emit searchResultsReceived(magicSearch->getLastSearch());
emit searchResultsReceived(results);
}
void MagicSearchModel::onLdapHaveMoreResults(const std::shared_ptr<linphone::MagicSearch> &magicSearch,

View file

@ -37,17 +37,14 @@ public:
MagicSearchModel(const std::shared_ptr<linphone::MagicSearch> &data, QObject *parent = nullptr);
~MagicSearchModel();
void search(QString filter);
void setSourceFlags(int flags);
void setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag);
void search(QString filter, int sourceFlags, LinphoneEnums::MagicSearchAggregation aggregation, int maxResults);
int mSourceFlags = (int)linphone::MagicSearch::Source::All;
LinphoneEnums::MagicSearchAggregation mAggregationFlag = LinphoneEnums::MagicSearchAggregation::None;
int getMaxResults() const;
void setMaxResults(int maxResults);
QString mLastSearch;
signals:
void sourceFlagsChanged(int sourceFlags);
void aggregationFlagChanged(LinphoneEnums::MagicSearchAggregation aggregationFlag);
void maxResultsChanged(int maxResults);
private:
DECLARE_ABSTRACT_OBJECT

View file

@ -47,6 +47,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Control/Display/Call/CallStatistics.qml
view/Control/Display/Contact/Avatar.qml
view/Control/Display/Contact/Contact.qml
view/Control/Display/Contact/ContactListItem.qml
view/Control/Display/Contact/ContactListView.qml
view/Control/Display/Contact/Voicemail.qml
view/Control/Display/Meeting/MeetingListView.qml

View file

@ -0,0 +1,264 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls.Basic as Control
import Linphone
import UtilsCpp 1.0
import ConstantsCpp 1.0
import SettingsCpp
FocusScope {
id: mainItem
implicitHeight: 56 * DefaultStyle.dp
property var searchResultItem
property bool showInitials: true // Display Initials of Display name.
property bool showDefaultAddress: true // Display address below display name.
property bool showActions: false // Display actions layout (call buttons)
property bool showContactMenu: true // Display the dot menu for contacts.
property string highlightText // Bold characters in Display name.
property bool displayNameCapitalization: true // Capitalize display name.
property bool selectionEnabled: true // Contact can be selected
property bool multiSelectionEnabled: false //Multiple items can be selected.
property list<string> selectedContacts // List of default address on selected contacts.
property int selectedContactCount: selectedContacts.length
property bool isSelected: false // selected in list => currentIndex == index
property var previousInitial // Use directly previous initial
property int itemsRightMargin: 39 * DefaultStyle.dp
property var displayName: searchResultItem.core.displayName
property string initial: displayName ? displayName[0].toLocaleLowerCase(ConstantsCpp.DefaultLocale) : ''
signal clicked(var mouse)
signal contactStarredChanged()
signal contactDeletionRequested(FriendGui contact)
Connections {
enabled: searchResultItem.core
target: searchResultItem.core
function onStarredChanged() { mainItem.contactStarredChanged()}
}
Text {
id: initial
anchors.left: parent.left
visible: mainItem.showInitials
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: 15 * DefaultStyle.dp
verticalAlignment: Text.AlignVCenter
width: 20 * DefaultStyle.dp
opacity: previousInitial != mainItem.initial ? 1 : 0
text: mainItem.initial
color: DefaultStyle.main2_400
font {
pixelSize: 20 * DefaultStyle.dp
weight: 500 * DefaultStyle.dp
capitalization: Font.AllUppercase
}
}
RowLayout {
id: contactDelegate
anchors.left: initial.visible ? initial.right : parent.left
anchors.right: parent.right
anchors.rightMargin: mainItem.itemsRightMargin
anchors.verticalCenter: parent.verticalCenter
spacing: 16 * DefaultStyle.dp
z: 1
Avatar {
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
Layout.leftMargin: 5 * DefaultStyle.dp
contact: searchResultItem
}
ColumnLayout {
spacing: 0
Text {
text: UtilsCpp.boldTextPart(mainItem.displayName, mainItem.highlightText)
font{
pixelSize: mainItem.showDefaultAddress ? 16 * DefaultStyle.dp : 14 * DefaultStyle.dp
capitalization: mainItem.displayNameCapitalization ? Font.Capitalize : Font.MixedCase
weight: mainItem.showDefaultAddress ? 800 * DefaultStyle.dp : 400 * DefaultStyle.dp
}
maximumLineCount: 1
Layout.fillWidth: true
}
Text {
Layout.topMargin: 2 * DefaultStyle.dp
Layout.fillWidth: true
visible: mainItem.showDefaultAddress
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(searchResultItem.core.defaultAddress) : searchResultItem.core.defaultAddress
maximumLineCount: 1
elide: Text.ElideRight
font {
weight: 300 * DefaultStyle.dp
pixelSize: 12 * DefaultStyle.dp
}
}
}
Item{Layout.fillWidth: true}
RowLayout {
id: actionsRow
z: 1
visible: actionButtons || friendPopup.visible || mainItem.multiSelectionEnabled
spacing: visible ? 16 * DefaultStyle.dp : 0
EffectImage {
id: isSelectedCheck
// visible: mainItem.multiSelectionEnabled && (mainItem.confInfoGui.core.getParticipantIndex(searchResultItem.core.defaultAddress) != -1)
visible: mainItem.multiSelectionEnabled && (mainItem.selectedContacts.indexOf(searchResultItem.core.defaultAddress) != -1)
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
imageSource: AppIcons.check
colorizationColor: DefaultStyle.main1_500_main
Connections {
target: mainItem
// onParticipantsChanged: isSelectedCheck.visible = mainItem.confInfoGui.core.getParticipantIndex(searchResultItem.core.defaultAddress) != -1
function onSelectedContactCountChanged(){
isSelectedCheck.visible = (mainItem.selectedContacts.indexOf(searchResultItem.core.defaultAddress) != -1)
}
}
}
RowLayout{
id: actionButtons
Layout.rightMargin: 10 * DefaultStyle.dp
visible: mainItem.showActions
spacing: visible ? 10 * DefaultStyle.dp : 0
Button {
id: callButton
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
icon.source: AppIcons.phone
focus: visible
contentImageColor: DefaultStyle.main2_500main
background: Rectangle {
anchors.fill: parent
radius: 40 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
onClicked: UtilsCpp.createCall(searchResultItem.core.defaultAddress)
KeyNavigation.right: chatButton
KeyNavigation.left: chatButton
}
Button {
id: chatButton
visible: actionButtons.visible && !SettingsCpp.disableChatFeature
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
icon.source: AppIcons.chatTeardropText
focus: visible && !callButton.visible
contentImageColor: DefaultStyle.main2_500main
background: Rectangle {
anchors.fill: parent
radius: 40 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
KeyNavigation.right: callButton
KeyNavigation.left: callButton
}
}
PopupButton {
id: friendPopup
z: 1
// Layout.rightMargin: 13 * DefaultStyle.dp
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: 8 * DefaultStyle.dp
popup.x: 0
popup.padding: 10 * DefaultStyle.dp
visible: mainItem.showContactMenu && (contactArea.containsMouse || hovered || popup.opened)
popup.contentItem: ColumnLayout {
Button {
visible: searchResultItem.core.isStored
text: searchResultItem.core.starred ? qsTr("Enlever des favoris") : qsTr("Mettre en favori")
icon.source: searchResultItem.core.starred ? AppIcons.heartFill : AppIcons.heart
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
spacing: 10 * DefaultStyle.dp
textSize: 14 * DefaultStyle.dp
textWeight: 400 * DefaultStyle.dp
textColor: DefaultStyle.main2_500main
contentImageColor: searchResultItem.core.starred ? DefaultStyle.danger_500main : DefaultStyle.main2_600
onClicked: {
searchResultItem.core.lSetStarred(!searchResultItem.core.starred)
friendPopup.close()
}
background: Item{}
}
Button {
text: qsTr("Partager")
icon.source: AppIcons.shareNetwork
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
spacing: 10 * DefaultStyle.dp
textSize: 14 * DefaultStyle.dp
textWeight: 400 * DefaultStyle.dp
textColor: DefaultStyle.main2_500main
onClicked: {
var vcard = searchResultItem.core.getVCard()
var username = searchResultItem.core.givenName + searchResultItem.core.familyName
var filepath = UtilsCpp.createVCardFile(username, vcard)
if (filepath == "") UtilsCpp.showInformationPopup(qsTr("Erreur"), qsTr("La création du fichier vcard a échoué"), false)
else mainWindow.showInformationPopup(qsTr("VCard créée"), qsTr("VCard du contact enregistrée dans %1").arg(filepath))
UtilsCpp.shareByEmail(qsTr("Partage de contact"), vcard, filepath)
}
background: Item{}
}
Button {
text: qsTr("Supprimer")
icon.source: AppIcons.trashCan
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
spacing: 10 * DefaultStyle.dp
textSize: 14 * DefaultStyle.dp
textWeight: 400 * DefaultStyle.dp
textColor: DefaultStyle.danger_500main
contentImageColor: DefaultStyle.danger_500main
visible: !searchResultItem.core.readOnly
onClicked: {
mainItem.contactDeletionRequested(searchResultItem)
friendPopup.close()
}
background: Item{}
}
}
}
}
}
MouseArea {
id: contactArea
enabled: mainItem.selectionEnabled
anchors.fill: contactDelegate
//height: mainItem.height
hoverEnabled: true
acceptedButtons: Qt.AllButtons
z: -1
focus: !actionButtons.visible
Rectangle {
anchors.fill: contactArea
radius: 8 * DefaultStyle.dp
opacity: 0.7
color: mainItem.isSelected ? DefaultStyle.main2_200 : DefaultStyle.main2_100
visible: contactArea.containsMouse || friendPopup.hovered || mainItem.isSelected || friendPopup.visible
}
Keys.onPressed: (event)=> {
if (event.key == Qt.Key_Space || event.key == Qt.Key_Enter || event.key == Qt.Key_Return) {
contactArea.clicked(undefined)
event.accepted = true;
}
}
onClicked: (mouse) => {
forceActiveFocus()
if (mouse && mouse.button == Qt.RightButton && mainItem.showContactMenu) {
friendPopup.open()
} else {
mainItem.clicked(mouse)
}
}
}
}

View file

@ -7,52 +7,56 @@ import UtilsCpp 1.0
import ConstantsCpp 1.0
import SettingsCpp
ListView {
id: mainItem
height: contentHeight
visible: contentHeight > 0
clip: true
currentIndex: -1
//keyNavigationWraps: true
// rightMargin: 5 * DefaultStyle.dp
property bool selectionEnabled: true
property bool hoverEnabled: true
// dots popup menu
property bool contactMenuVisible: true
// call, video call etc menu
property bool actionLayoutVisible: false
property bool initialHeadersVisible: true
property bool displayNameCapitalization: true
property bool showFavoritesOnly: false
property bool showDefaultAddress: false
property bool showLdapContacts: false
property bool showInitials: true // Display Initials of Display name.
property bool showDefaultAddress: true // Display address below display name.
property bool showActions: false // Display actions layout (call buttons)
property bool showContactMenu: true // Display the dot menu for contacts.
property bool showFavorites: true // Display the favorites in the header
property bool hideSuggestions: false // Hide not stored contacts (not suggestions)
property string highlightText // Bold characters in Display name.
property var sourceFlags: LinphoneEnums.MagicSearchSource.All
property bool displayNameCapitalization: true // Capitalize display name.
property bool selectionEnabled: true // Contact can be selected
property bool multiSelectionEnabled: false //Multiple items can be selected.
property list<string> selectedContacts // List of default address on selected contacts.
property FriendGui selectedContact//: model.getAt(currentIndex) || null
property bool searchOnInitialization: false
property var listProxy: MagicSearchProxy{}
property alias hideListProxy: magicSearchProxy.hideListProxy
property bool loading: false
property bool pauseSearch: false // true = don't search on text change
// Model properties
// set searchBarText without specifying a model to bold
// matching names
property string searchBarText
property string searchText: searchBarText.length === 0 ? "*" : searchBarText
property var aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend
property var sourceFlags: LinphoneEnums.MagicSearchSource.Friends | ((searchText.length > 0 && searchText != "*") || SettingsCpp.syncLdapContacts ? LinphoneEnums.MagicSearchSource.LdapServers : 0)
property string searchText// Binding is done on searchBarTextChanged
property ConferenceInfoGui confInfoGui
property bool multiSelectionEnabled: false
property list<string> selectedContacts
property int selectedContactCount: selectedContacts.length
property FriendGui selectedContact: model.getAt(currentIndex) || null
property bool haveFavorites: false
property int sectionsPixelSize: 16 * DefaultStyle.dp
property int sectionsWeight: 800 * DefaultStyle.dp
property int sectionsSpacing: 18 * DefaultStyle.dp
property int itemsRightMargin: 39 * DefaultStyle.dp
signal resultsReceived()
signal contactStarredChanged()
signal contactDeletionRequested(FriendGui contact)
signal contactAddedToSelection(string address)
signal contactRemovedFromSelection(string address)
signal contactClicked(FriendGui contact)
clip: true
highlightFollowsCurrentItem: true
cacheBuffer: 400
// Binding loop hack
onContentHeightChanged: Qt.callLater(function(){cacheBuffer = Math.max(0,contentHeight)})
function selectContact(address) {
var index = magicSearchProxy.findFriendIndexByAddress(address)
@ -89,9 +93,75 @@ ListView {
return index != -1
}
onCurrentIndexChanged: selectedContact = model.getAt(currentIndex) || null
onCountChanged: selectedContact = model.getAt(currentIndex) || null
onResultsReceived: {
loading = false
mainItem.positionViewAtBeginning()
}
onSearchBarTextChanged: {
loading = true
if(!pauseSearch) {
searchText = searchBarText.length === 0 ? "*" : searchBarText
}
}
onPauseSearchChanged: {
if(!pauseSearch){
searchText = searchBarText.length === 0 ? "*" : searchBarText
}
}
onAtYEndChanged: if(atYEnd) magicSearchProxy.displayMore()
keyNavigationEnabled: false
Keys.onPressed: (event)=> {
if(header.activeFocus) return;
if(event.key == Qt.Key_Up || event.key == Qt.Key_Down){
if (currentIndex == 0 && event.key == Qt.Key_Up) {
if( headerItem.list.count > 0) {
mainItem.highlightFollowsCurrentItem = false
currentIndex = -1
headerItem.list.currentIndex = headerItem.list.count -1
var item = headerItem.list.itemAtIndex(headerItem.list.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
headerItem.updatePosition()
event.accepted = true;
}else{
mainItem.currentIndex = mainItem.count - 1
var item = itemAtIndex(mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}
}else if(currentIndex >= mainItem.count -1 && event.key == Qt.Key_Down){
if( headerItem.list.count > 0) {
mainItem.highlightFollowsCurrentItem = false
mainItem.currentIndex = -1
headerItem.list.currentIndex = 0
var item = headerItem.list.itemAtIndex(headerItem.list.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
headerItem.updatePosition()
event.accepted = true;
}else{
mainItem.currentIndex = 0
var item = itemAtIndex(mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}
}else if(event.key == Qt.Key_Up){
mainItem.highlightFollowsCurrentItem = true
var item = itemAtIndex(--mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}else if(event.key == Qt.Key_Down){
mainItem.highlightFollowsCurrentItem = true
var item = itemAtIndex(++mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}
}
}
Component.onCompleted: {
if (confInfoGui) {
for(var i = 0; i < confInfoGui.core.participants.length; ++i) {
@ -99,33 +169,7 @@ ListView {
}
}
}
// strange behaviour with this lines
// When a popup opens after clicking on a contact, the selected contact
// changes because we lose focus on the list
// onActiveFocusChanged: if(activeFocus && (!footerItem || !footerItem.activeFocus)) {
// currentIndex = 0
// }
model: MagicSearchProxy {
id: magicSearchProxy
searchText: mainItem.searchText
// This property is needed instead of playing on the delegate visibility
// considering its starred status. Otherwise, the row in the list still
// exists even if its delegate is not visible, and creates navigation issues
showFavoritesOnly: mainItem.showFavoritesOnly
aggregationFlag: mainItem.aggregationFlag
parentProxy: mainItem.listProxy
showLdapContacts: mainItem.showLdapContacts
sourceFlags: mainItem.sourceFlags
onFriendCreated: (index) => {
mainItem.currentIndex = index
}
onInitialized: {
if(mainItem.searchOnInitialization) magicSearchProxy.forceUpdate()
}
}
Connections {
target: SettingsCpp
onLdapConfigChanged: {
@ -136,255 +180,302 @@ ListView {
Control.ScrollBar.vertical: ScrollBar {
id: scrollbar
rightPadding: 8 * DefaultStyle.dp
topPadding: mainItem.haveFavorites ? 24 * DefaultStyle.dp : 0 // Avoid to be on top of collapse button
active: true
interactive: true
// anchors.top: parent.top
// anchors.bottom: parent.bottom
// anchors.right: parent.right
policy: mainItem.contentHeight > mainItem.height ? Control.ScrollBar.AlwaysOn : Control.ScrollBar.AlwaysOff
}
Keys.onPressed: (event)=>{
if(event.key == Qt.Key_Tab && !mainItem.itemAtIndex(mainItem.currentIndex).activeFocus){
mainItem.itemAtIndex(mainItem.currentIndex).forceActiveFocus()
model: MagicSearchProxy {
id: magicSearchProxy
searchText: mainItem.searchText
aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend
sourceFlags: mainItem.sourceFlags
hideSuggestions: mainItem.hideSuggestions
initialDisplayItems: 20
onLocalFriendCreated: (index) => {
var item = itemAtIndex(index)
if(item){
mainItem.currentIndex = index
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
}
}
onInitialized: {
mainItem.loading = true
magicSearchProxy.forceUpdate()
}
onModelReset: mainItem.resultsReceived()
}
delegate: FocusScope {
id: itemDelegate
height: 56 * DefaultStyle.dp
width: mainItem.width
property var previousItem : mainItem.model.count > 0 && index > 0 ? mainItem.model.getAt(index-1) : null
property var previousDisplayName: previousItem ? previousItem.core.displayName : ""
property var displayName: modelData.core.displayName
Connections {
enabled: modelData.core
target: modelData.core
function onStarredChanged() { mainItem.contactStarredChanged()}
section.property: "isStored"
//section.criteria: ViewSection.FirstCharacter
section.delegate: Item{
width: mainItem.width
height: textItem.implicitHeight + sectionsSpacing * 2
required property bool section
Text {
id: textItem
anchors.fill: parent
text: section ? qsTr("Contacts") : qsTr("Suggestions")
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
font {
pixelSize: sectionsPixelSize
weight: sectionsWeight
}
}
}
Text {
id: initial
anchors.left: parent.left
visible: mainItem.initialHeadersVisible && mainItem.model.sourceFlags != LinphoneEnums.MagicSearchSource.All
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: 15 * DefaultStyle.dp
verticalAlignment: Text.AlignVCenter
width: 20 * DefaultStyle.dp
opacity: (!previousItem || !previousDisplayName.toLocaleLowerCase(ConstantsCpp.DefaultLocale).startsWith(displayName[0].toLocaleLowerCase(ConstantsCpp.DefaultLocale))) ? 1 : 0
text: displayName[0]
color: DefaultStyle.main2_400
font {
pixelSize: 20 * DefaultStyle.dp
weight: 500 * DefaultStyle.dp
capitalization: Font.AllUppercase
}
}
RowLayout {
id: contactDelegate
anchors.left: initial.visible ? initial.right : parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
spacing: 16 * DefaultStyle.dp
z: 1
Avatar {
Layout.leftMargin: 5 * DefaultStyle.dp
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
contact: modelData
}
ColumnLayout {
spacing: 0
Text {
text: UtilsCpp.boldTextPart(itemDelegate.displayName, mainItem.searchBarText)
font{
pixelSize: mainItem.showDefaultAddress ? 16 * DefaultStyle.dp : 14 * DefaultStyle.dp
capitalization: mainItem.displayNameCapitalization ? Font.Capitalize : Font.MixedCase
weight: mainItem.showDefaultAddress ? 800 * DefaultStyle.dp : 400 * DefaultStyle.dp
}
maximumLineCount: 1
Layout.fillWidth: true
}
Text {
maximumLineCount: 1
visible: mainItem.showDefaultAddress
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(modelData.core.defaultAddress) : modelData.core.defaultAddress
Layout.fillWidth: true
Layout.topMargin: 2 * DefaultStyle.dp
font {
weight: 300 * DefaultStyle.dp
pixelSize: 12 * DefaultStyle.dp
header: FocusScope{
id: headerItem
width: mainItem.width
height: favoritesContents.implicitHeight
property alias list: favoriteList
// Hack because changing currentindex change focus.
Timer{
id: focusDelay
interval: 10
onTriggered: {
mainItem.highlightFollowsCurrentItem = !headerItem.activeFocus
}
}
}
Item{Layout.fillWidth: true}
RowLayout {
id: actionsRow
z: 1
visible: actionButtons || friendPopup.visible || mainItem.multiSelectionEnabled
spacing: visible ? 16 * DefaultStyle.dp : 0
EffectImage {
id: isSelectedCheck
// visible: mainItem.multiSelectionEnabled && (mainItem.confInfoGui.core.getParticipantIndex(modelData.core.defaultAddress) != -1)
visible: mainItem.multiSelectionEnabled && (mainItem.selectedContacts.indexOf(modelData.core.defaultAddress) != -1)
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
imageSource: AppIcons.check
colorizationColor: DefaultStyle.main1_500_main
Connections {
target: mainItem
// onParticipantsChanged: isSelectedCheck.visible = mainItem.confInfoGui.core.getParticipantIndex(modelData.core.defaultAddress) != -1
function onSelectedContactCountChanged(){ isSelectedCheck.visible = (mainItem.selectedContacts.indexOf(modelData.core.defaultAddress) != -1)}
onActiveFocusChanged:focusDelay.restart()
//---------------------------------------------------
function updatePosition(){
var item = favoriteList.itemAtIndex(favoriteList.currentIndex)
if( item){
// For debugging just in case
//var listPosition = item.mapToItem(favoriteList, item.x, item.y)
//var newPosition = favoriteList.mapToItem(mainItem, listPosition.x, listPosition.y)
//console.log("item pos: " +item.x + " / " +item.y)
//console.log("fav pos: " +favoriteList.x + " / " +favoriteList.y)
//console.log("fav content: " +favoriteList.contentX + " / " +favoriteList.contentY)
//console.log("main pos: " +mainItem.x + " / " +mainItem.y)
//console.log("main content: " +mainItem.contentX + " / " +mainItem.contentY)
//console.log("list pos: " +listPosition.x + " / " +listPosition.y)
//console.log("new pos: " +newPosition.x + " / " +newPosition.y)
//console.log("header pos: " +headerItem.x + " / " +headerItem.y)
//console.log("Moving to " + (headerItem.y+item.y))
mainItem.contentY = headerItem.y+item.y
}
}
RowLayout{
id: actionButtons
visible: mainItem.actionLayoutVisible
spacing: visible ? 10 * DefaultStyle.dp : 0
Button {
id: callButton
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
icon.source: AppIcons.phone
contentImageColor: DefaultStyle.main2_600
focus: visible
background: Rectangle {
anchors.fill: parent
radius: 40 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
onClicked: UtilsCpp.createCall(modelData.core.defaultAddress)
KeyNavigation.right: chatButton
KeyNavigation.left: chatButton
}
Button {
id: chatButton
visible: actionButtons.visible && !SettingsCpp.disableChatFeature
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
icon.source: AppIcons.chatTeardropText
focus: visible && !callButton.visible
contentImageColor: DefaultStyle.main2_500main
background: Rectangle {
anchors.fill: parent
radius: 40 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
KeyNavigation.right: callButton
KeyNavigation.left: callButton
}
}
PopupButton {
id: friendPopup
z: 1
// Layout.rightMargin: 13 * DefaultStyle.dp
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: 8 * DefaultStyle.dp
popup.x: 0
popup.padding: 10 * DefaultStyle.dp
hoverEnabled: mainItem.hoverEnabled
visible: mainItem.contactMenuVisible && (contactArea.containsMouse || hovered || popup.opened) && (!mainItem.delegateButtons || mainItem.delegateButtons.length === 0)
popup.contentItem: ColumnLayout {
Button {
text: $modelData.core.starred ? qsTr("Enlever des favoris") : qsTr("Mettre en favori")
background: Item{}
icon.source: modelData.core.starred ? AppIcons.heartFill : AppIcons.heart
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
spacing: 10 * DefaultStyle.dp
textSize: 14 * DefaultStyle.dp
textWeight: 400 * DefaultStyle.dp
textColor: DefaultStyle.main2_500main
contentImageColor: modelData.core.starred ? DefaultStyle.danger_500main : DefaultStyle.main2_600
onClicked: {
modelData.core.lSetStarred(!modelData.core.starred)
friendPopup.close()
}
ColumnLayout {
id: favoritesContents
width: parent.width
spacing: mainItem.haveFavorites ? sectionsSpacing : 0
BusyIndicator {
Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: visible ? 60 * DefaultStyle.dp : 0
Layout.preferredWidth: 60 * DefaultStyle.dp
indicatorHeight: 60 * DefaultStyle.dp
indicatorWidth: 60 * DefaultStyle.dp
visible: mainItem.loading
indicatorColor: DefaultStyle.main1_500_main
}
Item{// Do not use directly RowLayout : there is an issue where the layout doesn't update on visible
Layout.fillWidth: true
Layout.preferredHeight: mainItem.haveFavorites ? favoriteTitle.implicitHeight : 0
RowLayout {
id: favoriteTitle
anchors.fill: parent
spacing: 0
// Need this because it can stay at 0 on display without manual relayouting (moving position, resize)
visible: mainItem.haveFavorites
onVisibleChanged: if(visible) {
Qt.callLater(mainItem.positionViewAtBeginning)// If not later, the view will not move to favoris at startup
}
Text {
//Layout.fillHeight: true
text: qsTr("Favoris")
font {
pixelSize: sectionsPixelSize
weight: sectionsWeight
}
}
Item {
Layout.fillWidth: true
}
Button {
id: favoriteExpandButton
background: Item{}
icon.source: favoriteList.visible ? AppIcons.upArrow : AppIcons.downArrow
Layout.fillHeight: true
Layout.preferredWidth: height
//Layout.preferredWidth: 24 * DefaultStyle.dp
//Layout.preferredHeight: 24 * DefaultStyle.dp
Layout.rightMargin: 23 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
focus: true
onClicked: favoriteList.visible = !favoriteList.visible
KeyNavigation.down: favoriteList
}
}
Button {
text: qsTr("Partager")
background: Item{}
icon.source: AppIcons.shareNetwork
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
spacing: 10 * DefaultStyle.dp
textSize: 14 * DefaultStyle.dp
textWeight: 400 * DefaultStyle.dp
textColor: DefaultStyle.main2_500main
onClicked: {
var vcard = modelData.core.getVCard()
var username = modelData.core.givenName + modelData.core.familyName
var filepath = UtilsCpp.createVCardFile(username, vcard)
if (filepath == "") UtilsCpp.showInformationPopup(qsTr("Erreur"), qsTr("La création du fichier vcard a échoué"), false)
else mainWindow.showInformationPopup(qsTr("VCard créée"), qsTr("VCard du contact enregistrée dans %1").arg(filepath))
UtilsCpp.shareByEmail(qsTr("Partage de contact"), vcard, filepath)
}
ListView{
id: favoriteList
Layout.fillWidth: true
Layout.preferredHeight: count > 0 ? contentHeight : 0// Show full and avoid scrolling
onCountChanged: mainItem.haveFavorites = count > 0
Keys.onPressed: (event)=> {
if(event.key == Qt.Key_Up || event.key == Qt.Key_Down) {
if (favoriteList.currentIndex == 0 && event.key == Qt.Key_Up) {
if( mainItem.count > 0) {
mainItem.highlightFollowsCurrentItem = true
favoriteList.currentIndex = -1
mainItem.currentIndex = mainItem.count-1
var item = mainItem.itemAtIndex(mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}else{
favoriteList.currentIndex = favoriteList.count - 1
var item = itemAtIndex(favoriteList.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}
}else if(currentIndex >= favoriteList.count -1 && event.key == Qt.Key_Down) {
if( mainItem.count > 0) {
mainItem.highlightFollowsCurrentItem = true
favoriteList.currentIndex = -1
mainItem.currentIndex = 0
var item = mainItem.itemAtIndex(mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}else{
favoriteList.currentIndex = 0
var item = itemAtIndex(favoriteList.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}
}else if(event.key == Qt.Key_Up){
mainItem.highlightFollowsCurrentItem = false
var item = itemAtIndex(--favoriteList.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
headerItem.updatePosition()
event.accepted = true;
}else if(event.key == Qt.Key_Down){
mainItem.highlightFollowsCurrentItem = false
var item = itemAtIndex(++favoriteList.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
headerItem.updatePosition()
event.accepted = true;
}
}
}
Button {
text: qsTr("Supprimer")
background: Item{}
icon.source: AppIcons.trashCan
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
spacing: 10 * DefaultStyle.dp
textSize: 14 * DefaultStyle.dp
textWeight: 400 * DefaultStyle.dp
textColor: DefaultStyle.danger_500main
contentImageColor: DefaultStyle.danger_500main
visible: !modelData.core.readOnly
onClicked: {
mainItem.contactDeletionRequested(modelData)
friendPopup.close()
property MagicSearchProxy proxy: MagicSearchProxy{
parentProxy: mainItem.model
showFavoritesOnly: true
hideSuggestions: mainItem.hideSuggestions
}
model : showFavorites && mainItem.searchBarText == '' ? proxy : []
delegate: ContactListItem{
width: favoriteList.width
focus: true
searchResultItem: $modelData
showInitials: mainItem.showInitials
showDefaultAddress: mainItem.showDefaultAddress
showActions: mainItem.showActions
showContactMenu: mainItem.showContactMenu
highlightText: mainItem.highlightText
displayNameCapitalization: mainItem.displayNameCapitalization
itemsRightMargin: mainItem.itemsRightMargin
selectionEnabled: mainItem.selectionEnabled
multiSelectionEnabled: mainItem.multiSelectionEnabled
selectedContacts: mainItem.selectedContacts
isSelected: mainItem.selectedContact && mainItem.selectedContact.core == searchResultItem.core
previousInitial: ''//favoriteList.count > 0 ? favoriteList.itemAtIndex(index-1)?.initial : '' // Binding on count
initial: '' // Hide initials but keep space
onIsSelectedChanged: if(isSelected) favoriteList.currentIndex = index
onContactStarredChanged: mainItem.contactStarredChanged()
onContactDeletionRequested: (contact) => mainItem.contactDeletionRequested(contact)
onClicked: (mouse) => {
mainItem.highlightFollowsCurrentItem = false
favoriteList.currentIndex = index
mainItem.selectedContact = searchResultItem
forceActiveFocus()
headerItem.updatePosition()
if (mainItem.multiSelectionEnabled) {
var indexInSelection = mainItem.selectedContacts.indexOf(searchResultItem.core.defaultAddress)
if (indexInSelection == -1) {
mainItem.addContactToSelection(searchResultItem.core.defaultAddress)
}
else {
mainItem.removeContactFromSelection(indexInSelection, 1)
}
}
mainItem.contactClicked(searchResultItem)
}
}
}
}
}
}
delegate: ContactListItem{
id: contactItem
width: mainItem.width
focus: true
MouseArea {
id: contactArea
enabled: mainItem.selectionEnabled
hoverEnabled: mainItem.hoverEnabled
anchors.fill: contactDelegate
height: mainItem.height
acceptedButtons: Qt.AllButtons
z: -1
focus: !actionButtons.visible
Rectangle {
anchors.fill: contactArea
opacity: 0.7
radius: 8 * DefaultStyle.dp
color: mainItem.currentIndex === index ? DefaultStyle.main2_200 : DefaultStyle.main2_100
visible: contactArea.containsMouse || friendPopup.hovered || mainItem.currentIndex === index
}
Keys.onPressed: (event)=> {
if (event.key == Qt.Key_Space || event.key == Qt.Key_Enter || event.key == Qt.Key_Return) {
contactArea.clicked(undefined)
event.accepted = true;
}
}
onClicked: (mouse) => {
if (mouse && mouse.button == Qt.RightButton) {
friendPopup.open()
} else {
mainItem.forceActiveFocus()
mainItem.currentIndex = index
if (mainItem.multiSelectionEnabled) {
var indexInSelection = mainItem.selectedContacts.indexOf(modelData.core.defaultAddress)
if (indexInSelection == -1) {
mainItem.addContactToSelection(modelData.core.defaultAddress)
}
else {
mainItem.removeContactFromSelection(indexInSelection, 1)
}
}
mainItem.contactClicked(modelData)
}
}
searchResultItem: $modelData
showInitials: mainItem.showInitials && searchResultItem.core.isStored
showDefaultAddress: mainItem.showDefaultAddress
showActions: mainItem.showActions
showContactMenu: searchResultItem.core.isStored
highlightText: mainItem.highlightText
displayNameCapitalization: mainItem.displayNameCapitalization
itemsRightMargin: mainItem.itemsRightMargin
selectionEnabled: mainItem.selectionEnabled
multiSelectionEnabled: mainItem.multiSelectionEnabled
selectedContacts: mainItem.selectedContacts
isSelected: mainItem.selectedContact && mainItem.selectedContact.core == searchResultItem.core
previousInitial: mainItem.itemAtIndex(index-1)?.initial
onIsSelectedChanged: if(isSelected) mainItem.currentIndex = index
onContactStarredChanged: mainItem.contactStarredChanged()
onContactDeletionRequested: (contact) => mainItem.contactDeletionRequested(contact)
onClicked: (mouse) => {
mainItem.highlightFollowsCurrentItem = true
if (mouse && mouse.button == Qt.RightButton) {
friendPopup.open()
} else {
forceActiveFocus()
if(mainItem.selectedContact && mainItem.selectedContact.core != contactItem.searchResultItem.core)
headerItem.list.currentIndex = -1
mainItem.selectedContact = contactItem.searchResultItem
if (mainItem.multiSelectionEnabled) {
var indexInSelection = mainItem.selectedContacts.indexOf(searchResultItem.core.defaultAddress)
if (indexInSelection == -1) {
mainItem.addContactToSelection(searchResultItem.core.defaultAddress)
}
else {
mainItem.removeContactFromSelection(indexInSelection, 1)
}
}
mainItem.contactClicked(searchResultItem)
}
}
}
}

View file

@ -11,13 +11,14 @@ FocusScope {
property int textInputWidth: 350 * DefaultStyle.dp
property color borderColor: "transparent"
property color focusedBorderColor: DefaultStyle.main2_500main
property string text: textField.text
property string text: textField.searchText
property bool magnifierVisible: true
property var validator: RegularExpressionValidator{}
property Control.Popup numericPadPopup
property alias numericPadButton: dialerButton
readonly property bool hasActiveFocus: textField.activeFocus
property alias color: backgroundItem.color
property bool delaySearch: true // Wait some idle time after typing to start searching
signal openNumericPadRequested()// Useful for redirection before displaying numeric pad.
@ -62,6 +63,9 @@ FocusScope {
anchors.leftMargin: magnifier.visible ? 0 : 10 * DefaultStyle.dp
anchors.right: clearTextButton.left
anchors.verticalCenter: parent.verticalCenter
property string searchText
focus: true
placeholderText: mainItem.placeholderText
placeholderTextColor: mainItem.placeholderTextColor
@ -75,6 +79,7 @@ FocusScope {
color: DefaultStyle.main2_600
selectByMouse: true
validator: mainItem.validator
onTextChanged: mainItem.delaySearch ? delayTimer.restart() : searchText = text
background: Item {
opacity: 0.
}
@ -83,6 +88,12 @@ FocusScope {
color: DefaultStyle.main2_500main
width: 1 * DefaultStyle.dp
}
Timer{
id: delayTimer
interval: 300
repeat: false
onTriggered: textField.searchText = textField.text
}
}
Button {
id: dialerButton

View file

@ -20,7 +20,6 @@ FocusScope {
signal transferCallToAnotherRequested(CallGui dest)
signal contactClicked(FriendGui contact)
clip: true
onVisibleChanged: if (numPadPopup.opened) numPadPopup.close()
ColumnLayout {
anchors.fill: parent
@ -71,7 +70,7 @@ FocusScope {
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.rightMargin: 39 * DefaultStyle.dp
Layout.maximumWidth: mainItem.width
//Layout.maximumWidth: mainItem.width
focus: true
color: mainItem.searchBarColor
borderColor: mainItem.searchBarBorderColor
@ -79,127 +78,72 @@ FocusScope {
numericPadPopup: mainItem.numPadPopup
KeyNavigation.down: grouCallButton
}
Flickable {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: 25 * DefaultStyle.dp
contentWidth: width
contentHeight: content.height
clip: true
Control.ScrollBar.vertical: ScrollBar {
active: true
interactive: true
policy: Control.ScrollBar.AsNeeded
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.rightMargin: 8 * DefaultStyle.dp
ColumnLayout {
id: content
spacing: 32 * DefaultStyle.dp
Button {
id: grouCallButton
visible: mainItem.groupCallVisible && !SettingsCpp.disableMeetingsFeature
Layout.preferredWidth: 320 * DefaultStyle.dp
Layout.preferredHeight: 44 * DefaultStyle.dp
padding: 0
KeyNavigation.up: searchBar
KeyNavigation.down: contactLoader.item
onClicked: mainItem.groupCallCreationRequested()
background: Rectangle {
anchors.fill: parent
radius: 50 * DefaultStyle.dp
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: DefaultStyle.main2_100}
GradientStop { position: 1.0; color: DefaultStyle.grey_0}
}
}
contentItem: RowLayout {
spacing: 16 * DefaultStyle.dp
anchors.verticalCenter: parent.verticalCenter
Image {
source: AppIcons.groupCall
Layout.preferredWidth: 44 * DefaultStyle.dp
sourceSize.width: 44 * DefaultStyle.dp
fillMode: Image.PreserveAspectFit
}
Text {
text: "Appel de groupe"
color: DefaultStyle.grey_1000
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
underline: grouCallButton.shadowEnabled
}
}
Item {
Layout.fillWidth: true
}
Image {
source: AppIcons.rightArrow
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
}
}
}
ColumnLayout {
id: content
spacing: 32 * DefaultStyle.dp
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 39 * DefaultStyle.dp
Button {
id: grouCallButton
visible: mainItem.groupCallVisible && !SettingsCpp.disableMeetingsFeature
Layout.preferredWidth: 320 * DefaultStyle.dp
Layout.preferredHeight: 44 * DefaultStyle.dp
padding: 0
KeyNavigation.up: searchBar
KeyNavigation.down: contactList.count >0 ? contactList : searchList
onClicked: mainItem.groupCallCreationRequested()
background: Rectangle {
anchors.fill: parent
radius: 50 * DefaultStyle.dp
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: DefaultStyle.main2_100}
GradientStop { position: 1.0; color: DefaultStyle.grey_0}
}
}
contentItem: RowLayout {
spacing: 16 * DefaultStyle.dp
anchors.verticalCenter: parent.verticalCenter
Image {
source: AppIcons.groupCall
Layout.preferredWidth: 44 * DefaultStyle.dp
sourceSize.width: 44 * DefaultStyle.dp
fillMode: Image.PreserveAspectFit
}
Text {
text: "Appel de groupe"
color: DefaultStyle.grey_1000
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
underline: grouCallButton.shadowEnabled
}
}
Item {
Layout.fillWidth: true
}
Image {
source: AppIcons.rightArrow
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
}
}
Loader{
// This is a hack for an incomprehensible behavior on sections title where they doesn't match with their delegate and can be unordered after resetting models.
id: contactLoader
Layout.fillWidth: true
Layout.fillHeight: true
property string t: searchBar.text
onTChanged: {
contactLoader.active = false
Qt.callLater(function(){contactLoader.active=true})
}
ColumnLayout {
spacing: 18 * DefaultStyle.dp
visible: contactList.contentHeight > 0
Text {
text: qsTr("Contacts")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
//-------------------------------------------------------------
sourceComponent: ContactListView{
id: contactList
searchBarText: searchBar.text
onContactClicked: (contact) => {
mainItem.contactClicked(contact)
}
ContactListView{
id: contactList
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
Control.ScrollBar.vertical.visible: false
contactMenuVisible: false
searchOnInitialization: true
searchBarText: searchBar.text
onContactClicked: (contact) => {
mainItem.contactClicked(contact)
}
}
}
ColumnLayout {
spacing: 18 * DefaultStyle.dp
visible: searchList.count > 0
Text {
text: qsTr("Suggestions")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
ContactListView{
id: searchList
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: contentHeight
contactMenuVisible: false
Control.ScrollBar.vertical.visible: false
initialHeadersVisible: false
displayNameCapitalization: false
searchBarText: searchBar.text
sourceFlags: LinphoneEnums.MagicSearchSource.All
hideListProxy: contactList.model
onContactClicked: (contact) => {
mainItem.contactClicked(contact)
}
}
}
Item {
Layout.fillHeight: true
}
}
}

View file

@ -10,7 +10,7 @@ FocusScope{
id: mainItem
property string placeHolderText: qsTr("Rechercher des contacts")
property list<string> selectedParticipants: suggestionList.selectedContacts
property list<string> selectedParticipants//: contactLoader.item ? contactLoader.item.selectedContacts
property int selectedParticipantsCount: selectedParticipants.length
property ConferenceInfoGui conferenceInfoGui
property color searchBarColor: DefaultStyle.grey_100
@ -30,7 +30,7 @@ FocusScope{
Layout.preferredHeight: contentHeight
Layout.maximumHeight: mainItem.height / 3
width: mainItem.width
model: suggestionList.selectedContacts
model: mainItem.selectedParticipants
clip: true
focus: participantList.count > 0
Keys.onPressed: (event) => {
@ -40,7 +40,7 @@ FocusScope{
}
delegate: FocusScope {
height: 56 * DefaultStyle.dp
width: participantList.width - scrollbar.implicitWidth - 12 * DefaultStyle.dp
width: participantList.width - scrollbar.implicitWidth - 28 * DefaultStyle.dp
RowLayout {
anchors.fill: parent
spacing: 10 * DefaultStyle.dp
@ -67,7 +67,7 @@ FocusScope{
icon.height: 24 * DefaultStyle.dp
focus: true
contentImageColor: DefaultStyle.main1_500_main
onClicked: suggestionList.removeSelectedContactByAddress(modelData)
onClicked: if(contactLoader.item) contactLoader.item.removeSelectedContactByAddress(modelData)
}
}
}
@ -83,7 +83,7 @@ FocusScope{
}
}
SearchBar {
id: searchbar
id: searchBar
Layout.fillWidth: true
Layout.topMargin: 6 * DefaultStyle.dp
Layout.rightMargin: 28 * DefaultStyle.dp
@ -95,86 +95,49 @@ FocusScope{
KeyNavigation.up: participantList.count > 0
? participantList
: nextItemInFocusChain(false)
KeyNavigation.down: contactList
KeyNavigation.down: contactLoader.item
}
Flickable {
Layout.fillWidth: true
Layout.fillHeight: true
contentWidth: width
contentHeight: content.height
clip: true
Control.ScrollBar.vertical: ScrollBar {
id: contactsScrollBar
active: true
interactive: true
policy: Control.ScrollBar.AsNeeded
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.rightMargin: 8 * DefaultStyle.dp
ColumnLayout {
id: content
spacing: 15 * DefaultStyle.dp
Text {
visible: !contactLoader.item?.loading && contactLoader.item?.count === 0
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 137 * DefaultStyle.dp
text: qsTr("Aucun contact%1").arg(searchBar.text.length !== 0 ? " correspondant" : "")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
ColumnLayout {
id: content
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: contactsScrollBar.implicitWidth + 12 * DefaultStyle.dp
Text {
Layout.topMargin: 6 * DefaultStyle.dp
text: qsTr("Contacts")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
Loader{
// This is a hack for an incomprehensible behavior on sections title where they doesn't match with their delegate and can be unordered after resetting models.
id: contactLoader
Layout.fillWidth: true
Layout.fillHeight: true
property string t: searchBar.text
onTChanged: {
contactLoader.active = false
Qt.callLater(function(){contactLoader.active=true})
}
ContactListView {
//-------------------------------------------------------------
sourceComponent: ContactListView{
id: contactList
visible: contentHeight > 0 || searchbar.text.length > 0
Layout.fillWidth: true
// Layout.fillHeight: true
Layout.topMargin: 8 * DefaultStyle.dp
Layout.preferredHeight: contentHeight
multiSelectionEnabled: true
contactMenuVisible: false
confInfoGui: mainItem.conferenceInfoGui
searchBarText: searchbar.text
searchOnInitialization: true
onContactAddedToSelection: (address) => {
suggestionList.addContactToSelection(address)
}
onContactRemovedFromSelection: (address) => suggestionList.removeSelectedContactByAddress(address)
Control.ScrollBar.vertical.visible: false
}
Text {
Layout.topMargin: 6 * DefaultStyle.dp
text: qsTr("Suggestions")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
ContactListView {
id: suggestionList
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: contentHeight
Control.ScrollBar.vertical.visible: false
contactMenuVisible: false
searchBarText: searchbar.text
sourceFlags: LinphoneEnums.MagicSearchSource.All
itemsRightMargin: 28 * DefaultStyle.dp
multiSelectionEnabled: true
displayNameCapitalization: false
hideListProxy: contactList.model
showContactMenu: false
confInfoGui: mainItem.conferenceInfoGui
selectedContacts: mainItem.selectedParticipants
onSelectedContactsChanged: Qt.callLater(function(){mainItem.selectedParticipants = selectedContacts})
searchBarText: searchBar.text
onContactAddedToSelection: (address) => {
contactList.addContactToSelection(address)
participantList.positionViewAtEnd()
}
onContactRemovedFromSelection: (address) => contactList.removeSelectedContactByAddress(address)
}
}
}
// Item {
// Layout.fillHeight: true
// }
}
}

View file

@ -171,8 +171,8 @@ Item {
if (text.length != 0) listPopup.open()
else listPopup.close()
}
KeyNavigation.down: contactList.count > 0 ? contactList : contactList.footerItem
KeyNavigation.up: contactList.footerItem
KeyNavigation.down: contactLoader.item?.count > 0 || !contactLoader.item?.footerItem? contactLoader.item : contactLoader.item?.footerItem
KeyNavigation.up: contactLoader.item?.footerItem ? contactLoader.item?.footerItem : contactLoader.item
component MagicSearchButton: Button {
id: button
@ -196,13 +196,13 @@ Item {
id: listPopup
width: magicSearchBar.width
property int maxHeight: 400 * DefaultStyle.dp
property bool displayScrollbar: contactList.contentHeight + topPadding + bottomPadding> maxHeight
height: Math.min(contactList.contentHeight + topPadding + bottomPadding, maxHeight)
property bool displayScrollbar: contactLoader.item?.contentHeight + topPadding + bottomPadding> maxHeight
height: Math.min(contactLoader.item?.contentHeight + topPadding + bottomPadding, maxHeight)
y: magicSearchBar.height
// closePolicy: Popup.NoAutoClose
topPadding: 20 * DefaultStyle.dp
bottomPadding: 20 * DefaultStyle.dp
rightPadding: 20 * DefaultStyle.dp
rightPadding: 10 * DefaultStyle.dp
leftPadding: 20 * DefaultStyle.dp
background: Item {
@ -213,7 +213,7 @@ Item {
color: DefaultStyle.grey_0
anchors.fill: parent
border.color: DefaultStyle.main1_500_main
border.width: contactList.activeFocus ? 2 : 0
border.width: contactLoader.item?.activeFocus ? 2 : 0
}
MultiEffect {
@ -237,125 +237,138 @@ Item {
}
}
contentItem: ContactListView {
id: contactList
visible: magicSearchBar.text.length != 0
Layout.preferredHeight: contentHeight
contentItem: Loader{
// This is a hack for an incomprehensible behavior on sections title where they doesn't match with their delegate and can be unordered after resetting models.
id: contactLoader
Layout.fillWidth: true
Layout.rightMargin: 5 * DefaultStyle.dp
initialHeadersVisible: false
contactMenuVisible: false
actionLayoutVisible: true
selectionEnabled: false
showDefaultAddress: true
showLdapContacts: true
Control.ScrollBar.vertical: scrollbar
searchText: magicSearchBar.text
Keys.onPressed: (event) => {
if(event.key == Qt.Key_Down){
if(contactList.currentIndex == contactList.count -1) {
contactList.currentIndex = -1
contactList.footerItem.forceActiveFocus()
event.accepted = true
}
} else if(event.key == Qt.Key_Up){
if(contactList.currentIndex <= 0) {
contactList.currentIndex = -1
contactList.footerItem.forceActiveFocus()
event.accepted = true
}
}
Layout.fillHeight: true
property bool deactivate: false
active: !deactivate && magicSearchBar.text != ''
property string t: magicSearchBar.text
onTChanged: {
contactLoader.deactivate = true
Qt.callLater(function(){contactLoader.deactivate=false})
}
header: Text {
visible: contactList.count > 0
text: qsTr("Contact")
color: DefaultStyle.main2_500main
font {
pixelSize: 13 * DefaultStyle.dp
weight: 700 * DefaultStyle.dp
}
}
footer: FocusScope{
id: suggestionFocusScope
width: contactList.width
height: visible ? content.implicitHeight : 0
onActiveFocusChanged: if(activeFocus) contactList.positionViewAtEnd()
visible: !contactList.haveAddress(suggestionText.text)
Rectangle{
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: suggestionRow.implicitHeight
color: suggestionFocusScope.activeFocus ? DefaultStyle.numericPadPressedButtonColor : 'transparent'
}
ColumnLayout {
id: content
anchors.fill: parent
anchors.rightMargin: 5 * DefaultStyle.dp
spacing: 10 * DefaultStyle.dp
Text {
text: qsTr("Suggestion")
color: DefaultStyle.main2_500main
font {
pixelSize: 13 * DefaultStyle.dp
weight: 700 * DefaultStyle.dp
}
}
Keys.onPressed: (event) => {
if(contactList.count <= 0) return;
if(event.key == Qt.Key_Down){
contactList.currentIndex = 0
//-------------------------------------------------------------
sourceComponent: ContactListView {
id: contactList
visible: magicSearchBar.text.length != 0
Layout.preferredHeight: item?.contentHeight
Layout.fillWidth: true
itemsRightMargin: 5 * DefaultStyle.dp //(Actions have already 10 of margin)
showInitials: false
showContactMenu: false
showActions: true
showFavorites: false
selectionEnabled: false
showDefaultAddress: true
hideSuggestions: true
sectionsPixelSize: 13 * DefaultStyle.dp
sectionsWeight: 700 * DefaultStyle.dp
sectionsSpacing: 5 * DefaultStyle.dp
Control.ScrollBar.vertical: scrollbar
searchBarText: magicSearchBar.text
Keys.onPressed: (event) => {
if(event.key == Qt.Key_Down){
if(contactList.currentIndex == contactList.count -1) {
contactList.currentIndex = -1
contactList.footerItem.forceActiveFocus()
event.accepted = true
} else if(event.key == Qt.Key_Up){
contactList.currentIndex = contactList.count - 1
}
} else if(event.key == Qt.Key_Up){
if(contactList.currentIndex <= 0) {
contactList.currentIndex = -1
contactList.footerItem.forceActiveFocus()
event.accepted = true
}
}
RowLayout {
id: suggestionRow
spacing: 10 * DefaultStyle.dp
}
footer: FocusScope{
id: suggestionFocusScope
width: contactList.width
height: visible ? content.implicitHeight : 0
onActiveFocusChanged: if(activeFocus) contactList.positionViewAtEnd()
visible: !contactList.haveAddress(suggestionText.text)
Rectangle{
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: suggestionRow.implicitHeight
color: suggestionFocusScope.activeFocus ? DefaultStyle.numericPadPressedButtonColor : 'transparent'
}
ColumnLayout {
id: content
anchors.fill: parent
anchors.leftMargin: 5 * DefaultStyle.dp
anchors.rightMargin: 15 * DefaultStyle.dp
Avatar {
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
_address: magicSearchBar.text
}
spacing: 10 * DefaultStyle.dp
Text {
id: suggestionText
property var urlObj: UtilsCpp.interpretUrl(magicSearchBar.text)
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(urlObj?.value) : urlObj?.value
text: qsTr("Suggestion")
color: DefaultStyle.main2_500main
font {
pixelSize: 12 * DefaultStyle.dp
weight: 300 * DefaultStyle.dp
pixelSize: 13 * DefaultStyle.dp
weight: 700 * DefaultStyle.dp
}
}
Item {
Layout.fillWidth: true
}
MagicSearchButton {
id: callButton
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
icon.source: AppIcons.phone
focus: true
onClicked: {
UtilsCpp.createCall(magicSearchBar.text)
magicSearchBar.clearText()
Keys.onPressed: (event) => {
if(contactList.count <= 0) return;
if(event.key == Qt.Key_Down){
contactList.currentIndex = 0
event.accepted = true
} else if(event.key == Qt.Key_Up){
contactList.currentIndex = contactList.count - 1
event.accepted = true
}
KeyNavigation.right: chatButton
KeyNavigation.left: chatButton
}
MagicSearchButton {
id: chatButton
visible: !SettingsCpp.disableChatFeature
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
icon.source: AppIcons.chatTeardropText
KeyNavigation.right: callButton
KeyNavigation.left: callButton
RowLayout {
id: suggestionRow
spacing: 10 * DefaultStyle.dp
Avatar {
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
_address: magicSearchBar.text
}
Text {
id: suggestionText
property var urlObj: UtilsCpp.interpretUrl(magicSearchBar.text)
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(urlObj?.value) : urlObj?.value
font {
pixelSize: 12 * DefaultStyle.dp
weight: 300 * DefaultStyle.dp
}
}
Item {
Layout.fillWidth: true
}
MagicSearchButton {
id: callButton
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
icon.source: AppIcons.phone
focus: true
onClicked: {
UtilsCpp.createCall(magicSearchBar.text)
magicSearchBar.clearText()
}
KeyNavigation.right: chatButton
KeyNavigation.left: chatButton
}
MagicSearchButton {
id: chatButton
visible: !SettingsCpp.disableChatFeature
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
icon.source: AppIcons.chatTeardropText
KeyNavigation.right: callButton
KeyNavigation.left: callButton
}
}
}
}

View file

@ -55,7 +55,7 @@ AbstractMainPage {
onNoItemButtonPressed: goToNewCall()
showDefaultItem: listStackView.currentItem && listStackView.currentItem.objectName == "historyListItem" && listStackView.currentItem.listView.count === 0
showDefaultItem: listStackView.currentItem && listStackView.currentItem.objectName == "historyListItem" && listStackView.currentItem.listView.count === 0 || false
function goToNewCall() {
if (listStackView.currentItem && listStackView.currentItem.objectName != "newCallItem") listStackView.push(newCallItem)

View file

@ -20,8 +20,7 @@ AbstractMainPage {
onVisibleChanged: if (!visible) {
rightPanelStackView.clear()
contactList.currentIndex = -1
favoriteList.currentIndex = -1
if(contactLoader.item) contactLoader.item.currentIndex = -1
}
onSelectedContactChanged: {
@ -52,13 +51,8 @@ AbstractMainPage {
// rightPanelStackView.initialItem: contactDetail
showDefaultItem: rightPanelStackView.depth == 0 && leftPanelNoItemText.visible && searchBar.text.length === 0
showDefaultItem: rightPanelStackView.depth == 0 && contactLoader.item?.count === 0 && searchBar.text.length === 0
MagicSearchProxy {
id: allFriends
showLdapContacts: SettingsCpp.syncLdapContacts
}
function deleteContact(contact) {
if (!contact) return
var mainWin = UtilsCpp.getMainWindow()
@ -211,170 +205,60 @@ AbstractMainPage {
spacing: 38 * DefaultStyle.dp
SearchBar {
id: searchBar
visible: contactList.count != 0 || text.length !== 0
visible: !contactLoader.item || contactLoader.item.loading || contactLoader.item.count != 0 || text.length !== 0
Layout.leftMargin: leftPanel.leftMargin
Layout.rightMargin: leftPanel.rightMargin
Layout.topMargin: 18 * DefaultStyle.dp
Layout.fillWidth: true
placeholderText: qsTr("Rechercher un contact")
KeyNavigation.up: createContactButton
KeyNavigation.down: favoriteList.contentHeight > 0 ? favoriteExpandButton : contactExpandButton
KeyNavigation.down: contactLoader.item
}
RowLayout {
Flickable {
id: listLayout
contentWidth: width
contentHeight: content.height
clip: true
Control.ScrollBar.vertical: contactsScrollbar
ColumnLayout {
id: content
spacing: 15 * DefaultStyle.dp
Text {
visible: contactLoader.item && !contactLoader.item.loading && contactLoader.item.count === 0
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 137 * DefaultStyle.dp
text: qsTr("Aucun contact%1").arg(searchBar.text.length !== 0 ? " correspondant" : "")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
Loader{
// This is a hack for an incomprehensible behavior on sections title where they doesn't match with their delegate and can be unordered after resetting models.
id: contactLoader
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
id: content
width: parent.width
spacing: 15 * DefaultStyle.dp
Text {
id: leftPanelNoItemText
visible: contactList.count === 0
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 137 * DefaultStyle.dp
text: qsTr("Aucun contact%1").arg(searchBar.text.length !== 0 ? " correspondant" : "")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
Layout.leftMargin: 45 * DefaultStyle.dp
property string t: searchBar.text
active: leftPanel.visible
onTChanged: {
contactLoader.active = false
Qt.callLater(function(){contactLoader.active=true})
}
//-------------------------------------------------------------
sourceComponent: ContactListView{
id: contactList
searchBarText: searchBar.text
hideSuggestions: true
sourceFlags: LinphoneEnums.MagicSearchSource.Friends | LinphoneEnums.MagicSearchSource.FavoriteFriends | LinphoneEnums.MagicSearchSource.LdapServers
onSelectedContactChanged: {
mainItem.selectedContact = selectedContact
}
ColumnLayout {
visible: favoriteList.contentHeight > 0
onVisibleChanged: if (visible && !favoriteList.visible) favoriteList.visible = true
Layout.leftMargin: leftPanel.leftMargin
Layout.rightMargin: leftPanel.rightMargin
spacing: 18 * DefaultStyle.dp
RowLayout {
spacing: 0
Text {
text: qsTr("Favoris")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
Item {
Layout.fillWidth: true
}
Button {
id: favoriteExpandButton
background: Item{}
icon.source: favoriteList.visible ? AppIcons.upArrow : AppIcons.downArrow
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
onClicked: favoriteList.visible = !favoriteList.visible
KeyNavigation.up: searchBar
KeyNavigation.down: favoriteList
}
}
ContactListView{
id: favoriteList
hoverEnabled: mainItem.leftPanelEnabled
highlightFollowsCurrentItem: true
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
Control.ScrollBar.vertical.visible: false
showFavoritesOnly: true
searchOnInitialization: true
contactMenuVisible: true
searchBarText: searchBar.text
listProxy: allFriends
onSelectedContactChanged: {
if (selectedContact) {
contactList.currentIndex = -1
}
mainItem.selectedContact = selectedContact
}
onContactDeletionRequested: (contact) => {
mainItem.deleteContact(contact)
}
}
onContactDeletionRequested: (contact) => {
mainItem.deleteContact(contact)
}
ColumnLayout {
visible: contactList.contentHeight > 0
onVisibleChanged: if (visible && !contactList.visible) contactList.visible = true
Layout.leftMargin: leftPanel.leftMargin
Layout.rightMargin: leftPanel.rightMargin
spacing: 16 * DefaultStyle.dp
RowLayout {
spacing: 0
Text {
text: qsTr("Contacts")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
Item {
Layout.fillWidth: true
}
Button {
id: contactExpandButton
background: Item{}
icon.source: contactList.visible ? AppIcons.upArrow : AppIcons.downArrow
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
onClicked: contactList.visible = !contactList.visible
KeyNavigation.up: favoriteList.visible ? favoriteList : searchBar
KeyNavigation.down: contactList
}
}
ContactListView{
id: contactList
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
Control.ScrollBar.vertical.visible: false
hoverEnabled: mainItem.leftPanelEnabled
contactMenuVisible: true
searchOnInitialization: true
highlightFollowsCurrentItem: true
searchBarText: searchBar.text
listProxy: allFriends
onCountChanged: {
if (initialFriendToDisplay.length !== 0) {
if (selectContact(initialFriendToDisplay) != -1) initialFriendToDisplay = ""
}
}
onSelectedContactChanged: {
if (selectedContact) {
favoriteList.currentIndex = -1
}
mainItem.selectedContact = selectedContact
}
onContactDeletionRequested: (contact) => {
mainItem.deleteContact(contact)
}
Connections {
target: contactList.model
function onFriendCreated(index) {
contactList.currentIndex = index
}
}
onCountChanged: {
if (initialFriendToDisplay.length !== 0) {
if (selectContact(initialFriendToDisplay) != -1) initialFriendToDisplay = ""
}
}
}
}
ScrollBar {
id: contactsScrollbar
Layout.fillHeight: true
Layout.rightMargin: 8 * DefaultStyle.dp
height: listLayout.height
active: true
interactive: true
policy: Control.ScrollBar.AsNeeded
}
}
}
}
@ -493,8 +377,6 @@ AbstractMainPage {
spacing: 0
Text {
text: contactDetail.contactName
Layout.fillWidth: true
maximumLineCount: 1
font {
pixelSize: 29 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
@ -579,6 +461,7 @@ AbstractMainPage {
model: mainItem.selectedContact ? mainItem.selectedContact.core.allAddresses : []
}
delegate: Item {
property var listViewModelData: modelData
width: addrList.width
height: 46 * DefaultStyle.dp
@ -595,7 +478,7 @@ AbstractMainPage {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
text: modelData.label
text: listViewModelData.label
font {
pixelSize: 13 * DefaultStyle.dp
weight: 700 * DefaultStyle.dp
@ -603,7 +486,7 @@ AbstractMainPage {
}
Text {
Layout.fillWidth: true
property string _text: modelData.address
property string _text: listViewModelData.address
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(_text) : _text
font {
pixelSize: 14 * DefaultStyle.dp
@ -624,7 +507,7 @@ AbstractMainPage {
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
onClicked: {
UtilsCpp.createCall(modelData.address)
UtilsCpp.createCall(listViewModelData.address)
}
}
}
@ -761,16 +644,17 @@ AbstractMainPage {
id: deviceDelegate
width: deviceList.width
height: 30 * DefaultStyle.dp
property var listViewModelData: modelData
property var callObj
property CallGui deviceCall: callObj ? callObj.value : null
property string deviceName: modelData.name.length != 0 ? modelData.name : qsTr("Appareil sans nom")
property string deviceName: listViewModelData.name.length != 0 ? listViewModelData.name : qsTr("Appareil sans nom")
Text {
text: deviceDelegate.deviceName
font.pixelSize: 14 * DefaultStyle.dp
}
Item{Layout.fillWidth: true}
Image{
visible: modelData.securityLevel === LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified
visible: listViewModelData.securityLevel === LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified
source: AppIcons.trusted
width: 22 * DefaultStyle.dp
height: 22 * DefaultStyle.dp
@ -778,7 +662,7 @@ AbstractMainPage {
Button {
Layout.preferredHeight: 30 * DefaultStyle.dp
visible: modelData.securityLevel != LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified
visible: listViewModelData.securityLevel != LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified
color: DefaultStyle.main1_100
icon.source: AppIcons.warningCircle
icon.height: 14 * DefaultStyle.dp
@ -794,12 +678,12 @@ AbstractMainPage {
onClicked: {
if (SettingsCpp.getDisplayDeviceCheckConfirmation()) {
verifyDevicePopup.deviceName = deviceDelegate.deviceName
verifyDevicePopup.deviceAddress = modelData.address
verifyDevicePopup.deviceAddress = listViewModelData.address
verifyDevicePopup.open()
}
else {
UtilsCpp.createCall(modelData.address, {}, LinphoneEnums.MediaEncryption.Zrtp)
parent.callObj = UtilsCpp.getCallByAddress(modelData.address)
UtilsCpp.createCall(listViewModelData.address, {}, LinphoneEnums.MediaEncryption.Zrtp)
parent.callObj = UtilsCpp.getCallByAddress(listViewModelData.address)
}
}
}