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(); auto linphoneSearch = CoreModel::getInstance()->getCore()->createMagicSearch();
linphoneSearch->setLimitedSearch(true); linphoneSearch->setLimitedSearch(true);
mLdapMagicSearchModel = Utils::makeQObject_ptr<MagicSearchModel>(linphoneSearch); mLdapMagicSearchModel = Utils::makeQObject_ptr<MagicSearchModel>(linphoneSearch);
mLdapMagicSearchModel->setSourceFlags((int)LinphoneEnums::MagicSearchSource::LdapServers);
mLdapMagicSearchModel->setAggregationFlag(LinphoneEnums::MagicSearchAggregation::Friend);
mLdapMagicSearchModel->setSelf(mLdapMagicSearchModel); mLdapMagicSearchModel->setSelf(mLdapMagicSearchModel);
mLdapMagicSearchModelConnection = QSharedPointer<SafeConnection<CallCore, MagicSearchModel>>( mLdapMagicSearchModelConnection = QSharedPointer<SafeConnection<CallCore, MagicSearchModel>>(
new SafeConnection<CallCore, MagicSearchModel>(me, mLdapMagicSearchModel), &QObject::deleteLater); 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; return map;
} }
QSharedPointer<FriendCore> FriendCore::create(const std::shared_ptr<linphone::Friend> &contact) { QSharedPointer<FriendCore> FriendCore::create(const std::shared_ptr<linphone::Friend> &contact, bool isStored) {
auto sharedPointer = QSharedPointer<FriendCore>(new FriendCore(contact), &QObject::deleteLater); auto sharedPointer = QSharedPointer<FriendCore>(new FriendCore(contact, isStored), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer); sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread()); sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer; 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); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
if (contact) { if (contact) {
mustBeInLinphoneThread(getClassName()); mustBeInLinphoneThread(getClassName());
@ -60,6 +60,7 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObje
mConsolidatedPresence = LinphoneEnums::fromLinphone(contact->getConsolidatedPresence()); mConsolidatedPresence = LinphoneEnums::fromLinphone(contact->getConsolidatedPresence());
mPresenceTimestamp = mFriendModel->getPresenceTimestamp(); mPresenceTimestamp = mFriendModel->getPresenceTimestamp();
mPictureUri = Utils::coreStringToAppString(contact->getPhoto()); mPictureUri = Utils::coreStringToAppString(contact->getPhoto());
auto defaultAddress = contact->getAddress();
auto vcard = contact->getVcard(); auto vcard = contact->getVcard();
if (vcard) { if (vcard) {
mOrganization = Utils::coreStringToAppString(vcard->getOrganization()); mOrganization = Utils::coreStringToAppString(vcard->getOrganization());
@ -69,13 +70,16 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObje
mFullName = Utils::coreStringToAppString(vcard->getFullName()); mFullName = Utils::coreStringToAppString(vcard->getFullName());
mVCardString = Utils::coreStringToAppString(vcard->asVcard4String()); mVCardString = Utils::coreStringToAppString(vcard->asVcard4String());
} }
if (defaultAddress) {
if (mGivenName.isEmpty()) mGivenName = Utils::coreStringToAppString(defaultAddress->getUsername());
}
auto addresses = contact->getAddresses(); auto addresses = contact->getAddresses();
for (auto &address : addresses) { for (auto &address : addresses) {
mAddressList.append( mAddressList.append(
createFriendAddressVariant(_addressLabel, Utils::coreStringToAppString(address->asStringUriOnly()))); createFriendAddressVariant(_addressLabel, Utils::coreStringToAppString(address->asStringUriOnly())));
} }
mDefaultAddress = mDefaultAddress =
contact->getAddress() ? Utils::coreStringToAppString(contact->getAddress()->asStringUriOnly()) : QString(); defaultAddress ? Utils::coreStringToAppString(contact->getAddress()->asStringUriOnly()) : QString();
auto phoneNumbers = contact->getPhoneNumbersWithLabel(); auto phoneNumbers = contact->getPhoneNumbersWithLabel();
for (auto &phoneNumber : phoneNumbers) { for (auto &phoneNumber : phoneNumbers) {
mPhoneNumberList.append( mPhoneNumberList.append(
@ -94,9 +98,11 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObje
mStarred = contact->getStarred(); mStarred = contact->getStarred();
mIsSaved = true; mIsSaved = true;
mIsStored = isStored;
} else { } else {
mIsSaved = false; mIsSaved = false;
mStarred = false; mStarred = false;
mIsStored = false;
} }
mIsLdap = ToolModel::friendIsInFriendList(ToolModel::getLdapFriendList(), contact); mIsLdap = ToolModel::friendIsInFriendList(ToolModel::getLdapFriendList(), contact);
@ -502,15 +508,28 @@ bool FriendCore::getIsSaved() const {
void FriendCore::setIsSaved(bool data) { void FriendCore::setIsSaved(bool data) {
if (mIsSaved != data) { if (mIsSaved != data) {
mIsSaved = data; mIsSaved = data;
if (mIsSaved) setIsStored(true);
emit isSavedChanged(mIsSaved); 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 { void FriendCore::writeIntoModel(std::shared_ptr<FriendModel> model) const {
mustBeInLinphoneThread(QString("[") + gClassName + "] " + Q_FUNC_INFO); mustBeInLinphoneThread(QString("[") + gClassName + "] " + Q_FUNC_INFO);
model->getFriend()->edit(); model->getFriend()->edit();
// needed to create the vcard if not created yet // 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(); auto core = CoreModel::getInstance()->getCore();
std::list<std::shared_ptr<linphone::Address>> addresses; 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 Q_PROPERTY(LinphoneEnums::ConsolidatedPresence consolidatedPresence READ getConsolidatedPresence NOTIFY
consolidatedPresenceChanged) consolidatedPresenceChanged)
Q_PROPERTY(bool isSaved READ getIsSaved NOTIFY isSavedChanged) 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(QString pictureUri READ getPictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
Q_PROPERTY(bool starred READ getStarred WRITE lSetStarred NOTIFY starredChanged) Q_PROPERTY(bool starred READ getStarred WRITE lSetStarred NOTIFY starredChanged)
Q_PROPERTY(bool readOnly READ getReadOnly CONSTANT) Q_PROPERTY(bool readOnly READ getReadOnly CONSTANT)
@ -72,8 +73,8 @@ class FriendCore : public QObject, public AbstractObject {
public: public:
// Should be call from model Thread. Will be automatically in App thread after initialization // 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); static QSharedPointer<FriendCore> create(const std::shared_ptr<linphone::Friend> &contact, bool isStored = true);
FriendCore(const std::shared_ptr<linphone::Friend> &contact); FriendCore(const std::shared_ptr<linphone::Friend> &contact, bool isStored = true);
FriendCore(const FriendCore &friendCore); FriendCore(const FriendCore &friendCore);
~FriendCore(); ~FriendCore();
void setSelf(QSharedPointer<FriendCore> me); void setSelf(QSharedPointer<FriendCore> me);
@ -81,7 +82,6 @@ public:
void reset(const FriendCore &contact); void reset(const FriendCore &contact);
QString getDisplayName() const; QString getDisplayName() const;
void setDisplayName(const QString &name);
QString getFamilyName() const; QString getFamilyName() const;
void setFamilyName(const QString &name); void setFamilyName(const QString &name);
@ -131,6 +131,9 @@ public:
bool getIsSaved() const; bool getIsSaved() const;
void setIsSaved(bool isSaved); void setIsSaved(bool isSaved);
bool getIsStored() const; // Exist in DB
void setIsStored(bool isStored);
QString getPictureUri() const; QString getPictureUri() const;
void setPictureUri(const QString &uri); void setPictureUri(const QString &uri);
void onPictureUriChanged(QString uri); void onPictureUriChanged(QString uri);
@ -163,6 +166,7 @@ signals:
void pictureUriChanged(); void pictureUriChanged();
void saved(); void saved();
void isSavedChanged(bool isSaved); void isSavedChanged(bool isSaved);
void isStoredChanged();
void removed(FriendCore *contact); void removed(FriendCore *contact);
void defaultAddressChanged(); void defaultAddressChanged();
void allAddressesChanged(); void allAddressesChanged();
@ -189,6 +193,7 @@ protected:
QString mDefaultAddress; QString mDefaultAddress;
QString mPictureUri; QString mPictureUri;
bool mIsSaved; bool mIsSaved;
bool mIsStored;
QString mVCardString; QString mVCardString;
bool mIsLdap; bool mIsLdap;
std::shared_ptr<FriendModel> mFriendModel; std::shared_ptr<FriendModel> mFriendModel;

View file

@ -26,11 +26,13 @@ DEFINE_ABSTRACT_OBJECT(FriendGui)
FriendGui::FriendGui(QObject *parent) : QObject(parent) { FriendGui::FriendGui(QObject *parent) : QObject(parent) {
mustBeInMainThread(getClassName()); mustBeInMainThread(getClassName());
mCore = FriendCore::create(nullptr); mCore = FriendCore::create(nullptr);
connect(mCore.get(), &FriendCore::isStoredChanged, this, &FriendGui::isStoredChanged);
} }
FriendGui::FriendGui(QSharedPointer<FriendCore> core) { FriendGui::FriendGui(QSharedPointer<FriendCore> core) {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
mCore = core; mCore = core;
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
connect(mCore.get(), &FriendCore::isStoredChanged, this, &FriendGui::isStoredChanged);
} }
FriendGui::~FriendGui() { FriendGui::~FriendGui() {
@ -43,3 +45,7 @@ void FriendGui::createContact(const QString &address) {
FriendCore *FriendGui::getCore() const { FriendCore *FriendGui::getCore() const {
return mCore.get(); 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_OBJECT
Q_PROPERTY(FriendCore *core READ getCore CONSTANT) Q_PROPERTY(FriendCore *core READ getCore CONSTANT)
Q_PROPERTY(bool isStored READ getIsStored NOTIFY isStoredChanged)
public: public:
FriendGui(QObject *parent = nullptr); FriendGui(QObject *parent = nullptr);
@ -37,6 +38,12 @@ public:
FriendCore *getCore() const; FriendCore *getCore() const;
Q_INVOKABLE void createContact(const QString &address); Q_INVOKABLE void createContact(const QString &address);
QSharedPointer<FriendCore> mCore; QSharedPointer<FriendCore> mCore;
bool getIsStored();
signals:
void isStoredChanged();
public:
DECLARE_ABSTRACT_OBJECT DECLARE_ABSTRACT_OBJECT
}; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -23,6 +23,7 @@
#include <QDebug> #include <QDebug>
#include "model/core/CoreModel.hpp" #include "model/core/CoreModel.hpp"
#include "model/setting/SettingsModel.hpp"
#include "model/tool/ToolModel.hpp" #include "model/tool/ToolModel.hpp"
#include "tool/Utils.hpp" #include "tool/Utils.hpp"
@ -37,35 +38,43 @@ MagicSearchModel::~MagicSearchModel() {
mustBeInLinphoneThread("~" + getClassName()); mustBeInLinphoneThread("~" + getClassName());
} }
void MagicSearchModel::search(QString filter) { void MagicSearchModel::search(QString filter,
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); int sourceFlags,
LinphoneEnums::MagicSearchAggregation aggregation,
int maxResults) {
mLastSearch = filter; mLastSearch = filter;
mMonitor->getContactsListAsync(filter != "*" ? Utils::appStringToCoreString(filter) : "", "", mSourceFlags, setMaxResults(maxResults);
LinphoneEnums::toLinphone(mAggregationFlag)); if ((filter == "" || filter == "*") && ((sourceFlags & (int)LinphoneEnums::MagicSearchSource::LdapServers) > 0) &&
} !SettingsModel::getInstance()->getSyncLdapContacts()) {
sourceFlags &= ~(int)LinphoneEnums::MagicSearchSource::LdapServers;
void MagicSearchModel::setSourceFlags(int flags) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
if (mSourceFlags != flags) {
mSourceFlags = flags;
emit sourceFlagsChanged(mSourceFlags);
} }
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) { int MagicSearchModel::getMaxResults() const {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); if (!mMonitor->getLimitedSearch()) return -1;
if (mAggregationFlag != flag) { else return mMonitor->getSearchLimit();
mAggregationFlag = flag; }
emit aggregationFlagChanged(mAggregationFlag);
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) { 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; bool isLdap = (it->getSourceFlags() & (int)LinphoneEnums::MagicSearchSource::LdapServers) != 0;
if (isLdap && it->getFriend()) updateLdapFriendListWithFriend(it->getFriend()); if (isLdap && it->getFriend()) updateLdapFriendListWithFriend(it->getFriend());
} }
emit searchResultsReceived(magicSearch->getLastSearch()); emit searchResultsReceived(results);
} }
void MagicSearchModel::onLdapHaveMoreResults(const std::shared_ptr<linphone::MagicSearch> &magicSearch, 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(const std::shared_ptr<linphone::MagicSearch> &data, QObject *parent = nullptr);
~MagicSearchModel(); ~MagicSearchModel();
void search(QString filter); void search(QString filter, int sourceFlags, LinphoneEnums::MagicSearchAggregation aggregation, int maxResults);
void setSourceFlags(int flags);
void setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag);
int mSourceFlags = (int)linphone::MagicSearch::Source::All; int getMaxResults() const;
LinphoneEnums::MagicSearchAggregation mAggregationFlag = LinphoneEnums::MagicSearchAggregation::None; void setMaxResults(int maxResults);
QString mLastSearch; QString mLastSearch;
signals: signals:
void sourceFlagsChanged(int sourceFlags); void maxResultsChanged(int maxResults);
void aggregationFlagChanged(LinphoneEnums::MagicSearchAggregation aggregationFlag);
private: private:
DECLARE_ABSTRACT_OBJECT DECLARE_ABSTRACT_OBJECT

View file

@ -47,6 +47,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Control/Display/Call/CallStatistics.qml view/Control/Display/Call/CallStatistics.qml
view/Control/Display/Contact/Avatar.qml view/Control/Display/Contact/Avatar.qml
view/Control/Display/Contact/Contact.qml view/Control/Display/Contact/Contact.qml
view/Control/Display/Contact/ContactListItem.qml
view/Control/Display/Contact/ContactListView.qml view/Control/Display/Contact/ContactListView.qml
view/Control/Display/Contact/Voicemail.qml view/Control/Display/Contact/Voicemail.qml
view/Control/Display/Meeting/MeetingListView.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,53 +7,57 @@ import UtilsCpp 1.0
import ConstantsCpp 1.0 import ConstantsCpp 1.0
import SettingsCpp import SettingsCpp
ListView { ListView {
id: mainItem id: mainItem
height: contentHeight
visible: contentHeight > 0
clip: true
currentIndex: -1
//keyNavigationWraps: true
// rightMargin: 5 * DefaultStyle.dp
property bool selectionEnabled: true property bool showInitials: true // Display Initials of Display name.
property bool hoverEnabled: true property bool showDefaultAddress: true // Display address below display name.
// dots popup menu property bool showActions: false // Display actions layout (call buttons)
property bool contactMenuVisible: true property bool showContactMenu: true // Display the dot menu for contacts.
// call, video call etc menu property bool showFavorites: true // Display the favorites in the header
property bool actionLayoutVisible: false property bool hideSuggestions: false // Hide not stored contacts (not suggestions)
property bool initialHeadersVisible: true property string highlightText // Bold characters in Display name.
property bool displayNameCapitalization: true property var sourceFlags: LinphoneEnums.MagicSearchSource.All
property bool showFavoritesOnly: false
property bool showDefaultAddress: false property bool displayNameCapitalization: true // Capitalize display name.
property bool showLdapContacts: false
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 bool searchOnInitialization: false
property bool loading: false
property var listProxy: MagicSearchProxy{} property bool pauseSearch: false // true = don't search on text change
property alias hideListProxy: magicSearchProxy.hideListProxy
// Model properties // Model properties
// set searchBarText without specifying a model to bold // set searchBarText without specifying a model to bold
// matching names // matching names
property string searchBarText property string searchBarText
property string searchText: searchBarText.length === 0 ? "*" : searchBarText property string searchText// Binding is done on searchBarTextChanged
property var aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend
property var sourceFlags: LinphoneEnums.MagicSearchSource.Friends | ((searchText.length > 0 && searchText != "*") || SettingsCpp.syncLdapContacts ? LinphoneEnums.MagicSearchSource.LdapServers : 0)
property ConferenceInfoGui confInfoGui property ConferenceInfoGui confInfoGui
property bool multiSelectionEnabled: false property bool haveFavorites: false
property list<string> selectedContacts property int sectionsPixelSize: 16 * DefaultStyle.dp
property int selectedContactCount: selectedContacts.length property int sectionsWeight: 800 * DefaultStyle.dp
property int sectionsSpacing: 18 * DefaultStyle.dp
property FriendGui selectedContact: model.getAt(currentIndex) || null property int itemsRightMargin: 39 * DefaultStyle.dp
signal resultsReceived()
signal contactStarredChanged() signal contactStarredChanged()
signal contactDeletionRequested(FriendGui contact) signal contactDeletionRequested(FriendGui contact)
signal contactAddedToSelection(string address) signal contactAddedToSelection(string address)
signal contactRemovedFromSelection(string address) signal contactRemovedFromSelection(string address)
signal contactClicked(FriendGui contact) 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) { function selectContact(address) {
var index = magicSearchProxy.findFriendIndexByAddress(address) var index = magicSearchProxy.findFriendIndexByAddress(address)
if (index != -1) { if (index != -1) {
@ -89,9 +93,75 @@ ListView {
return index != -1 return index != -1
} }
onCurrentIndexChanged: selectedContact = model.getAt(currentIndex) || null onResultsReceived: {
onCountChanged: selectedContact = model.getAt(currentIndex) || null 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: { Component.onCompleted: {
if (confInfoGui) { if (confInfoGui) {
for(var i = 0; i < confInfoGui.core.participants.length; ++i) { for(var i = 0; i < confInfoGui.core.participants.length; ++i) {
@ -100,32 +170,6 @@ 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 { Connections {
target: SettingsCpp target: SettingsCpp
onLdapConfigChanged: { onLdapConfigChanged: {
@ -136,255 +180,302 @@ ListView {
Control.ScrollBar.vertical: ScrollBar { Control.ScrollBar.vertical: ScrollBar {
id: scrollbar id: scrollbar
rightPadding: 8 * DefaultStyle.dp
topPadding: mainItem.haveFavorites ? 24 * DefaultStyle.dp : 0 // Avoid to be on top of collapse button
active: true active: true
interactive: true interactive: true
// anchors.top: parent.top policy: mainItem.contentHeight > mainItem.height ? Control.ScrollBar.AlwaysOn : Control.ScrollBar.AlwaysOff
// anchors.bottom: parent.bottom
// anchors.right: parent.right
} }
Keys.onPressed: (event)=>{
if(event.key == Qt.Key_Tab && !mainItem.itemAtIndex(mainItem.currentIndex).activeFocus){ model: MagicSearchProxy {
mainItem.itemAtIndex(mainItem.currentIndex).forceActiveFocus() 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()
} }
} }
delegate: FocusScope { onInitialized: {
id: itemDelegate mainItem.loading = true
height: 56 * DefaultStyle.dp magicSearchProxy.forceUpdate()
}
onModelReset: mainItem.resultsReceived()
}
section.property: "isStored"
//section.criteria: ViewSection.FirstCharacter
section.delegate: Item{
width: mainItem.width width: mainItem.width
property var previousItem : mainItem.model.count > 0 && index > 0 ? mainItem.model.getAt(index-1) : null height: textItem.implicitHeight + sectionsSpacing * 2
property var previousDisplayName: previousItem ? previousItem.core.displayName : "" required property bool section
property var displayName: modelData.core.displayName
Connections {
enabled: modelData.core
target: modelData.core
function onStarredChanged() { mainItem.contactStarredChanged()}
}
Text { Text {
id: initial id: textItem
anchors.left: parent.left anchors.fill: parent
visible: mainItem.initialHeadersVisible && mainItem.model.sourceFlags != LinphoneEnums.MagicSearchSource.All text: section ? qsTr("Contacts") : qsTr("Suggestions")
anchors.verticalCenter: parent.verticalCenter horizontalAlignment: Text.AlignLeft
anchors.rightMargin: 15 * DefaultStyle.dp
verticalAlignment: Text.AlignVCenter 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 { font {
pixelSize: 20 * DefaultStyle.dp pixelSize: sectionsPixelSize
weight: 500 * DefaultStyle.dp weight: sectionsWeight
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
} }
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
}
}
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
}
}
ColumnLayout { ColumnLayout {
spacing: 0 id: favoritesContents
Text { width: parent.width
text: UtilsCpp.boldTextPart(itemDelegate.displayName, mainItem.searchBarText) spacing: mainItem.haveFavorites ? sectionsSpacing : 0
font{ BusyIndicator {
pixelSize: mainItem.showDefaultAddress ? 16 * DefaultStyle.dp : 14 * DefaultStyle.dp Layout.alignment: Qt.AlignCenter
capitalization: mainItem.displayNameCapitalization ? Font.Capitalize : Font.MixedCase Layout.preferredHeight: visible ? 60 * DefaultStyle.dp : 0
weight: mainItem.showDefaultAddress ? 800 * DefaultStyle.dp : 400 * DefaultStyle.dp Layout.preferredWidth: 60 * DefaultStyle.dp
indicatorHeight: 60 * DefaultStyle.dp
indicatorWidth: 60 * DefaultStyle.dp
visible: mainItem.loading
indicatorColor: DefaultStyle.main1_500_main
} }
maximumLineCount: 1 Item{// Do not use directly RowLayout : there is an issue where the layout doesn't update on visible
Layout.fillWidth: true Layout.fillWidth: true
} Layout.preferredHeight: mainItem.haveFavorites ? favoriteTitle.implicitHeight : 0
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
}
}
}
Item{Layout.fillWidth: true}
RowLayout { RowLayout {
id: actionsRow id: favoriteTitle
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)}
}
}
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 anchors.fill: parent
radius: 40 * DefaultStyle.dp spacing: 0
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 { // Need this because it can stay at 0 on display without manual relayouting (moving position, resize)
Button { visible: mainItem.haveFavorites
text: $modelData.core.starred ? qsTr("Enlever des favoris") : qsTr("Mettre en favori") onVisibleChanged: if(visible) {
background: Item{} Qt.callLater(mainItem.positionViewAtBeginning)// If not later, the view will not move to favoris at startup
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()
} }
Text {
//Layout.fillHeight: true
text: qsTr("Favoris")
font {
pixelSize: sectionsPixelSize
weight: sectionsWeight
}
}
Item {
Layout.fillWidth: true
} }
Button { Button {
text: qsTr("Partager") id: favoriteExpandButton
background: Item{} background: Item{}
icon.source: AppIcons.shareNetwork 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.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp icon.height: 24 * DefaultStyle.dp
spacing: 10 * DefaultStyle.dp focus: true
textSize: 14 * DefaultStyle.dp onClicked: favoriteList.visible = !favoriteList.visible
textWeight: 400 * DefaultStyle.dp KeyNavigation.down: favoriteList
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)
}
}
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()
}
}
}
} }
} }
} }
ListView{
id: favoriteList
Layout.fillWidth: true
Layout.preferredHeight: count > 0 ? contentHeight : 0// Show full and avoid scrolling
MouseArea {
id: contactArea
enabled: mainItem.selectionEnabled onCountChanged: mainItem.haveFavorites = count > 0
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)=> { Keys.onPressed: (event)=> {
if (event.key == Qt.Key_Space || event.key == Qt.Key_Enter || event.key == Qt.Key_Return) { if(event.key == Qt.Key_Up || event.key == Qt.Key_Down) {
contactArea.clicked(undefined) 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; event.accepted = true;
} }
} }
}
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) => { onClicked: (mouse) => {
if (mouse && mouse.button == Qt.RightButton) { mainItem.highlightFollowsCurrentItem = false
friendPopup.open() favoriteList.currentIndex = index
} else { mainItem.selectedContact = searchResultItem
mainItem.forceActiveFocus() forceActiveFocus()
mainItem.currentIndex = index headerItem.updatePosition()
if (mainItem.multiSelectionEnabled) { if (mainItem.multiSelectionEnabled) {
var indexInSelection = mainItem.selectedContacts.indexOf(modelData.core.defaultAddress) var indexInSelection = mainItem.selectedContacts.indexOf(searchResultItem.core.defaultAddress)
if (indexInSelection == -1) { if (indexInSelection == -1) {
mainItem.addContactToSelection(modelData.core.defaultAddress) mainItem.addContactToSelection(searchResultItem.core.defaultAddress)
} }
else { else {
mainItem.removeContactFromSelection(indexInSelection, 1) mainItem.removeContactFromSelection(indexInSelection, 1)
} }
} }
mainItem.contactClicked(modelData) mainItem.contactClicked(searchResultItem)
} }
} }
} }
} }
}
delegate: ContactListItem{
id: contactItem
width: mainItem.width
focus: true
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 int textInputWidth: 350 * DefaultStyle.dp
property color borderColor: "transparent" property color borderColor: "transparent"
property color focusedBorderColor: DefaultStyle.main2_500main property color focusedBorderColor: DefaultStyle.main2_500main
property string text: textField.text property string text: textField.searchText
property bool magnifierVisible: true property bool magnifierVisible: true
property var validator: RegularExpressionValidator{} property var validator: RegularExpressionValidator{}
property Control.Popup numericPadPopup property Control.Popup numericPadPopup
property alias numericPadButton: dialerButton property alias numericPadButton: dialerButton
readonly property bool hasActiveFocus: textField.activeFocus readonly property bool hasActiveFocus: textField.activeFocus
property alias color: backgroundItem.color 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. signal openNumericPadRequested()// Useful for redirection before displaying numeric pad.
@ -62,6 +63,9 @@ FocusScope {
anchors.leftMargin: magnifier.visible ? 0 : 10 * DefaultStyle.dp anchors.leftMargin: magnifier.visible ? 0 : 10 * DefaultStyle.dp
anchors.right: clearTextButton.left anchors.right: clearTextButton.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
property string searchText
focus: true focus: true
placeholderText: mainItem.placeholderText placeholderText: mainItem.placeholderText
placeholderTextColor: mainItem.placeholderTextColor placeholderTextColor: mainItem.placeholderTextColor
@ -75,6 +79,7 @@ FocusScope {
color: DefaultStyle.main2_600 color: DefaultStyle.main2_600
selectByMouse: true selectByMouse: true
validator: mainItem.validator validator: mainItem.validator
onTextChanged: mainItem.delaySearch ? delayTimer.restart() : searchText = text
background: Item { background: Item {
opacity: 0. opacity: 0.
} }
@ -83,6 +88,12 @@ FocusScope {
color: DefaultStyle.main2_500main color: DefaultStyle.main2_500main
width: 1 * DefaultStyle.dp width: 1 * DefaultStyle.dp
} }
Timer{
id: delayTimer
interval: 300
repeat: false
onTriggered: textField.searchText = textField.text
}
} }
Button { Button {
id: dialerButton id: dialerButton

View file

@ -20,7 +20,6 @@ FocusScope {
signal transferCallToAnotherRequested(CallGui dest) signal transferCallToAnotherRequested(CallGui dest)
signal contactClicked(FriendGui contact) signal contactClicked(FriendGui contact)
clip: true clip: true
onVisibleChanged: if (numPadPopup.opened) numPadPopup.close()
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
@ -71,7 +70,7 @@ FocusScope {
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: 39 * DefaultStyle.dp Layout.rightMargin: 39 * DefaultStyle.dp
Layout.maximumWidth: mainItem.width //Layout.maximumWidth: mainItem.width
focus: true focus: true
color: mainItem.searchBarColor color: mainItem.searchBarColor
borderColor: mainItem.searchBarBorderColor borderColor: mainItem.searchBarBorderColor
@ -79,29 +78,9 @@ FocusScope {
numericPadPopup: mainItem.numPadPopup numericPadPopup: mainItem.numPadPopup
KeyNavigation.down: grouCallButton 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 { ColumnLayout {
id: content id: content
spacing: 32 * DefaultStyle.dp spacing: 32 * DefaultStyle.dp
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 39 * DefaultStyle.dp
Button { Button {
id: grouCallButton id: grouCallButton
visible: mainItem.groupCallVisible && !SettingsCpp.disableMeetingsFeature visible: mainItem.groupCallVisible && !SettingsCpp.disableMeetingsFeature
@ -109,7 +88,7 @@ FocusScope {
Layout.preferredHeight: 44 * DefaultStyle.dp Layout.preferredHeight: 44 * DefaultStyle.dp
padding: 0 padding: 0
KeyNavigation.up: searchBar KeyNavigation.up: searchBar
KeyNavigation.down: contactList.count >0 ? contactList : searchList KeyNavigation.down: contactLoader.item
onClicked: mainItem.groupCallCreationRequested() onClicked: mainItem.groupCallCreationRequested()
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
@ -148,60 +127,25 @@ FocusScope {
} }
} }
} }
ColumnLayout { Loader{
spacing: 18 * DefaultStyle.dp // 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.
visible: contactList.contentHeight > 0 id: contactLoader
Text { Layout.fillWidth: true
text: qsTr("Contacts") Layout.fillHeight: true
font { property string t: searchBar.text
pixelSize: 16 * DefaultStyle.dp onTChanged: {
weight: 800 * DefaultStyle.dp contactLoader.active = false
Qt.callLater(function(){contactLoader.active=true})
} }
} //-------------------------------------------------------------
ContactListView{ sourceComponent: ContactListView{
id: contactList id: contactList
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
Control.ScrollBar.vertical.visible: false
contactMenuVisible: false
searchOnInitialization: true
searchBarText: searchBar.text searchBarText: searchBar.text
onContactClicked: (contact) => { onContactClicked: (contact) => {
mainItem.contactClicked(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 id: mainItem
property string placeHolderText: qsTr("Rechercher des contacts") 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 int selectedParticipantsCount: selectedParticipants.length
property ConferenceInfoGui conferenceInfoGui property ConferenceInfoGui conferenceInfoGui
property color searchBarColor: DefaultStyle.grey_100 property color searchBarColor: DefaultStyle.grey_100
@ -30,7 +30,7 @@ FocusScope{
Layout.preferredHeight: contentHeight Layout.preferredHeight: contentHeight
Layout.maximumHeight: mainItem.height / 3 Layout.maximumHeight: mainItem.height / 3
width: mainItem.width width: mainItem.width
model: suggestionList.selectedContacts model: mainItem.selectedParticipants
clip: true clip: true
focus: participantList.count > 0 focus: participantList.count > 0
Keys.onPressed: (event) => { Keys.onPressed: (event) => {
@ -40,7 +40,7 @@ FocusScope{
} }
delegate: FocusScope { delegate: FocusScope {
height: 56 * DefaultStyle.dp height: 56 * DefaultStyle.dp
width: participantList.width - scrollbar.implicitWidth - 12 * DefaultStyle.dp width: participantList.width - scrollbar.implicitWidth - 28 * DefaultStyle.dp
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
spacing: 10 * DefaultStyle.dp spacing: 10 * DefaultStyle.dp
@ -67,7 +67,7 @@ FocusScope{
icon.height: 24 * DefaultStyle.dp icon.height: 24 * DefaultStyle.dp
focus: true focus: true
contentImageColor: DefaultStyle.main1_500_main contentImageColor: DefaultStyle.main1_500_main
onClicked: suggestionList.removeSelectedContactByAddress(modelData) onClicked: if(contactLoader.item) contactLoader.item.removeSelectedContactByAddress(modelData)
} }
} }
} }
@ -83,7 +83,7 @@ FocusScope{
} }
} }
SearchBar { SearchBar {
id: searchbar id: searchBar
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 6 * DefaultStyle.dp Layout.topMargin: 6 * DefaultStyle.dp
Layout.rightMargin: 28 * DefaultStyle.dp Layout.rightMargin: 28 * DefaultStyle.dp
@ -95,86 +95,49 @@ FocusScope{
KeyNavigation.up: participantList.count > 0 KeyNavigation.up: participantList.count > 0
? participantList ? participantList
: nextItemInFocusChain(false) : 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 { ColumnLayout {
id: content id: content
anchors.left: parent.left spacing: 15 * DefaultStyle.dp
anchors.right: parent.right
anchors.rightMargin: contactsScrollBar.implicitWidth + 12 * DefaultStyle.dp
Text { Text {
Layout.topMargin: 6 * DefaultStyle.dp visible: !contactLoader.item?.loading && contactLoader.item?.count === 0
text: qsTr("Contacts") Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 137 * DefaultStyle.dp
text: qsTr("Aucun contact%1").arg(searchBar.text.length !== 0 ? " correspondant" : "")
font { font {
pixelSize: 16 * DefaultStyle.dp pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp weight: 800 * DefaultStyle.dp
} }
} }
ContactListView { Loader{
id: contactList // 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.
visible: contentHeight > 0 || searchbar.text.length > 0 id: contactLoader
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.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredHeight: contentHeight property string t: searchBar.text
Control.ScrollBar.vertical.visible: false onTChanged: {
contactMenuVisible: false contactLoader.active = false
searchBarText: searchbar.text Qt.callLater(function(){contactLoader.active=true})
sourceFlags: LinphoneEnums.MagicSearchSource.All }
//-------------------------------------------------------------
sourceComponent: ContactListView{
id: contactList
Layout.fillWidth: true
Layout.fillHeight: true
itemsRightMargin: 28 * DefaultStyle.dp
multiSelectionEnabled: true multiSelectionEnabled: true
displayNameCapitalization: false showContactMenu: false
hideListProxy: contactList.model confInfoGui: mainItem.conferenceInfoGui
selectedContacts: mainItem.selectedParticipants
onSelectedContactsChanged: Qt.callLater(function(){mainItem.selectedParticipants = selectedContacts})
searchBarText: searchBar.text
onContactAddedToSelection: (address) => { onContactAddedToSelection: (address) => {
contactList.addContactToSelection(address) contactList.addContactToSelection(address)
participantList.positionViewAtEnd()
} }
onContactRemovedFromSelection: (address) => contactList.removeSelectedContactByAddress(address) onContactRemovedFromSelection: (address) => contactList.removeSelectedContactByAddress(address)
} }
} }
} }
// Item {
// Layout.fillHeight: true
// }
} }
} }

View file

@ -171,8 +171,8 @@ Item {
if (text.length != 0) listPopup.open() if (text.length != 0) listPopup.open()
else listPopup.close() else listPopup.close()
} }
KeyNavigation.down: contactList.count > 0 ? contactList : contactList.footerItem KeyNavigation.down: contactLoader.item?.count > 0 || !contactLoader.item?.footerItem? contactLoader.item : contactLoader.item?.footerItem
KeyNavigation.up: contactList.footerItem KeyNavigation.up: contactLoader.item?.footerItem ? contactLoader.item?.footerItem : contactLoader.item
component MagicSearchButton: Button { component MagicSearchButton: Button {
id: button id: button
@ -196,13 +196,13 @@ Item {
id: listPopup id: listPopup
width: magicSearchBar.width width: magicSearchBar.width
property int maxHeight: 400 * DefaultStyle.dp property int maxHeight: 400 * DefaultStyle.dp
property bool displayScrollbar: contactList.contentHeight + topPadding + bottomPadding> maxHeight property bool displayScrollbar: contactLoader.item?.contentHeight + topPadding + bottomPadding> maxHeight
height: Math.min(contactList.contentHeight + topPadding + bottomPadding, maxHeight) height: Math.min(contactLoader.item?.contentHeight + topPadding + bottomPadding, maxHeight)
y: magicSearchBar.height y: magicSearchBar.height
// closePolicy: Popup.NoAutoClose // closePolicy: Popup.NoAutoClose
topPadding: 20 * DefaultStyle.dp topPadding: 20 * DefaultStyle.dp
bottomPadding: 20 * DefaultStyle.dp bottomPadding: 20 * DefaultStyle.dp
rightPadding: 20 * DefaultStyle.dp rightPadding: 10 * DefaultStyle.dp
leftPadding: 20 * DefaultStyle.dp leftPadding: 20 * DefaultStyle.dp
background: Item { background: Item {
@ -213,7 +213,7 @@ Item {
color: DefaultStyle.grey_0 color: DefaultStyle.grey_0
anchors.fill: parent anchors.fill: parent
border.color: DefaultStyle.main1_500_main border.color: DefaultStyle.main1_500_main
border.width: contactList.activeFocus ? 2 : 0 border.width: contactLoader.item?.activeFocus ? 2 : 0
} }
MultiEffect { MultiEffect {
@ -237,20 +237,39 @@ Item {
} }
} }
contentItem: ContactListView { 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.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})
}
//-------------------------------------------------------------
sourceComponent: ContactListView {
id: contactList id: contactList
visible: magicSearchBar.text.length != 0 visible: magicSearchBar.text.length != 0
Layout.preferredHeight: contentHeight Layout.preferredHeight: item?.contentHeight
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: 5 * DefaultStyle.dp itemsRightMargin: 5 * DefaultStyle.dp //(Actions have already 10 of margin)
initialHeadersVisible: false showInitials: false
contactMenuVisible: false showContactMenu: false
actionLayoutVisible: true showActions: true
showFavorites: false
selectionEnabled: false selectionEnabled: false
showDefaultAddress: true showDefaultAddress: true
showLdapContacts: true hideSuggestions: true
sectionsPixelSize: 13 * DefaultStyle.dp
sectionsWeight: 700 * DefaultStyle.dp
sectionsSpacing: 5 * DefaultStyle.dp
Control.ScrollBar.vertical: scrollbar Control.ScrollBar.vertical: scrollbar
searchText: magicSearchBar.text searchBarText: magicSearchBar.text
Keys.onPressed: (event) => { Keys.onPressed: (event) => {
if(event.key == Qt.Key_Down){ if(event.key == Qt.Key_Down){
@ -267,15 +286,7 @@ Item {
} }
} }
} }
header: Text {
visible: contactList.count > 0
text: qsTr("Contact")
color: DefaultStyle.main2_500main
font {
pixelSize: 13 * DefaultStyle.dp
weight: 700 * DefaultStyle.dp
}
}
footer: FocusScope{ footer: FocusScope{
id: suggestionFocusScope id: suggestionFocusScope
width: contactList.width width: contactList.width
@ -292,7 +303,8 @@ Item {
ColumnLayout { ColumnLayout {
id: content id: content
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 5 * DefaultStyle.dp anchors.leftMargin: 5 * DefaultStyle.dp
anchors.rightMargin: 15 * DefaultStyle.dp
spacing: 10 * DefaultStyle.dp spacing: 10 * DefaultStyle.dp
Text { Text {
@ -363,6 +375,7 @@ Item {
} }
} }
} }
}
RowLayout { RowLayout {
spacing: 10 * DefaultStyle.dp spacing: 10 * DefaultStyle.dp
PopupButton { PopupButton {

View file

@ -55,7 +55,7 @@ AbstractMainPage {
onNoItemButtonPressed: goToNewCall() 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() { function goToNewCall() {
if (listStackView.currentItem && listStackView.currentItem.objectName != "newCallItem") listStackView.push(newCallItem) if (listStackView.currentItem && listStackView.currentItem.objectName != "newCallItem") listStackView.push(newCallItem)

View file

@ -20,8 +20,7 @@ AbstractMainPage {
onVisibleChanged: if (!visible) { onVisibleChanged: if (!visible) {
rightPanelStackView.clear() rightPanelStackView.clear()
contactList.currentIndex = -1 if(contactLoader.item) contactLoader.item.currentIndex = -1
favoriteList.currentIndex = -1
} }
onSelectedContactChanged: { onSelectedContactChanged: {
@ -52,12 +51,7 @@ AbstractMainPage {
// rightPanelStackView.initialItem: contactDetail // 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) { function deleteContact(contact) {
if (!contact) return if (!contact) return
@ -211,33 +205,20 @@ AbstractMainPage {
spacing: 38 * DefaultStyle.dp spacing: 38 * DefaultStyle.dp
SearchBar { SearchBar {
id: 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.leftMargin: leftPanel.leftMargin
Layout.rightMargin: leftPanel.rightMargin Layout.rightMargin: leftPanel.rightMargin
Layout.topMargin: 18 * DefaultStyle.dp Layout.topMargin: 18 * DefaultStyle.dp
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: qsTr("Rechercher un contact") placeholderText: qsTr("Rechercher un contact")
KeyNavigation.up: createContactButton 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
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout { ColumnLayout {
id: content id: content
width: parent.width
spacing: 15 * DefaultStyle.dp spacing: 15 * DefaultStyle.dp
Text { Text {
id: leftPanelNoItemText visible: contactLoader.item && !contactLoader.item.loading && contactLoader.item.count === 0
visible: contactList.count === 0
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 137 * DefaultStyle.dp Layout.topMargin: 137 * DefaultStyle.dp
text: qsTr("Aucun contact%1").arg(searchBar.text.length !== 0 ? " correspondant" : "") text: qsTr("Aucun contact%1").arg(searchBar.text.length !== 0 ? " correspondant" : "")
@ -246,134 +227,37 @@ AbstractMainPage {
weight: 800 * DefaultStyle.dp weight: 800 * DefaultStyle.dp
} }
} }
ColumnLayout { Loader{
visible: favoriteList.contentHeight > 0 // 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.
onVisibleChanged: if (visible && !favoriteList.visible) favoriteList.visible = true id: contactLoader
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 Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 45 * DefaultStyle.dp
property string t: searchBar.text
active: leftPanel.visible
onTChanged: {
contactLoader.active = false
Qt.callLater(function(){contactLoader.active=true})
} }
Button { //-------------------------------------------------------------
id: favoriteExpandButton sourceComponent: ContactListView{
background: Item{} id: contactList
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 searchBarText: searchBar.text
listProxy: allFriends hideSuggestions: true
sourceFlags: LinphoneEnums.MagicSearchSource.Friends | LinphoneEnums.MagicSearchSource.FavoriteFriends | LinphoneEnums.MagicSearchSource.LdapServers
onSelectedContactChanged: { onSelectedContactChanged: {
if (selectedContact) {
contactList.currentIndex = -1
}
mainItem.selectedContact = selectedContact mainItem.selectedContact = selectedContact
} }
onContactDeletionRequested: (contact) => { onContactDeletionRequested: (contact) => {
mainItem.deleteContact(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: { onCountChanged: {
if (initialFriendToDisplay.length !== 0) { if (initialFriendToDisplay.length !== 0) {
if (selectContact(initialFriendToDisplay) != -1) initialFriendToDisplay = "" 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
}
}
}
}
}
}
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 spacing: 0
Text { Text {
text: contactDetail.contactName text: contactDetail.contactName
Layout.fillWidth: true
maximumLineCount: 1
font { font {
pixelSize: 29 * DefaultStyle.dp pixelSize: 29 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp weight: 800 * DefaultStyle.dp
@ -579,6 +461,7 @@ AbstractMainPage {
model: mainItem.selectedContact ? mainItem.selectedContact.core.allAddresses : [] model: mainItem.selectedContact ? mainItem.selectedContact.core.allAddresses : []
} }
delegate: Item { delegate: Item {
property var listViewModelData: modelData
width: addrList.width width: addrList.width
height: 46 * DefaultStyle.dp height: 46 * DefaultStyle.dp
@ -595,7 +478,7 @@ AbstractMainPage {
Layout.fillWidth: true Layout.fillWidth: true
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: modelData.label text: listViewModelData.label
font { font {
pixelSize: 13 * DefaultStyle.dp pixelSize: 13 * DefaultStyle.dp
weight: 700 * DefaultStyle.dp weight: 700 * DefaultStyle.dp
@ -603,7 +486,7 @@ AbstractMainPage {
} }
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
property string _text: modelData.address property string _text: listViewModelData.address
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(_text) : _text text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(_text) : _text
font { font {
pixelSize: 14 * DefaultStyle.dp pixelSize: 14 * DefaultStyle.dp
@ -624,7 +507,7 @@ AbstractMainPage {
icon.width: 24 * DefaultStyle.dp icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp icon.height: 24 * DefaultStyle.dp
onClicked: { onClicked: {
UtilsCpp.createCall(modelData.address) UtilsCpp.createCall(listViewModelData.address)
} }
} }
} }
@ -761,16 +644,17 @@ AbstractMainPage {
id: deviceDelegate id: deviceDelegate
width: deviceList.width width: deviceList.width
height: 30 * DefaultStyle.dp height: 30 * DefaultStyle.dp
property var listViewModelData: modelData
property var callObj property var callObj
property CallGui deviceCall: callObj ? callObj.value : null 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 {
text: deviceDelegate.deviceName text: deviceDelegate.deviceName
font.pixelSize: 14 * DefaultStyle.dp font.pixelSize: 14 * DefaultStyle.dp
} }
Item{Layout.fillWidth: true} Item{Layout.fillWidth: true}
Image{ Image{
visible: modelData.securityLevel === LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified visible: listViewModelData.securityLevel === LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified
source: AppIcons.trusted source: AppIcons.trusted
width: 22 * DefaultStyle.dp width: 22 * DefaultStyle.dp
height: 22 * DefaultStyle.dp height: 22 * DefaultStyle.dp
@ -778,7 +662,7 @@ AbstractMainPage {
Button { Button {
Layout.preferredHeight: 30 * DefaultStyle.dp Layout.preferredHeight: 30 * DefaultStyle.dp
visible: modelData.securityLevel != LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified visible: listViewModelData.securityLevel != LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified
color: DefaultStyle.main1_100 color: DefaultStyle.main1_100
icon.source: AppIcons.warningCircle icon.source: AppIcons.warningCircle
icon.height: 14 * DefaultStyle.dp icon.height: 14 * DefaultStyle.dp
@ -794,12 +678,12 @@ AbstractMainPage {
onClicked: { onClicked: {
if (SettingsCpp.getDisplayDeviceCheckConfirmation()) { if (SettingsCpp.getDisplayDeviceCheckConfirmation()) {
verifyDevicePopup.deviceName = deviceDelegate.deviceName verifyDevicePopup.deviceName = deviceDelegate.deviceName
verifyDevicePopup.deviceAddress = modelData.address verifyDevicePopup.deviceAddress = listViewModelData.address
verifyDevicePopup.open() verifyDevicePopup.open()
} }
else { else {
UtilsCpp.createCall(modelData.address, {}, LinphoneEnums.MediaEncryption.Zrtp) UtilsCpp.createCall(listViewModelData.address, {}, LinphoneEnums.MediaEncryption.Zrtp)
parent.callObj = UtilsCpp.getCallByAddress(modelData.address) parent.callObj = UtilsCpp.getCallByAddress(listViewModelData.address)
} }
} }
} }