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->makeConnectToModel(&MagicSearchModel::sourceFlagsChanged, [this](int flags) { mModelConnection->invokeToModel([this, filter, sourceFlags, aggregationFlag, maxResults]() {
mModelConnection->invokeToCore([this, flags]() { setSourceFlags(flags); }); mMagicSearch->search(filter, sourceFlags, aggregationFlag, maxResults);
}); });
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;
emit parentProxyChanged(); }
void MagicSearchProxy::setHideSuggestions(bool data) {
auto list = dynamic_cast<SortFilterList *>(sourceModel());
if (list->mHideSuggestions != data) {
list->mHideSuggestions = data;
list->invalidate();
emit hideSuggestionsChanged();
}
}
MagicSearchProxy *MagicSearchProxy::getParentProxy() const {
return mParentProxy;
}
void MagicSearchProxy::setParentProxy(MagicSearchProxy *parentProxy) {
if (parentProxy && parentProxy->mList) {
mParentProxy = parentProxy;
setList(parentProxy->mList);
emit parentProxyChanged();
}
} }
MagicSearchProxy *MagicSearchProxy::getHideListProxy() const { 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,52 +7,56 @@ 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)
@ -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) {
@ -99,33 +169,7 @@ ListView {
} }
} }
} }
// strange behaviour with this lines
// When a popup opens after clicking on a contact, the selected contact
// changes because we lose focus on the list
// onActiveFocusChanged: if(activeFocus && (!footerItem || !footerItem.activeFocus)) {
// currentIndex = 0
// }
model: MagicSearchProxy {
id: magicSearchProxy
searchText: mainItem.searchText
// This property is needed instead of playing on the delegate visibility
// considering its starred status. Otherwise, the row in the list still
// exists even if its delegate is not visible, and creates navigation issues
showFavoritesOnly: mainItem.showFavoritesOnly
aggregationFlag: mainItem.aggregationFlag
parentProxy: mainItem.listProxy
showLdapContacts: mainItem.showLdapContacts
sourceFlags: mainItem.sourceFlags
onFriendCreated: (index) => {
mainItem.currentIndex = index
}
onInitialized: {
if(mainItem.searchOnInitialization) magicSearchProxy.forceUpdate()
}
}
Connections { 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()
}
} }
onInitialized: {
mainItem.loading = true
magicSearchProxy.forceUpdate()
}
onModelReset: mainItem.resultsReceived()
} }
delegate: FocusScope {
id: itemDelegate section.property: "isStored"
height: 56 * DefaultStyle.dp //section.criteria: ViewSection.FirstCharacter
width: mainItem.width section.delegate: Item{
property var previousItem : mainItem.model.count > 0 && index > 0 ? mainItem.model.getAt(index-1) : null width: mainItem.width
property var previousDisplayName: previousItem ? previousItem.core.displayName : "" height: textItem.implicitHeight + sectionsSpacing * 2
property var displayName: modelData.core.displayName required property bool section
Text {
Connections { id: textItem
enabled: modelData.core anchors.fill: parent
target: modelData.core text: section ? qsTr("Contacts") : qsTr("Suggestions")
function onStarredChanged() { mainItem.contactStarredChanged()} horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
font {
pixelSize: sectionsPixelSize
weight: sectionsWeight
}
}
} }
Text { header: FocusScope{
id: initial id: headerItem
anchors.left: parent.left width: mainItem.width
visible: mainItem.initialHeadersVisible && mainItem.model.sourceFlags != LinphoneEnums.MagicSearchSource.All height: favoritesContents.implicitHeight
anchors.verticalCenter: parent.verticalCenter property alias list: favoriteList
anchors.rightMargin: 15 * DefaultStyle.dp
verticalAlignment: Text.AlignVCenter // Hack because changing currentindex change focus.
width: 20 * DefaultStyle.dp Timer{
opacity: (!previousItem || !previousDisplayName.toLocaleLowerCase(ConstantsCpp.DefaultLocale).startsWith(displayName[0].toLocaleLowerCase(ConstantsCpp.DefaultLocale))) ? 1 : 0 id: focusDelay
text: displayName[0] interval: 10
color: DefaultStyle.main2_400 onTriggered: {
font { mainItem.highlightFollowsCurrentItem = !headerItem.activeFocus
pixelSize: 20 * DefaultStyle.dp
weight: 500 * DefaultStyle.dp
capitalization: Font.AllUppercase
}
}
RowLayout {
id: contactDelegate
anchors.left: initial.visible ? initial.right : parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
spacing: 16 * DefaultStyle.dp
z: 1
Avatar {
Layout.leftMargin: 5 * DefaultStyle.dp
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
contact: modelData
}
ColumnLayout {
spacing: 0
Text {
text: UtilsCpp.boldTextPart(itemDelegate.displayName, mainItem.searchBarText)
font{
pixelSize: mainItem.showDefaultAddress ? 16 * DefaultStyle.dp : 14 * DefaultStyle.dp
capitalization: mainItem.displayNameCapitalization ? Font.Capitalize : Font.MixedCase
weight: mainItem.showDefaultAddress ? 800 * DefaultStyle.dp : 400 * DefaultStyle.dp
}
maximumLineCount: 1
Layout.fillWidth: true
}
Text {
maximumLineCount: 1
visible: mainItem.showDefaultAddress
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(modelData.core.defaultAddress) : modelData.core.defaultAddress
Layout.fillWidth: true
Layout.topMargin: 2 * DefaultStyle.dp
font {
weight: 300 * DefaultStyle.dp
pixelSize: 12 * DefaultStyle.dp
} }
} }
} onActiveFocusChanged:focusDelay.restart()
Item{Layout.fillWidth: true} //---------------------------------------------------
RowLayout {
id: actionsRow function updatePosition(){
z: 1 var item = favoriteList.itemAtIndex(favoriteList.currentIndex)
visible: actionButtons || friendPopup.visible || mainItem.multiSelectionEnabled if( item){
spacing: visible ? 16 * DefaultStyle.dp : 0 // For debugging just in case
EffectImage { //var listPosition = item.mapToItem(favoriteList, item.x, item.y)
id: isSelectedCheck //var newPosition = favoriteList.mapToItem(mainItem, listPosition.x, listPosition.y)
// visible: mainItem.multiSelectionEnabled && (mainItem.confInfoGui.core.getParticipantIndex(modelData.core.defaultAddress) != -1) //console.log("item pos: " +item.x + " / " +item.y)
visible: mainItem.multiSelectionEnabled && (mainItem.selectedContacts.indexOf(modelData.core.defaultAddress) != -1) //console.log("fav pos: " +favoriteList.x + " / " +favoriteList.y)
Layout.preferredWidth: 24 * DefaultStyle.dp //console.log("fav content: " +favoriteList.contentX + " / " +favoriteList.contentY)
Layout.preferredHeight: 24 * DefaultStyle.dp //console.log("main pos: " +mainItem.x + " / " +mainItem.y)
imageSource: AppIcons.check //console.log("main content: " +mainItem.contentX + " / " +mainItem.contentY)
colorizationColor: DefaultStyle.main1_500_main //console.log("list pos: " +listPosition.x + " / " +listPosition.y)
Connections { //console.log("new pos: " +newPosition.x + " / " +newPosition.y)
target: mainItem //console.log("header pos: " +headerItem.x + " / " +headerItem.y)
// onParticipantsChanged: isSelectedCheck.visible = mainItem.confInfoGui.core.getParticipantIndex(modelData.core.defaultAddress) != -1 //console.log("Moving to " + (headerItem.y+item.y))
function onSelectedContactCountChanged(){ isSelectedCheck.visible = (mainItem.selectedContacts.indexOf(modelData.core.defaultAddress) != -1)} mainItem.contentY = headerItem.y+item.y
} }
}
RowLayout{
id: actionButtons
visible: mainItem.actionLayoutVisible
spacing: visible ? 10 * DefaultStyle.dp : 0
Button {
id: callButton
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
icon.source: AppIcons.phone
contentImageColor: DefaultStyle.main2_600
focus: visible
background: Rectangle {
anchors.fill: parent
radius: 40 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
onClicked: UtilsCpp.createCall(modelData.core.defaultAddress)
KeyNavigation.right: chatButton
KeyNavigation.left: chatButton
}
Button {
id: chatButton
visible: actionButtons.visible && !SettingsCpp.disableChatFeature
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
icon.source: AppIcons.chatTeardropText
focus: visible && !callButton.visible
contentImageColor: DefaultStyle.main2_500main
background: Rectangle {
anchors.fill: parent
radius: 40 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
KeyNavigation.right: callButton
KeyNavigation.left: callButton
}
}
PopupButton {
id: friendPopup
z: 1
// Layout.rightMargin: 13 * DefaultStyle.dp
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: 8 * DefaultStyle.dp
popup.x: 0
popup.padding: 10 * DefaultStyle.dp
hoverEnabled: mainItem.hoverEnabled
visible: mainItem.contactMenuVisible && (contactArea.containsMouse || hovered || popup.opened) && (!mainItem.delegateButtons || mainItem.delegateButtons.length === 0)
popup.contentItem: ColumnLayout { }
Button {
text: $modelData.core.starred ? qsTr("Enlever des favoris") : qsTr("Mettre en favori") ColumnLayout {
background: Item{} id: favoritesContents
icon.source: modelData.core.starred ? AppIcons.heartFill : AppIcons.heart width: parent.width
icon.width: 24 * DefaultStyle.dp spacing: mainItem.haveFavorites ? sectionsSpacing : 0
icon.height: 24 * DefaultStyle.dp BusyIndicator {
spacing: 10 * DefaultStyle.dp Layout.alignment: Qt.AlignCenter
textSize: 14 * DefaultStyle.dp Layout.preferredHeight: visible ? 60 * DefaultStyle.dp : 0
textWeight: 400 * DefaultStyle.dp Layout.preferredWidth: 60 * DefaultStyle.dp
textColor: DefaultStyle.main2_500main indicatorHeight: 60 * DefaultStyle.dp
contentImageColor: modelData.core.starred ? DefaultStyle.danger_500main : DefaultStyle.main2_600 indicatorWidth: 60 * DefaultStyle.dp
onClicked: { visible: mainItem.loading
modelData.core.lSetStarred(!modelData.core.starred) indicatorColor: DefaultStyle.main1_500_main
friendPopup.close()
}
Item{// Do not use directly RowLayout : there is an issue where the layout doesn't update on visible
Layout.fillWidth: true
Layout.preferredHeight: mainItem.haveFavorites ? favoriteTitle.implicitHeight : 0
RowLayout {
id: favoriteTitle
anchors.fill: parent
spacing: 0
// Need this because it can stay at 0 on display without manual relayouting (moving position, resize)
visible: mainItem.haveFavorites
onVisibleChanged: if(visible) {
Qt.callLater(mainItem.positionViewAtBeginning)// If not later, the view will not move to favoris at startup
}
Text {
//Layout.fillHeight: true
text: qsTr("Favoris")
font {
pixelSize: sectionsPixelSize
weight: sectionsWeight
}
}
Item {
Layout.fillWidth: true
}
Button {
id: favoriteExpandButton
background: Item{}
icon.source: favoriteList.visible ? AppIcons.upArrow : AppIcons.downArrow
Layout.fillHeight: true
Layout.preferredWidth: height
//Layout.preferredWidth: 24 * DefaultStyle.dp
//Layout.preferredHeight: 24 * DefaultStyle.dp
Layout.rightMargin: 23 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
focus: true
onClicked: favoriteList.visible = !favoriteList.visible
KeyNavigation.down: favoriteList
} }
} }
Button { }
text: qsTr("Partager") ListView{
background: Item{} id: favoriteList
icon.source: AppIcons.shareNetwork Layout.fillWidth: true
icon.width: 24 * DefaultStyle.dp Layout.preferredHeight: count > 0 ? contentHeight : 0// Show full and avoid scrolling
icon.height: 24 * DefaultStyle.dp
spacing: 10 * DefaultStyle.dp
textSize: 14 * DefaultStyle.dp
textWeight: 400 * DefaultStyle.dp onCountChanged: mainItem.haveFavorites = count > 0
textColor: DefaultStyle.main2_500main Keys.onPressed: (event)=> {
onClicked: { if(event.key == Qt.Key_Up || event.key == Qt.Key_Down) {
var vcard = modelData.core.getVCard() if (favoriteList.currentIndex == 0 && event.key == Qt.Key_Up) {
var username = modelData.core.givenName + modelData.core.familyName if( mainItem.count > 0) {
var filepath = UtilsCpp.createVCardFile(username, vcard) mainItem.highlightFollowsCurrentItem = true
if (filepath == "") UtilsCpp.showInformationPopup(qsTr("Erreur"), qsTr("La création du fichier vcard a échoué"), false) favoriteList.currentIndex = -1
else mainWindow.showInformationPopup(qsTr("VCard créée"), qsTr("VCard du contact enregistrée dans %1").arg(filepath)) mainItem.currentIndex = mainItem.count-1
UtilsCpp.shareByEmail(qsTr("Partage de contact"), vcard, filepath) var item = mainItem.itemAtIndex(mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}else{
favoriteList.currentIndex = favoriteList.count - 1
var item = itemAtIndex(favoriteList.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}
}else if(currentIndex >= favoriteList.count -1 && event.key == Qt.Key_Down) {
if( mainItem.count > 0) {
mainItem.highlightFollowsCurrentItem = true
favoriteList.currentIndex = -1
mainItem.currentIndex = 0
var item = mainItem.itemAtIndex(mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}else{
favoriteList.currentIndex = 0
var item = itemAtIndex(favoriteList.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}
}else if(event.key == Qt.Key_Up){
mainItem.highlightFollowsCurrentItem = false
var item = itemAtIndex(--favoriteList.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
headerItem.updatePosition()
event.accepted = true;
}else if(event.key == Qt.Key_Down){
mainItem.highlightFollowsCurrentItem = false
var item = itemAtIndex(++favoriteList.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
headerItem.updatePosition()
event.accepted = true;
}
} }
} }
Button { property MagicSearchProxy proxy: MagicSearchProxy{
text: qsTr("Supprimer") parentProxy: mainItem.model
background: Item{} showFavoritesOnly: true
icon.source: AppIcons.trashCan hideSuggestions: mainItem.hideSuggestions
icon.width: 24 * DefaultStyle.dp }
icon.height: 24 * DefaultStyle.dp model : showFavorites && mainItem.searchBarText == '' ? proxy : []
spacing: 10 * DefaultStyle.dp delegate: ContactListItem{
textSize: 14 * DefaultStyle.dp width: favoriteList.width
textWeight: 400 * DefaultStyle.dp focus: true
textColor: DefaultStyle.danger_500main
contentImageColor: DefaultStyle.danger_500main searchResultItem: $modelData
visible: !modelData.core.readOnly showInitials: mainItem.showInitials
onClicked: { showDefaultAddress: mainItem.showDefaultAddress
mainItem.contactDeletionRequested(modelData) showActions: mainItem.showActions
friendPopup.close() showContactMenu: mainItem.showContactMenu
highlightText: mainItem.highlightText
displayNameCapitalization: mainItem.displayNameCapitalization
itemsRightMargin: mainItem.itemsRightMargin
selectionEnabled: mainItem.selectionEnabled
multiSelectionEnabled: mainItem.multiSelectionEnabled
selectedContacts: mainItem.selectedContacts
isSelected: mainItem.selectedContact && mainItem.selectedContact.core == searchResultItem.core
previousInitial: ''//favoriteList.count > 0 ? favoriteList.itemAtIndex(index-1)?.initial : '' // Binding on count
initial: '' // Hide initials but keep space
onIsSelectedChanged: if(isSelected) favoriteList.currentIndex = index
onContactStarredChanged: mainItem.contactStarredChanged()
onContactDeletionRequested: (contact) => mainItem.contactDeletionRequested(contact)
onClicked: (mouse) => {
mainItem.highlightFollowsCurrentItem = false
favoriteList.currentIndex = index
mainItem.selectedContact = searchResultItem
forceActiveFocus()
headerItem.updatePosition()
if (mainItem.multiSelectionEnabled) {
var indexInSelection = mainItem.selectedContacts.indexOf(searchResultItem.core.defaultAddress)
if (indexInSelection == -1) {
mainItem.addContactToSelection(searchResultItem.core.defaultAddress)
}
else {
mainItem.removeContactFromSelection(indexInSelection, 1)
}
}
mainItem.contactClicked(searchResultItem)
} }
} }
} }
} }
} }
}
delegate: ContactListItem{
id: contactItem
width: mainItem.width
focus: true
MouseArea { searchResultItem: $modelData
id: contactArea showInitials: mainItem.showInitials && searchResultItem.core.isStored
enabled: mainItem.selectionEnabled showDefaultAddress: mainItem.showDefaultAddress
hoverEnabled: mainItem.hoverEnabled showActions: mainItem.showActions
anchors.fill: contactDelegate showContactMenu: searchResultItem.core.isStored
height: mainItem.height highlightText: mainItem.highlightText
acceptedButtons: Qt.AllButtons
z: -1 displayNameCapitalization: mainItem.displayNameCapitalization
focus: !actionButtons.visible itemsRightMargin: mainItem.itemsRightMargin
Rectangle {
anchors.fill: contactArea selectionEnabled: mainItem.selectionEnabled
opacity: 0.7 multiSelectionEnabled: mainItem.multiSelectionEnabled
radius: 8 * DefaultStyle.dp selectedContacts: mainItem.selectedContacts
color: mainItem.currentIndex === index ? DefaultStyle.main2_200 : DefaultStyle.main2_100 isSelected: mainItem.selectedContact && mainItem.selectedContact.core == searchResultItem.core
visible: contactArea.containsMouse || friendPopup.hovered || mainItem.currentIndex === index previousInitial: mainItem.itemAtIndex(index-1)?.initial
}
Keys.onPressed: (event)=> { onIsSelectedChanged: if(isSelected) mainItem.currentIndex = index
if (event.key == Qt.Key_Space || event.key == Qt.Key_Enter || event.key == Qt.Key_Return) { onContactStarredChanged: mainItem.contactStarredChanged()
contactArea.clicked(undefined) onContactDeletionRequested: (contact) => mainItem.contactDeletionRequested(contact)
event.accepted = true; onClicked: (mouse) => {
} mainItem.highlightFollowsCurrentItem = true
} if (mouse && mouse.button == Qt.RightButton) {
onClicked: (mouse) => { friendPopup.open()
if (mouse && mouse.button == Qt.RightButton) { } else {
friendPopup.open() forceActiveFocus()
} else { if(mainItem.selectedContact && mainItem.selectedContact.core != contactItem.searchResultItem.core)
mainItem.forceActiveFocus() headerItem.list.currentIndex = -1
mainItem.currentIndex = index mainItem.selectedContact = contactItem.searchResultItem
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)
} }
}
} }
} }
} }

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,127 +78,72 @@ FocusScope {
numericPadPopup: mainItem.numPadPopup numericPadPopup: mainItem.numPadPopup
KeyNavigation.down: grouCallButton KeyNavigation.down: grouCallButton
} }
Flickable { ColumnLayout {
Layout.fillWidth: true id: content
Layout.fillHeight: true spacing: 32 * DefaultStyle.dp
Layout.topMargin: 25 * DefaultStyle.dp Button {
contentWidth: width id: grouCallButton
contentHeight: content.height visible: mainItem.groupCallVisible && !SettingsCpp.disableMeetingsFeature
clip: true Layout.preferredWidth: 320 * DefaultStyle.dp
Control.ScrollBar.vertical: ScrollBar { Layout.preferredHeight: 44 * DefaultStyle.dp
active: true padding: 0
interactive: true KeyNavigation.up: searchBar
policy: Control.ScrollBar.AsNeeded KeyNavigation.down: contactLoader.item
anchors.top: parent.top onClicked: mainItem.groupCallCreationRequested()
anchors.bottom: parent.bottom background: Rectangle {
anchors.right: parent.right anchors.fill: parent
anchors.rightMargin: 8 * DefaultStyle.dp radius: 50 * DefaultStyle.dp
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: DefaultStyle.main2_100}
GradientStop { position: 1.0; color: DefaultStyle.grey_0}
}
}
contentItem: RowLayout {
spacing: 16 * DefaultStyle.dp
anchors.verticalCenter: parent.verticalCenter
Image {
source: AppIcons.groupCall
Layout.preferredWidth: 44 * DefaultStyle.dp
sourceSize.width: 44 * DefaultStyle.dp
fillMode: Image.PreserveAspectFit
}
Text {
text: "Appel de groupe"
color: DefaultStyle.grey_1000
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
underline: grouCallButton.shadowEnabled
}
}
Item {
Layout.fillWidth: true
}
Image {
source: AppIcons.rightArrow
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
}
}
} }
Loader{
ColumnLayout { // 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: content id: contactLoader
spacing: 32 * DefaultStyle.dp Layout.fillWidth: true
anchors.left: parent.left Layout.fillHeight: true
anchors.right: parent.right property string t: searchBar.text
anchors.rightMargin: 39 * DefaultStyle.dp onTChanged: {
Button { contactLoader.active = false
id: grouCallButton Qt.callLater(function(){contactLoader.active=true})
visible: mainItem.groupCallVisible && !SettingsCpp.disableMeetingsFeature
Layout.preferredWidth: 320 * DefaultStyle.dp
Layout.preferredHeight: 44 * DefaultStyle.dp
padding: 0
KeyNavigation.up: searchBar
KeyNavigation.down: contactList.count >0 ? contactList : searchList
onClicked: mainItem.groupCallCreationRequested()
background: Rectangle {
anchors.fill: parent
radius: 50 * DefaultStyle.dp
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: DefaultStyle.main2_100}
GradientStop { position: 1.0; color: DefaultStyle.grey_0}
}
}
contentItem: RowLayout {
spacing: 16 * DefaultStyle.dp
anchors.verticalCenter: parent.verticalCenter
Image {
source: AppIcons.groupCall
Layout.preferredWidth: 44 * DefaultStyle.dp
sourceSize.width: 44 * DefaultStyle.dp
fillMode: Image.PreserveAspectFit
}
Text {
text: "Appel de groupe"
color: DefaultStyle.grey_1000
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
underline: grouCallButton.shadowEnabled
}
}
Item {
Layout.fillWidth: true
}
Image {
source: AppIcons.rightArrow
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
}
}
} }
ColumnLayout { //-------------------------------------------------------------
spacing: 18 * DefaultStyle.dp sourceComponent: ContactListView{
visible: contactList.contentHeight > 0 id: contactList
Text { searchBarText: searchBar.text
text: qsTr("Contacts") onContactClicked: (contact) => {
font { mainItem.contactClicked(contact)
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
} }
ContactListView{
id: contactList
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
Control.ScrollBar.vertical.visible: false
contactMenuVisible: false
searchOnInitialization: true
searchBarText: searchBar.text
onContactClicked: (contact) => {
mainItem.contactClicked(contact)
}
}
}
ColumnLayout {
spacing: 18 * DefaultStyle.dp
visible: searchList.count > 0
Text {
text: qsTr("Suggestions")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
ContactListView{
id: searchList
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: contentHeight
contactMenuVisible: false
Control.ScrollBar.vertical.visible: false
initialHeadersVisible: false
displayNameCapitalization: false
searchBarText: searchBar.text
sourceFlags: LinphoneEnums.MagicSearchSource.All
hideListProxy: contactList.model
onContactClicked: (contact) => {
mainItem.contactClicked(contact)
}
}
}
Item {
Layout.fillHeight: true
} }
} }
} }

View file

@ -10,7 +10,7 @@ FocusScope{
id: mainItem 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 { ColumnLayout {
Layout.fillWidth: true id: content
Layout.fillHeight: true spacing: 15 * DefaultStyle.dp
contentWidth: width Text {
contentHeight: content.height visible: !contactLoader.item?.loading && contactLoader.item?.count === 0
clip: true Layout.alignment: Qt.AlignHCenter
Control.ScrollBar.vertical: ScrollBar { Layout.topMargin: 137 * DefaultStyle.dp
id: contactsScrollBar text: qsTr("Aucun contact%1").arg(searchBar.text.length !== 0 ? " correspondant" : "")
active: true font {
interactive: true pixelSize: 16 * DefaultStyle.dp
policy: Control.ScrollBar.AsNeeded weight: 800 * DefaultStyle.dp
anchors.top: parent.top }
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.rightMargin: 8 * DefaultStyle.dp
} }
ColumnLayout { Loader{
id: content // 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.
anchors.left: parent.left id: contactLoader
anchors.right: parent.right Layout.fillWidth: true
anchors.rightMargin: contactsScrollBar.implicitWidth + 12 * DefaultStyle.dp Layout.fillHeight: true
Text { property string t: searchBar.text
Layout.topMargin: 6 * DefaultStyle.dp onTChanged: {
text: qsTr("Contacts") contactLoader.active = false
font { Qt.callLater(function(){contactLoader.active=true})
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
} }
ContactListView { //-------------------------------------------------------------
sourceComponent: ContactListView{
id: contactList id: contactList
visible: contentHeight > 0 || searchbar.text.length > 0
Layout.fillWidth: true
// Layout.fillHeight: true
Layout.topMargin: 8 * DefaultStyle.dp
Layout.preferredHeight: contentHeight
multiSelectionEnabled: true
contactMenuVisible: false
confInfoGui: mainItem.conferenceInfoGui
searchBarText: searchbar.text
searchOnInitialization: true
onContactAddedToSelection: (address) => {
suggestionList.addContactToSelection(address)
}
onContactRemovedFromSelection: (address) => suggestionList.removeSelectedContactByAddress(address)
Control.ScrollBar.vertical.visible: false
}
Text {
Layout.topMargin: 6 * DefaultStyle.dp
text: qsTr("Suggestions")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
ContactListView {
id: suggestionList
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredHeight: contentHeight itemsRightMargin: 28 * DefaultStyle.dp
Control.ScrollBar.vertical.visible: false
contactMenuVisible: false
searchBarText: searchbar.text
sourceFlags: LinphoneEnums.MagicSearchSource.All
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,125 +237,138 @@ Item {
} }
} }
contentItem: ContactListView { contentItem: 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: magicSearchBar.text.length != 0 id: contactLoader
Layout.preferredHeight: contentHeight
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: 5 * DefaultStyle.dp Layout.fillHeight: true
initialHeadersVisible: false property bool deactivate: false
contactMenuVisible: false active: !deactivate && magicSearchBar.text != ''
actionLayoutVisible: true property string t: magicSearchBar.text
selectionEnabled: false onTChanged: {
showDefaultAddress: true contactLoader.deactivate = true
showLdapContacts: true Qt.callLater(function(){contactLoader.deactivate=false})
Control.ScrollBar.vertical: scrollbar
searchText: magicSearchBar.text
Keys.onPressed: (event) => {
if(event.key == Qt.Key_Down){
if(contactList.currentIndex == contactList.count -1) {
contactList.currentIndex = -1
contactList.footerItem.forceActiveFocus()
event.accepted = true
}
} else if(event.key == Qt.Key_Up){
if(contactList.currentIndex <= 0) {
contactList.currentIndex = -1
contactList.footerItem.forceActiveFocus()
event.accepted = true
}
}
} }
header: Text { //-------------------------------------------------------------
visible: contactList.count > 0 sourceComponent: ContactListView {
text: qsTr("Contact") id: contactList
color: DefaultStyle.main2_500main visible: magicSearchBar.text.length != 0
font { Layout.preferredHeight: item?.contentHeight
pixelSize: 13 * DefaultStyle.dp Layout.fillWidth: true
weight: 700 * DefaultStyle.dp itemsRightMargin: 5 * DefaultStyle.dp //(Actions have already 10 of margin)
} showInitials: false
} showContactMenu: false
footer: FocusScope{ showActions: true
id: suggestionFocusScope showFavorites: false
width: contactList.width selectionEnabled: false
height: visible ? content.implicitHeight : 0 showDefaultAddress: true
onActiveFocusChanged: if(activeFocus) contactList.positionViewAtEnd() hideSuggestions: true
visible: !contactList.haveAddress(suggestionText.text)
Rectangle{ sectionsPixelSize: 13 * DefaultStyle.dp
anchors.left: parent.left sectionsWeight: 700 * DefaultStyle.dp
anchors.right: parent.right sectionsSpacing: 5 * DefaultStyle.dp
anchors.bottom: parent.bottom
height: suggestionRow.implicitHeight Control.ScrollBar.vertical: scrollbar
color: suggestionFocusScope.activeFocus ? DefaultStyle.numericPadPressedButtonColor : 'transparent' searchBarText: magicSearchBar.text
}
ColumnLayout { Keys.onPressed: (event) => {
id: content if(event.key == Qt.Key_Down){
anchors.fill: parent if(contactList.currentIndex == contactList.count -1) {
anchors.rightMargin: 5 * DefaultStyle.dp contactList.currentIndex = -1
contactList.footerItem.forceActiveFocus()
spacing: 10 * DefaultStyle.dp
Text {
text: qsTr("Suggestion")
color: DefaultStyle.main2_500main
font {
pixelSize: 13 * DefaultStyle.dp
weight: 700 * DefaultStyle.dp
}
}
Keys.onPressed: (event) => {
if(contactList.count <= 0) return;
if(event.key == Qt.Key_Down){
contactList.currentIndex = 0
event.accepted = true event.accepted = true
} else if(event.key == Qt.Key_Up){ }
contactList.currentIndex = contactList.count - 1 } else if(event.key == Qt.Key_Up){
if(contactList.currentIndex <= 0) {
contactList.currentIndex = -1
contactList.footerItem.forceActiveFocus()
event.accepted = true event.accepted = true
} }
} }
RowLayout { }
id: suggestionRow
spacing: 10 * DefaultStyle.dp footer: FocusScope{
id: suggestionFocusScope
width: contactList.width
height: visible ? content.implicitHeight : 0
onActiveFocusChanged: if(activeFocus) contactList.positionViewAtEnd()
visible: !contactList.haveAddress(suggestionText.text)
Rectangle{
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: suggestionRow.implicitHeight
color: suggestionFocusScope.activeFocus ? DefaultStyle.numericPadPressedButtonColor : 'transparent'
}
ColumnLayout {
id: content
anchors.fill: parent
anchors.leftMargin: 5 * DefaultStyle.dp
anchors.rightMargin: 15 * DefaultStyle.dp
Avatar { spacing: 10 * DefaultStyle.dp
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
_address: magicSearchBar.text
}
Text { Text {
id: suggestionText text: qsTr("Suggestion")
property var urlObj: UtilsCpp.interpretUrl(magicSearchBar.text) color: DefaultStyle.main2_500main
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(urlObj?.value) : urlObj?.value
font { font {
pixelSize: 12 * DefaultStyle.dp pixelSize: 13 * DefaultStyle.dp
weight: 300 * DefaultStyle.dp weight: 700 * DefaultStyle.dp
} }
} }
Item {
Layout.fillWidth: true Keys.onPressed: (event) => {
} if(contactList.count <= 0) return;
MagicSearchButton { if(event.key == Qt.Key_Down){
id: callButton contactList.currentIndex = 0
Layout.preferredWidth: 45 * DefaultStyle.dp event.accepted = true
Layout.preferredHeight: 45 * DefaultStyle.dp } else if(event.key == Qt.Key_Up){
icon.source: AppIcons.phone contactList.currentIndex = contactList.count - 1
focus: true event.accepted = true
onClicked: {
UtilsCpp.createCall(magicSearchBar.text)
magicSearchBar.clearText()
} }
KeyNavigation.right: chatButton
KeyNavigation.left: chatButton
} }
MagicSearchButton { RowLayout {
id: chatButton id: suggestionRow
visible: !SettingsCpp.disableChatFeature spacing: 10 * DefaultStyle.dp
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp Avatar {
icon.source: AppIcons.chatTeardropText Layout.preferredWidth: 45 * DefaultStyle.dp
KeyNavigation.right: callButton Layout.preferredHeight: 45 * DefaultStyle.dp
KeyNavigation.left: callButton _address: magicSearchBar.text
}
Text {
id: suggestionText
property var urlObj: UtilsCpp.interpretUrl(magicSearchBar.text)
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(urlObj?.value) : urlObj?.value
font {
pixelSize: 12 * DefaultStyle.dp
weight: 300 * DefaultStyle.dp
}
}
Item {
Layout.fillWidth: true
}
MagicSearchButton {
id: callButton
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
icon.source: AppIcons.phone
focus: true
onClicked: {
UtilsCpp.createCall(magicSearchBar.text)
magicSearchBar.clearText()
}
KeyNavigation.right: chatButton
KeyNavigation.left: chatButton
}
MagicSearchButton {
id: chatButton
visible: !SettingsCpp.disableChatFeature
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
icon.source: AppIcons.chatTeardropText
KeyNavigation.right: callButton
KeyNavigation.left: callButton
}
} }
} }
} }

View file

@ -55,7 +55,7 @@ AbstractMainPage {
onNoItemButtonPressed: goToNewCall() 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,13 +51,8 @@ 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
var mainWin = UtilsCpp.getMainWindow() var mainWin = UtilsCpp.getMainWindow()
@ -211,170 +205,60 @@ 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
} }
ColumnLayout {
RowLayout { id: content
Flickable { spacing: 15 * DefaultStyle.dp
id: listLayout Text {
contentWidth: width visible: contactLoader.item && !contactLoader.item.loading && contactLoader.item.count === 0
contentHeight: content.height Layout.alignment: Qt.AlignHCenter
clip: true Layout.topMargin: 137 * DefaultStyle.dp
Control.ScrollBar.vertical: contactsScrollbar text: qsTr("Aucun contact%1").arg(searchBar.text.length !== 0 ? " correspondant" : "")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
Loader{
// This is a hack for an incomprehensible behavior on sections title where they doesn't match with their delegate and can be unordered after resetting models.
id: contactLoader
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.leftMargin: 45 * DefaultStyle.dp
ColumnLayout { property string t: searchBar.text
id: content active: leftPanel.visible
width: parent.width onTChanged: {
spacing: 15 * DefaultStyle.dp contactLoader.active = false
Text { Qt.callLater(function(){contactLoader.active=true})
id: leftPanelNoItemText }
visible: contactList.count === 0 //-------------------------------------------------------------
Layout.alignment: Qt.AlignHCenter sourceComponent: ContactListView{
Layout.topMargin: 137 * DefaultStyle.dp id: contactList
text: qsTr("Aucun contact%1").arg(searchBar.text.length !== 0 ? " correspondant" : "") searchBarText: searchBar.text
font { hideSuggestions: true
pixelSize: 16 * DefaultStyle.dp sourceFlags: LinphoneEnums.MagicSearchSource.Friends | LinphoneEnums.MagicSearchSource.FavoriteFriends | LinphoneEnums.MagicSearchSource.LdapServers
weight: 800 * DefaultStyle.dp
} onSelectedContactChanged: {
mainItem.selectedContact = selectedContact
} }
ColumnLayout { onContactDeletionRequested: (contact) => {
visible: favoriteList.contentHeight > 0 mainItem.deleteContact(contact)
onVisibleChanged: if (visible && !favoriteList.visible) favoriteList.visible = true
Layout.leftMargin: leftPanel.leftMargin
Layout.rightMargin: leftPanel.rightMargin
spacing: 18 * DefaultStyle.dp
RowLayout {
spacing: 0
Text {
text: qsTr("Favoris")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
Item {
Layout.fillWidth: true
}
Button {
id: favoriteExpandButton
background: Item{}
icon.source: favoriteList.visible ? AppIcons.upArrow : AppIcons.downArrow
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
onClicked: favoriteList.visible = !favoriteList.visible
KeyNavigation.up: searchBar
KeyNavigation.down: favoriteList
}
}
ContactListView{
id: favoriteList
hoverEnabled: mainItem.leftPanelEnabled
highlightFollowsCurrentItem: true
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
Control.ScrollBar.vertical.visible: false
showFavoritesOnly: true
searchOnInitialization: true
contactMenuVisible: true
searchBarText: searchBar.text
listProxy: allFriends
onSelectedContactChanged: {
if (selectedContact) {
contactList.currentIndex = -1
}
mainItem.selectedContact = selectedContact
}
onContactDeletionRequested: (contact) => {
mainItem.deleteContact(contact)
}
}
} }
ColumnLayout { onCountChanged: {
visible: contactList.contentHeight > 0 if (initialFriendToDisplay.length !== 0) {
onVisibleChanged: if (visible && !contactList.visible) contactList.visible = true if (selectContact(initialFriendToDisplay) != -1) initialFriendToDisplay = ""
Layout.leftMargin: leftPanel.leftMargin
Layout.rightMargin: leftPanel.rightMargin
spacing: 16 * DefaultStyle.dp
RowLayout {
spacing: 0
Text {
text: qsTr("Contacts")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
Item {
Layout.fillWidth: true
}
Button {
id: contactExpandButton
background: Item{}
icon.source: contactList.visible ? AppIcons.upArrow : AppIcons.downArrow
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
onClicked: contactList.visible = !contactList.visible
KeyNavigation.up: favoriteList.visible ? favoriteList : searchBar
KeyNavigation.down: contactList
}
}
ContactListView{
id: contactList
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
Control.ScrollBar.vertical.visible: false
hoverEnabled: mainItem.leftPanelEnabled
contactMenuVisible: true
searchOnInitialization: true
highlightFollowsCurrentItem: true
searchBarText: searchBar.text
listProxy: allFriends
onCountChanged: {
if (initialFriendToDisplay.length !== 0) {
if (selectContact(initialFriendToDisplay) != -1) initialFriendToDisplay = ""
}
}
onSelectedContactChanged: {
if (selectedContact) {
favoriteList.currentIndex = -1
}
mainItem.selectedContact = selectedContact
}
onContactDeletionRequested: (contact) => {
mainItem.deleteContact(contact)
}
Connections {
target: contactList.model
function onFriendCreated(index) {
contactList.currentIndex = index
}
}
} }
} }
} }
} }
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)
} }
} }
} }