mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-17 11:28:07 +00:00
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:
parent
97c2c1e214
commit
a6561ccb19
22 changed files with 1158 additions and 883 deletions
|
|
@ -824,8 +824,6 @@ void CallCore::findRemoteLdapFriend(QSharedPointer<CallCore> me) {
|
|||
auto linphoneSearch = CoreModel::getInstance()->getCore()->createMagicSearch();
|
||||
linphoneSearch->setLimitedSearch(true);
|
||||
mLdapMagicSearchModel = Utils::makeQObject_ptr<MagicSearchModel>(linphoneSearch);
|
||||
mLdapMagicSearchModel->setSourceFlags((int)LinphoneEnums::MagicSearchSource::LdapServers);
|
||||
mLdapMagicSearchModel->setAggregationFlag(LinphoneEnums::MagicSearchAggregation::Friend);
|
||||
mLdapMagicSearchModel->setSelf(mLdapMagicSearchModel);
|
||||
mLdapMagicSearchModelConnection = QSharedPointer<SafeConnection<CallCore, MagicSearchModel>>(
|
||||
new SafeConnection<CallCore, MagicSearchModel>(me, mLdapMagicSearchModel), &QObject::deleteLater);
|
||||
|
|
@ -845,5 +843,6 @@ void CallCore::findRemoteLdapFriend(QSharedPointer<CallCore> me) {
|
|||
});
|
||||
}
|
||||
});
|
||||
mLdapMagicSearchModel->search(mRemoteUsername);
|
||||
mLdapMagicSearchModel->search(mRemoteAddress, (int)LinphoneEnums::MagicSearchSource::LdapServers,
|
||||
LinphoneEnums::MagicSearchAggregation::Friend, -1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,14 +44,14 @@ QVariant createFriendDevice(const QString &name, const QString &address, Linphon
|
|||
return map;
|
||||
}
|
||||
|
||||
QSharedPointer<FriendCore> FriendCore::create(const std::shared_ptr<linphone::Friend> &contact) {
|
||||
auto sharedPointer = QSharedPointer<FriendCore>(new FriendCore(contact), &QObject::deleteLater);
|
||||
QSharedPointer<FriendCore> FriendCore::create(const std::shared_ptr<linphone::Friend> &contact, bool isStored) {
|
||||
auto sharedPointer = QSharedPointer<FriendCore>(new FriendCore(contact, isStored), &QObject::deleteLater);
|
||||
sharedPointer->setSelf(sharedPointer);
|
||||
sharedPointer->moveToThread(App::getInstance()->thread());
|
||||
return sharedPointer;
|
||||
}
|
||||
|
||||
FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObject(nullptr) {
|
||||
FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact, bool isStored) : QObject(nullptr) {
|
||||
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
|
||||
if (contact) {
|
||||
mustBeInLinphoneThread(getClassName());
|
||||
|
|
@ -60,6 +60,7 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObje
|
|||
mConsolidatedPresence = LinphoneEnums::fromLinphone(contact->getConsolidatedPresence());
|
||||
mPresenceTimestamp = mFriendModel->getPresenceTimestamp();
|
||||
mPictureUri = Utils::coreStringToAppString(contact->getPhoto());
|
||||
auto defaultAddress = contact->getAddress();
|
||||
auto vcard = contact->getVcard();
|
||||
if (vcard) {
|
||||
mOrganization = Utils::coreStringToAppString(vcard->getOrganization());
|
||||
|
|
@ -69,13 +70,16 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObje
|
|||
mFullName = Utils::coreStringToAppString(vcard->getFullName());
|
||||
mVCardString = Utils::coreStringToAppString(vcard->asVcard4String());
|
||||
}
|
||||
if (defaultAddress) {
|
||||
if (mGivenName.isEmpty()) mGivenName = Utils::coreStringToAppString(defaultAddress->getUsername());
|
||||
}
|
||||
auto addresses = contact->getAddresses();
|
||||
for (auto &address : addresses) {
|
||||
mAddressList.append(
|
||||
createFriendAddressVariant(_addressLabel, Utils::coreStringToAppString(address->asStringUriOnly())));
|
||||
}
|
||||
mDefaultAddress =
|
||||
contact->getAddress() ? Utils::coreStringToAppString(contact->getAddress()->asStringUriOnly()) : QString();
|
||||
defaultAddress ? Utils::coreStringToAppString(contact->getAddress()->asStringUriOnly()) : QString();
|
||||
auto phoneNumbers = contact->getPhoneNumbersWithLabel();
|
||||
for (auto &phoneNumber : phoneNumbers) {
|
||||
mPhoneNumberList.append(
|
||||
|
|
@ -94,9 +98,11 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObje
|
|||
|
||||
mStarred = contact->getStarred();
|
||||
mIsSaved = true;
|
||||
mIsStored = isStored;
|
||||
} else {
|
||||
mIsSaved = false;
|
||||
mStarred = false;
|
||||
mIsStored = false;
|
||||
}
|
||||
|
||||
mIsLdap = ToolModel::friendIsInFriendList(ToolModel::getLdapFriendList(), contact);
|
||||
|
|
@ -502,15 +508,28 @@ bool FriendCore::getIsSaved() const {
|
|||
void FriendCore::setIsSaved(bool data) {
|
||||
if (mIsSaved != data) {
|
||||
mIsSaved = data;
|
||||
if (mIsSaved) setIsStored(true);
|
||||
emit isSavedChanged(mIsSaved);
|
||||
}
|
||||
}
|
||||
|
||||
bool FriendCore::getIsStored() const {
|
||||
return mIsStored;
|
||||
}
|
||||
void FriendCore::setIsStored(bool data) {
|
||||
if (mIsStored != data) {
|
||||
mIsStored = data;
|
||||
emit isStoredChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void FriendCore::writeIntoModel(std::shared_ptr<FriendModel> model) const {
|
||||
mustBeInLinphoneThread(QString("[") + gClassName + "] " + Q_FUNC_INFO);
|
||||
model->getFriend()->edit();
|
||||
// needed to create the vcard if not created yet
|
||||
model->setName(mGivenName + (mFamilyName.isEmpty() || mGivenName.isEmpty() ? "" : " ") + mFamilyName);
|
||||
model->setName(mFullName.isEmpty()
|
||||
? mGivenName + (mFamilyName.isEmpty() || mGivenName.isEmpty() ? "" : " ") + mFamilyName
|
||||
: mFullName);
|
||||
auto core = CoreModel::getInstance()->getCore();
|
||||
|
||||
std::list<std::shared_ptr<linphone::Address>> addresses;
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class FriendCore : public QObject, public AbstractObject {
|
|||
Q_PROPERTY(LinphoneEnums::ConsolidatedPresence consolidatedPresence READ getConsolidatedPresence NOTIFY
|
||||
consolidatedPresenceChanged)
|
||||
Q_PROPERTY(bool isSaved READ getIsSaved NOTIFY isSavedChanged)
|
||||
Q_PROPERTY(bool isStored READ getIsStored NOTIFY isStoredChanged)
|
||||
Q_PROPERTY(QString pictureUri READ getPictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
|
||||
Q_PROPERTY(bool starred READ getStarred WRITE lSetStarred NOTIFY starredChanged)
|
||||
Q_PROPERTY(bool readOnly READ getReadOnly CONSTANT)
|
||||
|
|
@ -72,8 +73,8 @@ class FriendCore : public QObject, public AbstractObject {
|
|||
|
||||
public:
|
||||
// Should be call from model Thread. Will be automatically in App thread after initialization
|
||||
static QSharedPointer<FriendCore> create(const std::shared_ptr<linphone::Friend> &contact);
|
||||
FriendCore(const std::shared_ptr<linphone::Friend> &contact);
|
||||
static QSharedPointer<FriendCore> create(const std::shared_ptr<linphone::Friend> &contact, bool isStored = true);
|
||||
FriendCore(const std::shared_ptr<linphone::Friend> &contact, bool isStored = true);
|
||||
FriendCore(const FriendCore &friendCore);
|
||||
~FriendCore();
|
||||
void setSelf(QSharedPointer<FriendCore> me);
|
||||
|
|
@ -81,7 +82,6 @@ public:
|
|||
void reset(const FriendCore &contact);
|
||||
|
||||
QString getDisplayName() const;
|
||||
void setDisplayName(const QString &name);
|
||||
|
||||
QString getFamilyName() const;
|
||||
void setFamilyName(const QString &name);
|
||||
|
|
@ -131,6 +131,9 @@ public:
|
|||
bool getIsSaved() const;
|
||||
void setIsSaved(bool isSaved);
|
||||
|
||||
bool getIsStored() const; // Exist in DB
|
||||
void setIsStored(bool isStored);
|
||||
|
||||
QString getPictureUri() const;
|
||||
void setPictureUri(const QString &uri);
|
||||
void onPictureUriChanged(QString uri);
|
||||
|
|
@ -163,6 +166,7 @@ signals:
|
|||
void pictureUriChanged();
|
||||
void saved();
|
||||
void isSavedChanged(bool isSaved);
|
||||
void isStoredChanged();
|
||||
void removed(FriendCore *contact);
|
||||
void defaultAddressChanged();
|
||||
void allAddressesChanged();
|
||||
|
|
@ -189,6 +193,7 @@ protected:
|
|||
QString mDefaultAddress;
|
||||
QString mPictureUri;
|
||||
bool mIsSaved;
|
||||
bool mIsStored;
|
||||
QString mVCardString;
|
||||
bool mIsLdap;
|
||||
std::shared_ptr<FriendModel> mFriendModel;
|
||||
|
|
|
|||
|
|
@ -26,11 +26,13 @@ DEFINE_ABSTRACT_OBJECT(FriendGui)
|
|||
FriendGui::FriendGui(QObject *parent) : QObject(parent) {
|
||||
mustBeInMainThread(getClassName());
|
||||
mCore = FriendCore::create(nullptr);
|
||||
connect(mCore.get(), &FriendCore::isStoredChanged, this, &FriendGui::isStoredChanged);
|
||||
}
|
||||
FriendGui::FriendGui(QSharedPointer<FriendCore> core) {
|
||||
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
|
||||
mCore = core;
|
||||
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
|
||||
connect(mCore.get(), &FriendCore::isStoredChanged, this, &FriendGui::isStoredChanged);
|
||||
}
|
||||
|
||||
FriendGui::~FriendGui() {
|
||||
|
|
@ -43,3 +45,7 @@ void FriendGui::createContact(const QString &address) {
|
|||
FriendCore *FriendGui::getCore() const {
|
||||
return mCore.get();
|
||||
}
|
||||
|
||||
bool FriendGui::getIsStored() {
|
||||
return mCore ? mCore->getIsStored() : false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ class FriendGui : public QObject, public AbstractObject {
|
|||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(FriendCore *core READ getCore CONSTANT)
|
||||
Q_PROPERTY(bool isStored READ getIsStored NOTIFY isStoredChanged)
|
||||
|
||||
public:
|
||||
FriendGui(QObject *parent = nullptr);
|
||||
|
|
@ -37,6 +38,12 @@ public:
|
|||
FriendCore *getCore() const;
|
||||
Q_INVOKABLE void createContact(const QString &address);
|
||||
QSharedPointer<FriendCore> mCore;
|
||||
|
||||
bool getIsStored();
|
||||
signals:
|
||||
void isStoredChanged();
|
||||
|
||||
public:
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -88,3 +88,7 @@ void SortFilterProxy::setSortOrder(const Qt::SortOrder &order) {
|
|||
void SortFilterProxy::remove(int index, int count) {
|
||||
QSortFilterProxyModel::removeRows(index, count);
|
||||
}
|
||||
|
||||
void SortFilterProxy::invalidateFilter() {
|
||||
QSortFilterProxyModel::invalidateFilter();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ public:
|
|||
void setFilterText(const QString &filter);
|
||||
|
||||
Q_INVOKABLE void remove(int index, int count = 1);
|
||||
void invalidateFilter();
|
||||
|
||||
signals:
|
||||
void countChanged();
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
#include "MagicSearchList.hpp"
|
||||
#include "core/App.hpp"
|
||||
#include "core/friend/FriendCore.hpp"
|
||||
#include "core/friend/FriendGui.hpp"
|
||||
#include "tool/Utils.hpp"
|
||||
#include <QSharedPointer>
|
||||
#include <linphone++/linphone.hh>
|
||||
|
|
@ -62,7 +61,7 @@ void MagicSearchList::setSelf(QSharedPointer<MagicSearchList> me) {
|
|||
if (haveContact == mList.end()) {
|
||||
connect(friendCore.get(), &FriendCore::removed, this, qOverload<QObject *>(&MagicSearchList::remove));
|
||||
add(friendCore);
|
||||
emit friendCreated(getCount() - 1);
|
||||
emit friendCreated(getCount() - 1, new FriendGui(friendCore));
|
||||
}
|
||||
});
|
||||
mCoreModelConnection->invokeToModel([this] {
|
||||
|
|
@ -71,34 +70,17 @@ void MagicSearchList::setSelf(QSharedPointer<MagicSearchList> me) {
|
|||
auto magicSearch = Utils::makeQObject_ptr<MagicSearchModel>(linphoneSearch);
|
||||
mCoreModelConnection->invokeToCore([this, magicSearch] {
|
||||
mMagicSearch = magicSearch;
|
||||
mMagicSearch->mSourceFlags = mSourceFlags;
|
||||
mMagicSearch->mAggregationFlag = mAggregationFlag;
|
||||
mMagicSearch->setSelf(mMagicSearch);
|
||||
mModelConnection = QSharedPointer<SafeConnection<MagicSearchList, MagicSearchModel>>(
|
||||
new SafeConnection<MagicSearchList, MagicSearchModel>(mCoreModelConnection->mCore.mQData, mMagicSearch),
|
||||
&QObject::deleteLater);
|
||||
mModelConnection->makeConnectToCore(&MagicSearchList::lSearch, [this](QString filter) {
|
||||
mModelConnection->invokeToModel([this, filter]() { mMagicSearch->search(filter); });
|
||||
});
|
||||
mModelConnection->makeConnectToCore(&MagicSearchList::lSetSourceFlags, [this](int flags) {
|
||||
mModelConnection->invokeToModel([this, flags]() { mMagicSearch->setSourceFlags(flags); });
|
||||
});
|
||||
mModelConnection->makeConnectToCore(&MagicSearchList::lSetAggregationFlag,
|
||||
[this](LinphoneEnums::MagicSearchAggregation aggregation) {
|
||||
mModelConnection->invokeToModel([this, aggregation]() {
|
||||
mMagicSearch->setAggregationFlag(aggregation);
|
||||
});
|
||||
});
|
||||
mModelConnection->makeConnectToCore(
|
||||
&MagicSearchList::lSetAggregationFlag, [this](LinphoneEnums::MagicSearchAggregation flag) {
|
||||
mModelConnection->invokeToModel([this, flag]() { mMagicSearch->setAggregationFlag(flag); });
|
||||
});
|
||||
mModelConnection->makeConnectToModel(&MagicSearchModel::sourceFlagsChanged, [this](int flags) {
|
||||
mModelConnection->invokeToCore([this, flags]() { setSourceFlags(flags); });
|
||||
});
|
||||
mModelConnection->makeConnectToModel(
|
||||
&MagicSearchModel::aggregationFlagChanged, [this](LinphoneEnums::MagicSearchAggregation flag) {
|
||||
mModelConnection->invokeToCore([this, flag]() { setAggregationFlag(flag); });
|
||||
&MagicSearchList::lSearch,
|
||||
[this](QString filter, int sourceFlags, LinphoneEnums::MagicSearchAggregation aggregationFlag,
|
||||
int maxResults) {
|
||||
mModelConnection->invokeToModel([this, filter, sourceFlags, aggregationFlag, maxResults]() {
|
||||
mMagicSearch->search(filter, sourceFlags, aggregationFlag, maxResults);
|
||||
});
|
||||
});
|
||||
mModelConnection->makeConnectToModel(
|
||||
&MagicSearchModel::searchResultsReceived,
|
||||
|
|
@ -106,26 +88,30 @@ void MagicSearchList::setSelf(QSharedPointer<MagicSearchList> me) {
|
|||
auto *contacts = new QList<QSharedPointer<FriendCore>>();
|
||||
for (auto it : results) {
|
||||
QSharedPointer<FriendCore> contact;
|
||||
if (it->getFriend()) {
|
||||
contact = FriendCore::create(it->getFriend());
|
||||
auto linphoneFriend = it->getFriend();
|
||||
// Considered LDAP results as stored.
|
||||
bool isStored = (it->getSourceFlags() & (int)linphone::MagicSearch::Source::LdapServers) > 0;
|
||||
if (linphoneFriend) {
|
||||
contact = FriendCore::create(linphoneFriend);
|
||||
contacts->append(contact);
|
||||
} else if (auto address = it->getAddress()) {
|
||||
auto linphoneFriend = CoreModel::getInstance()->getCore()->createFriend();
|
||||
contact = FriendCore::create(linphoneFriend);
|
||||
auto displayname = Utils::coreStringToAppString(address->getDisplayName());
|
||||
auto splitted = displayname.split(" ");
|
||||
if (splitted.size() > 0) {
|
||||
linphoneFriend->setAddress(address);
|
||||
contact = FriendCore::create(linphoneFriend, isStored);
|
||||
auto displayName = Utils::coreStringToAppString(address->getDisplayName());
|
||||
auto splitted = displayName.split(" ");
|
||||
if (!displayName.isEmpty() && splitted.size() > 0) {
|
||||
contact->setGivenName(splitted[0]);
|
||||
splitted.removeFirst();
|
||||
contact->setFamilyName(splitted.join(" "));
|
||||
} else {
|
||||
contact->setGivenName(Utils::coreStringToAppString(address->getUsername()));
|
||||
}
|
||||
contact->appendAddress(Utils::coreStringToAppString(address->asStringUriOnly()));
|
||||
contacts->append(contact);
|
||||
} else if (!it->getPhoneNumber().empty()) {
|
||||
auto linphoneFriend = CoreModel::getInstance()->getCore()->createFriend();
|
||||
contact = FriendCore::create(linphoneFriend);
|
||||
linphoneFriend = CoreModel::getInstance()->getCore()->createFriend();
|
||||
linphoneFriend->setAddress(address);
|
||||
contact = FriendCore::create(linphoneFriend, isStored);
|
||||
contact->setGivenName(Utils::coreStringToAppString(it->getPhoneNumber()));
|
||||
contact->appendPhoneNumber(tr("Phone"), Utils::coreStringToAppString(it->getPhoneNumber()));
|
||||
contacts->append(contact);
|
||||
|
|
@ -148,10 +134,11 @@ void MagicSearchList::setResults(const QList<QSharedPointer<FriendCore>> &contac
|
|||
if (!isFriendCore) continue;
|
||||
disconnect(isFriendCore.get());
|
||||
}
|
||||
qDebug() << log().arg("SetResults: %1").arg(contacts.size());
|
||||
resetData<FriendCore>(contacts);
|
||||
for (auto it : contacts) {
|
||||
connect(it.get(), &FriendCore::removed, this, qOverload<QObject *>(&MagicSearchList::remove));
|
||||
connect(it.get(), &FriendCore::starredChanged, this, [this] { lSearch(mSearchFilter); });
|
||||
connect(it.get(), &FriendCore::starredChanged, this, &MagicSearchList::friendStarredChanged);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +148,7 @@ void MagicSearchList::addResult(const QSharedPointer<FriendCore> &contact) {
|
|||
void MagicSearchList::setSearch(const QString &search) {
|
||||
mSearchFilter = search;
|
||||
if (!search.isEmpty()) {
|
||||
lSearch(search);
|
||||
emit lSearch(search, mSourceFlags, mAggregationFlag, mMaxResults);
|
||||
} else {
|
||||
beginResetModel();
|
||||
mList.clear();
|
||||
|
|
@ -180,6 +167,17 @@ void MagicSearchList::setSourceFlags(int flags) {
|
|||
}
|
||||
}
|
||||
|
||||
int MagicSearchList::getMaxResults() const {
|
||||
return mMaxResults;
|
||||
}
|
||||
|
||||
void MagicSearchList::setMaxResults(int maxResults) {
|
||||
if (mMaxResults != maxResults) {
|
||||
mMaxResults = maxResults;
|
||||
emit maxResultsChanged(mMaxResults);
|
||||
}
|
||||
}
|
||||
|
||||
LinphoneEnums::MagicSearchAggregation MagicSearchList::getAggregationFlag() const {
|
||||
return mAggregationFlag;
|
||||
}
|
||||
|
|
@ -191,11 +189,20 @@ void MagicSearchList::setAggregationFlag(LinphoneEnums::MagicSearchAggregation f
|
|||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> MagicSearchList::roleNames() const {
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[Qt::DisplayRole] = "$modelData";
|
||||
roles[Qt::DisplayRole + 1] = "isStored";
|
||||
return roles;
|
||||
}
|
||||
|
||||
QVariant MagicSearchList::data(const QModelIndex &index, int role) const {
|
||||
int row = index.row();
|
||||
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();
|
||||
if (role == Qt::DisplayRole) {
|
||||
return QVariant::fromValue(new FriendGui(mList[row].objectCast<FriendCore>()));
|
||||
} else if (role == Qt::DisplayRole + 1) {
|
||||
return mList[row].objectCast<FriendCore>()->getIsStored();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#define MAGIC_SEARCH_LIST_H_
|
||||
|
||||
#include "../proxy/ListProxy.hpp"
|
||||
#include "core/friend/FriendGui.hpp"
|
||||
#include "model/search/MagicSearchModel.hpp"
|
||||
#include "tool/AbstractObject.hpp"
|
||||
#include "tool/thread/SafeConnection.hpp"
|
||||
|
|
@ -50,19 +51,24 @@ public:
|
|||
LinphoneEnums::MagicSearchAggregation getAggregationFlag() const;
|
||||
void setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag);
|
||||
|
||||
int getMaxResults() const;
|
||||
void setMaxResults(int maxResults);
|
||||
|
||||
virtual QHash<int, QByteArray> roleNames() const override;
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
int findFriendIndexByAddress(const QString &address);
|
||||
|
||||
signals:
|
||||
void lSearch(QString filter);
|
||||
void lSetSourceFlags(int sourceFlags);
|
||||
void lSetAggregationFlag(LinphoneEnums::MagicSearchAggregation aggregationFlag);
|
||||
void
|
||||
lSearch(QString filter, int sourceFlags, LinphoneEnums::MagicSearchAggregation aggregationFlag, int maxResults);
|
||||
|
||||
void sourceFlagsChanged(int sourceFlags);
|
||||
void aggregationFlagChanged(LinphoneEnums::MagicSearchAggregation flag);
|
||||
void maxResultsChanged(int maxResults);
|
||||
|
||||
void friendCreated(int index);
|
||||
void friendCreated(int index, FriendGui *data);
|
||||
void friendStarredChanged();
|
||||
|
||||
void initialized();
|
||||
|
||||
|
|
@ -70,6 +76,7 @@ private:
|
|||
int mSourceFlags;
|
||||
LinphoneEnums::MagicSearchAggregation mAggregationFlag;
|
||||
QString mSearchFilter;
|
||||
int mMaxResults = -1;
|
||||
|
||||
std::shared_ptr<MagicSearchModel> mMagicSearch;
|
||||
QSharedPointer<SafeConnection<MagicSearchList, MagicSearchModel>> mModelConnection;
|
||||
|
|
|
|||
|
|
@ -20,16 +20,14 @@
|
|||
|
||||
#include "MagicSearchProxy.hpp"
|
||||
#include "MagicSearchList.hpp"
|
||||
|
||||
#include "core/App.hpp"
|
||||
#include "core/friend/FriendGui.hpp"
|
||||
#include "core/friend/FriendCore.hpp"
|
||||
|
||||
MagicSearchProxy::MagicSearchProxy(QObject *parent) : LimitProxy(parent) {
|
||||
mSourceFlags = (int)LinphoneEnums::MagicSearchSource::Friends | (int)LinphoneEnums::MagicSearchSource::LdapServers;
|
||||
mAggregationFlag = LinphoneEnums::MagicSearchAggregation::Friend;
|
||||
setList(MagicSearchList::create());
|
||||
sort(0);
|
||||
connect(this, &MagicSearchProxy::forceUpdate, [this] {
|
||||
if (mList) emit mList->lSearch(mSearchText);
|
||||
if (mList) emit mList->lSearch(mSearchText, getSourceFlags(), getAggregationFlag(), getMaxResults());
|
||||
});
|
||||
connect(App::getInstance(), &App::currentDateChanged, this, &MagicSearchProxy::forceUpdate);
|
||||
}
|
||||
|
|
@ -50,28 +48,28 @@ void MagicSearchProxy::setList(QSharedPointer<MagicSearchList> newList) {
|
|||
Qt::QueuedConnection);
|
||||
connect(mList.get(), &MagicSearchList::aggregationFlagChanged, this, &MagicSearchProxy::aggregationFlagChanged,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
mList.get(), &MagicSearchList::friendCreated, this,
|
||||
[this](int index) {
|
||||
auto proxyIndex =
|
||||
dynamic_cast<SortFilterList *>(sourceModel())->mapFromSource(mList->index(index, 0)).row();
|
||||
// auto proxyIndex = mapFromSource(sourceModel()->index(index, 0)); // OLD (keep for checking new proxy
|
||||
// behavior)
|
||||
emit friendCreated(proxyIndex);
|
||||
[this](int index, FriendGui *data) {
|
||||
auto sortModel = dynamic_cast<SortFilterList *>(sourceModel());
|
||||
sortModel->invalidate();
|
||||
if (!data->mCore->isLdap()) {
|
||||
auto proxyIndex = sortModel->mapFromSource(mList->index(index, 0)).row();
|
||||
// auto proxyIndex = mapFromSource(sourceModel()->index(index, 0)); // OLD (keep for checking new
|
||||
// proxy behavior)
|
||||
emit localFriendCreated(proxyIndex);
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
connect(
|
||||
mList.get(), &MagicSearchList::initialized, this,
|
||||
[this, newList = mList.get()] {
|
||||
emit newList->lSetSourceFlags(mSourceFlags);
|
||||
emit newList->lSetAggregationFlag(mAggregationFlag);
|
||||
emit initialized();
|
||||
},
|
||||
mList.get(), &MagicSearchList::initialized, this, [this, newList = mList.get()] { emit initialized(); },
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
auto sortFilterList = new SortFilterList(mList.get(), Qt::AscendingOrder);
|
||||
if (oldModel) {
|
||||
sortFilterList->mShowFavoritesOnly = oldModel->mShowFavoritesOnly;
|
||||
sortFilterList->mHideSuggestions = oldModel->mHideSuggestions;
|
||||
sortFilterList->mShowLdapContacts = oldModel->mShowLdapContacts;
|
||||
sortFilterList->mHideListProxy = oldModel->mHideListProxy;
|
||||
if (sortFilterList->mHideListProxy) {
|
||||
|
|
@ -81,7 +79,14 @@ void MagicSearchProxy::setList(QSharedPointer<MagicSearchList> newList) {
|
|||
[this, sortFilterList]() { sortFilterList->invalidate(); });
|
||||
}
|
||||
}
|
||||
connect(
|
||||
mList.get(), &MagicSearchList::friendStarredChanged, this,
|
||||
[this, sortFilterList]() {
|
||||
if (showFavoritesOnly()) sortFilterList->invalidate();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
setSourceModels(sortFilterList);
|
||||
sort(0);
|
||||
}
|
||||
|
||||
int MagicSearchProxy::findFriendIndexByAddress(const QString &address) {
|
||||
|
|
@ -105,14 +110,19 @@ void MagicSearchProxy::setSearchText(const QString &search) {
|
|||
}
|
||||
|
||||
int MagicSearchProxy::getSourceFlags() const {
|
||||
return mSourceFlags;
|
||||
return mList->getSourceFlags();
|
||||
}
|
||||
|
||||
void MagicSearchProxy::setSourceFlags(int flags) {
|
||||
if (flags != mSourceFlags) {
|
||||
mSourceFlags = flags;
|
||||
emit mList->lSetSourceFlags(flags);
|
||||
}
|
||||
mList->setSourceFlags(flags);
|
||||
}
|
||||
|
||||
int MagicSearchProxy::getMaxResults() const {
|
||||
return mList->getMaxResults();
|
||||
}
|
||||
|
||||
void MagicSearchProxy::setMaxResults(int flags) {
|
||||
mList->setMaxResults(flags);
|
||||
}
|
||||
|
||||
bool MagicSearchProxy::showFavoritesOnly() const {
|
||||
|
|
@ -128,9 +138,28 @@ void MagicSearchProxy::setShowFavoritesOnly(bool show) {
|
|||
}
|
||||
}
|
||||
|
||||
void MagicSearchProxy::setParentProxy(MagicSearchProxy *proxy) {
|
||||
setList(proxy->mList);
|
||||
emit parentProxyChanged();
|
||||
bool MagicSearchProxy::getHideSuggestions() const {
|
||||
return dynamic_cast<SortFilterList *>(sourceModel())->mHideSuggestions;
|
||||
}
|
||||
void MagicSearchProxy::setHideSuggestions(bool data) {
|
||||
auto list = dynamic_cast<SortFilterList *>(sourceModel());
|
||||
if (list->mHideSuggestions != data) {
|
||||
list->mHideSuggestions = data;
|
||||
list->invalidate();
|
||||
emit hideSuggestionsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
MagicSearchProxy *MagicSearchProxy::getParentProxy() const {
|
||||
return mParentProxy;
|
||||
}
|
||||
|
||||
void MagicSearchProxy::setParentProxy(MagicSearchProxy *parentProxy) {
|
||||
if (parentProxy && parentProxy->mList) {
|
||||
mParentProxy = parentProxy;
|
||||
setList(parentProxy->mList);
|
||||
emit parentProxyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
MagicSearchProxy *MagicSearchProxy::getHideListProxy() const {
|
||||
|
|
@ -138,36 +167,34 @@ MagicSearchProxy *MagicSearchProxy::getHideListProxy() const {
|
|||
return list ? list->mHideListProxy : nullptr;
|
||||
}
|
||||
|
||||
void MagicSearchProxy::setHideListProxy(MagicSearchProxy *proxy) {
|
||||
void MagicSearchProxy::setHideListProxy(MagicSearchProxy *hideListProxy) {
|
||||
auto list = dynamic_cast<SortFilterList *>(sourceModel());
|
||||
if (list && list->mHideListProxy != proxy) {
|
||||
if (list && list->mHideListProxy != hideListProxy) {
|
||||
if (list->mHideListProxy) list->disconnect(list->mHideListProxy);
|
||||
list->mHideListProxy = proxy;
|
||||
list->mHideListProxy = hideListProxy;
|
||||
list->invalidate();
|
||||
if (proxy) {
|
||||
connect(proxy, &MagicSearchProxy::countChanged, list, [this, list]() { list->invalidate(); });
|
||||
connect(proxy, &MagicSearchProxy::modelReset, list, [this, list]() { list->invalidate(); });
|
||||
if (hideListProxy) {
|
||||
connect(hideListProxy, &MagicSearchProxy::countChanged, list, [this, list]() { list->invalidateFilter(); });
|
||||
connect(hideListProxy, &MagicSearchProxy::modelReset, list, [this, list]() { list->invalidateFilter(); });
|
||||
}
|
||||
emit hideListProxyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
LinphoneEnums::MagicSearchAggregation MagicSearchProxy::getAggregationFlag() const {
|
||||
return mAggregationFlag;
|
||||
return mList->getAggregationFlag();
|
||||
}
|
||||
|
||||
void MagicSearchProxy::setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag) {
|
||||
if (flag != mAggregationFlag) {
|
||||
mAggregationFlag = flag;
|
||||
emit mList->lSetAggregationFlag(flag);
|
||||
}
|
||||
mList->setAggregationFlag(flag);
|
||||
}
|
||||
|
||||
bool MagicSearchProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
|
||||
auto friendCore = getItemAtSource<MagicSearchList, FriendCore>(sourceRow);
|
||||
auto toShow = false;
|
||||
if (friendCore) {
|
||||
toShow = (!mShowFavoritesOnly || friendCore->getStarred()) && (mShowLdapContacts || !friendCore->isLdap());
|
||||
toShow = (!mHideSuggestions || friendCore->getIsStored()) &&
|
||||
(!mShowFavoritesOnly || friendCore->getStarred()) && (mShowLdapContacts || !friendCore->isLdap());
|
||||
if (toShow && mHideListProxy) {
|
||||
for (auto &friendAddress : friendCore->getAllAddresses()) {
|
||||
toShow = mHideListProxy->findFriendIndexByAddress(friendAddress.toMap()["address"].toString()) == -1;
|
||||
|
|
@ -184,6 +211,10 @@ bool MagicSearchProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft, c
|
|||
auto r = getItemAtSource<MagicSearchList, FriendCore>(sourceRight.row());
|
||||
|
||||
if (l && r) {
|
||||
bool lIsStored = l->getIsStored();
|
||||
bool rIsStored = r->getIsStored();
|
||||
if (lIsStored && !rIsStored) return true;
|
||||
else if (!lIsStored && rIsStored) return false;
|
||||
auto lName = l->getDisplayName().toLower();
|
||||
auto rName = r->getDisplayName().toLower();
|
||||
return lName < rName;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#define MAGIC_SEARCH_PROXY_H_
|
||||
|
||||
#include "../proxy/LimitProxy.hpp"
|
||||
#include "core/friend/FriendGui.hpp"
|
||||
#include "core/search/MagicSearchList.hpp"
|
||||
#include "tool/LinphoneEnums.hpp"
|
||||
|
||||
|
|
@ -32,18 +33,21 @@ class MagicSearchProxy : public LimitProxy {
|
|||
|
||||
Q_PROPERTY(QString searchText READ getSearchText WRITE setSearchText NOTIFY searchTextChanged)
|
||||
Q_PROPERTY(int sourceFlags READ getSourceFlags WRITE setSourceFlags NOTIFY sourceFlagsChanged)
|
||||
|
||||
Q_PROPERTY(int maxResults READ getMaxResults WRITE setMaxResults NOTIFY maxResultsChanged)
|
||||
Q_PROPERTY(LinphoneEnums::MagicSearchAggregation aggregationFlag READ getAggregationFlag WRITE setAggregationFlag
|
||||
NOTIFY aggregationFlagChanged)
|
||||
Q_PROPERTY(bool showFavoritesOnly READ showFavoritesOnly WRITE setShowFavoritesOnly NOTIFY showFavoriteOnlyChanged)
|
||||
Q_PROPERTY(MagicSearchProxy *parentProxy WRITE setParentProxy NOTIFY parentProxyChanged)
|
||||
Q_PROPERTY(MagicSearchProxy *parentProxy READ getParentProxy WRITE setParentProxy NOTIFY parentProxyChanged)
|
||||
Q_PROPERTY(MagicSearchProxy *hideListProxy READ getHideListProxy WRITE setHideListProxy NOTIFY hideListProxyChanged)
|
||||
|
||||
Q_PROPERTY(bool showLdapContacts READ showLdapContacts WRITE setShowLdapContacts CONSTANT)
|
||||
Q_PROPERTY(bool hideSuggestions READ getHideSuggestions WRITE setHideSuggestions NOTIFY hideSuggestionsChanged)
|
||||
|
||||
public:
|
||||
DECLARE_SORTFILTER_CLASS(bool mShowFavoritesOnly = false; bool mShowLdapContacts = false;
|
||||
DECLARE_SORTFILTER_CLASS(bool mShowFavoritesOnly = false; bool mShowLdapContacts = true;
|
||||
bool mHideSuggestions = false;
|
||||
MagicSearchProxy *mHideListProxy = nullptr;)
|
||||
|
||||
MagicSearchProxy(QObject *parent = Q_NULLPTR);
|
||||
~MagicSearchProxy();
|
||||
|
||||
|
|
@ -56,12 +60,19 @@ public:
|
|||
LinphoneEnums::MagicSearchAggregation getAggregationFlag() const;
|
||||
void setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag);
|
||||
|
||||
int getMaxResults() const;
|
||||
void setMaxResults(int maxResults);
|
||||
|
||||
bool showFavoritesOnly() const;
|
||||
void setShowFavoritesOnly(bool show);
|
||||
|
||||
bool showLdapContacts() const;
|
||||
void setShowLdapContacts(bool show);
|
||||
|
||||
bool getHideSuggestions() const;
|
||||
void setHideSuggestions(bool data);
|
||||
|
||||
MagicSearchProxy *getParentProxy() const;
|
||||
void setList(QSharedPointer<MagicSearchList> list);
|
||||
Q_INVOKABLE void setParentProxy(MagicSearchProxy *proxy);
|
||||
|
||||
|
|
@ -75,17 +86,18 @@ signals:
|
|||
void searchTextChanged();
|
||||
void sourceFlagsChanged(int sourceFlags);
|
||||
void aggregationFlagChanged(LinphoneEnums::MagicSearchAggregation aggregationFlag);
|
||||
void maxResultsChanged(int maxResults);
|
||||
void forceUpdate();
|
||||
void friendCreated(int index);
|
||||
void localFriendCreated(int index);
|
||||
void showFavoriteOnlyChanged();
|
||||
void hideSuggestionsChanged();
|
||||
void parentProxyChanged();
|
||||
void hideListProxyChanged();
|
||||
void initialized();
|
||||
|
||||
protected:
|
||||
MagicSearchProxy *mParentProxy = nullptr;
|
||||
QString mSearchText;
|
||||
int mSourceFlags;
|
||||
LinphoneEnums::MagicSearchAggregation mAggregationFlag;
|
||||
QSharedPointer<MagicSearchList> mList;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include <QDebug>
|
||||
|
||||
#include "model/core/CoreModel.hpp"
|
||||
#include "model/setting/SettingsModel.hpp"
|
||||
#include "model/tool/ToolModel.hpp"
|
||||
#include "tool/Utils.hpp"
|
||||
|
||||
|
|
@ -37,35 +38,43 @@ MagicSearchModel::~MagicSearchModel() {
|
|||
mustBeInLinphoneThread("~" + getClassName());
|
||||
}
|
||||
|
||||
void MagicSearchModel::search(QString filter) {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
void MagicSearchModel::search(QString filter,
|
||||
int sourceFlags,
|
||||
LinphoneEnums::MagicSearchAggregation aggregation,
|
||||
int maxResults) {
|
||||
mLastSearch = filter;
|
||||
mMonitor->getContactsListAsync(filter != "*" ? Utils::appStringToCoreString(filter) : "", "", mSourceFlags,
|
||||
LinphoneEnums::toLinphone(mAggregationFlag));
|
||||
}
|
||||
|
||||
void MagicSearchModel::setSourceFlags(int flags) {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
if (mSourceFlags != flags) {
|
||||
mSourceFlags = flags;
|
||||
emit sourceFlagsChanged(mSourceFlags);
|
||||
setMaxResults(maxResults);
|
||||
if ((filter == "" || filter == "*") && ((sourceFlags & (int)LinphoneEnums::MagicSearchSource::LdapServers) > 0) &&
|
||||
!SettingsModel::getInstance()->getSyncLdapContacts()) {
|
||||
sourceFlags &= ~(int)LinphoneEnums::MagicSearchSource::LdapServers;
|
||||
}
|
||||
qInfo() << log().arg("Searching ") << filter << " from " << sourceFlags << " with limit " << maxResults;
|
||||
mMonitor->getContactsListAsync(filter != "*" ? Utils::appStringToCoreString(filter) : "", "", sourceFlags,
|
||||
LinphoneEnums::toLinphone(aggregation));
|
||||
}
|
||||
|
||||
void MagicSearchModel::setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag) {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
if (mAggregationFlag != flag) {
|
||||
mAggregationFlag = flag;
|
||||
emit aggregationFlagChanged(mAggregationFlag);
|
||||
int MagicSearchModel::getMaxResults() const {
|
||||
if (!mMonitor->getLimitedSearch()) return -1;
|
||||
else return mMonitor->getSearchLimit();
|
||||
}
|
||||
|
||||
void MagicSearchModel::setMaxResults(int maxResults) {
|
||||
if (maxResults <= 0 && mMonitor->getLimitedSearch() ||
|
||||
maxResults > 0 && (!mMonitor->getLimitedSearch() || maxResults != mMonitor->getSearchLimit())) {
|
||||
mMonitor->setLimitedSearch(maxResults > 0);
|
||||
if (maxResults > 0) mMonitor->setSearchLimit(maxResults);
|
||||
emit maxResultsChanged(maxResults);
|
||||
}
|
||||
}
|
||||
|
||||
void MagicSearchModel::onSearchResultsReceived(const std::shared_ptr<linphone::MagicSearch> &magicSearch) {
|
||||
for (auto it : magicSearch->getLastSearch()) {
|
||||
qDebug() << log().arg("SDK send callback: onSearchResultsReceived");
|
||||
auto results = magicSearch->getLastSearch();
|
||||
for (auto it : results) {
|
||||
bool isLdap = (it->getSourceFlags() & (int)LinphoneEnums::MagicSearchSource::LdapServers) != 0;
|
||||
if (isLdap && it->getFriend()) updateLdapFriendListWithFriend(it->getFriend());
|
||||
}
|
||||
emit searchResultsReceived(magicSearch->getLastSearch());
|
||||
emit searchResultsReceived(results);
|
||||
}
|
||||
|
||||
void MagicSearchModel::onLdapHaveMoreResults(const std::shared_ptr<linphone::MagicSearch> &magicSearch,
|
||||
|
|
|
|||
|
|
@ -37,17 +37,14 @@ public:
|
|||
MagicSearchModel(const std::shared_ptr<linphone::MagicSearch> &data, QObject *parent = nullptr);
|
||||
~MagicSearchModel();
|
||||
|
||||
void search(QString filter);
|
||||
void setSourceFlags(int flags);
|
||||
void setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag);
|
||||
void search(QString filter, int sourceFlags, LinphoneEnums::MagicSearchAggregation aggregation, int maxResults);
|
||||
|
||||
int mSourceFlags = (int)linphone::MagicSearch::Source::All;
|
||||
LinphoneEnums::MagicSearchAggregation mAggregationFlag = LinphoneEnums::MagicSearchAggregation::None;
|
||||
int getMaxResults() const;
|
||||
void setMaxResults(int maxResults);
|
||||
QString mLastSearch;
|
||||
|
||||
signals:
|
||||
void sourceFlagsChanged(int sourceFlags);
|
||||
void aggregationFlagChanged(LinphoneEnums::MagicSearchAggregation aggregationFlag);
|
||||
void maxResultsChanged(int maxResults);
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
|
|||
view/Control/Display/Call/CallStatistics.qml
|
||||
view/Control/Display/Contact/Avatar.qml
|
||||
view/Control/Display/Contact/Contact.qml
|
||||
view/Control/Display/Contact/ContactListItem.qml
|
||||
view/Control/Display/Contact/ContactListView.qml
|
||||
view/Control/Display/Contact/Voicemail.qml
|
||||
view/Control/Display/Meeting/MeetingListView.qml
|
||||
|
|
|
|||
264
Linphone/view/Control/Display/Contact/ContactListItem.qml
Normal file
264
Linphone/view/Control/Display/Contact/ContactListItem.qml
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,52 +7,56 @@ import UtilsCpp 1.0
|
|||
import ConstantsCpp 1.0
|
||||
import SettingsCpp
|
||||
|
||||
|
||||
ListView {
|
||||
id: mainItem
|
||||
height: contentHeight
|
||||
visible: contentHeight > 0
|
||||
clip: true
|
||||
currentIndex: -1
|
||||
//keyNavigationWraps: true
|
||||
// rightMargin: 5 * DefaultStyle.dp
|
||||
|
||||
property bool selectionEnabled: true
|
||||
property bool hoverEnabled: true
|
||||
// dots popup menu
|
||||
property bool contactMenuVisible: true
|
||||
// call, video call etc menu
|
||||
property bool actionLayoutVisible: false
|
||||
property bool initialHeadersVisible: true
|
||||
property bool displayNameCapitalization: true
|
||||
property bool showFavoritesOnly: false
|
||||
property bool showDefaultAddress: false
|
||||
property bool showLdapContacts: false
|
||||
property bool showInitials: true // Display Initials of Display name.
|
||||
property bool showDefaultAddress: true // Display address below display name.
|
||||
property bool showActions: false // Display actions layout (call buttons)
|
||||
property bool showContactMenu: true // Display the dot menu for contacts.
|
||||
property bool showFavorites: true // Display the favorites in the header
|
||||
property bool hideSuggestions: false // Hide not stored contacts (not suggestions)
|
||||
property string highlightText // Bold characters in Display name.
|
||||
property var sourceFlags: LinphoneEnums.MagicSearchSource.All
|
||||
|
||||
property bool displayNameCapitalization: true // Capitalize display name.
|
||||
|
||||
property bool selectionEnabled: true // Contact can be selected
|
||||
property bool multiSelectionEnabled: false //Multiple items can be selected.
|
||||
property list<string> selectedContacts // List of default address on selected contacts.
|
||||
property FriendGui selectedContact//: model.getAt(currentIndex) || null
|
||||
|
||||
property bool searchOnInitialization: false
|
||||
|
||||
property var listProxy: MagicSearchProxy{}
|
||||
property alias hideListProxy: magicSearchProxy.hideListProxy
|
||||
property bool loading: false
|
||||
property bool pauseSearch: false // true = don't search on text change
|
||||
|
||||
// Model properties
|
||||
// set searchBarText without specifying a model to bold
|
||||
// matching names
|
||||
property string searchBarText
|
||||
property string searchText: searchBarText.length === 0 ? "*" : searchBarText
|
||||
property var aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend
|
||||
property var sourceFlags: LinphoneEnums.MagicSearchSource.Friends | ((searchText.length > 0 && searchText != "*") || SettingsCpp.syncLdapContacts ? LinphoneEnums.MagicSearchSource.LdapServers : 0)
|
||||
|
||||
property string searchText// Binding is done on searchBarTextChanged
|
||||
property ConferenceInfoGui confInfoGui
|
||||
|
||||
property bool multiSelectionEnabled: false
|
||||
property list<string> selectedContacts
|
||||
property int selectedContactCount: selectedContacts.length
|
||||
|
||||
property FriendGui selectedContact: model.getAt(currentIndex) || null
|
||||
|
||||
|
||||
property bool haveFavorites: false
|
||||
property int sectionsPixelSize: 16 * DefaultStyle.dp
|
||||
property int sectionsWeight: 800 * DefaultStyle.dp
|
||||
property int sectionsSpacing: 18 * DefaultStyle.dp
|
||||
|
||||
property int itemsRightMargin: 39 * DefaultStyle.dp
|
||||
|
||||
signal resultsReceived()
|
||||
signal contactStarredChanged()
|
||||
signal contactDeletionRequested(FriendGui contact)
|
||||
signal contactAddedToSelection(string address)
|
||||
signal contactRemovedFromSelection(string address)
|
||||
signal contactClicked(FriendGui contact)
|
||||
|
||||
clip: true
|
||||
highlightFollowsCurrentItem: true
|
||||
cacheBuffer: 400
|
||||
// Binding loop hack
|
||||
onContentHeightChanged: Qt.callLater(function(){cacheBuffer = Math.max(0,contentHeight)})
|
||||
|
||||
function selectContact(address) {
|
||||
var index = magicSearchProxy.findFriendIndexByAddress(address)
|
||||
|
|
@ -89,9 +93,75 @@ ListView {
|
|||
return index != -1
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: selectedContact = model.getAt(currentIndex) || null
|
||||
onCountChanged: selectedContact = model.getAt(currentIndex) || null
|
||||
|
||||
onResultsReceived: {
|
||||
loading = false
|
||||
mainItem.positionViewAtBeginning()
|
||||
}
|
||||
onSearchBarTextChanged: {
|
||||
loading = true
|
||||
if(!pauseSearch) {
|
||||
searchText = searchBarText.length === 0 ? "*" : searchBarText
|
||||
}
|
||||
}
|
||||
onPauseSearchChanged: {
|
||||
if(!pauseSearch){
|
||||
searchText = searchBarText.length === 0 ? "*" : searchBarText
|
||||
}
|
||||
}
|
||||
onAtYEndChanged: if(atYEnd) magicSearchProxy.displayMore()
|
||||
keyNavigationEnabled: false
|
||||
Keys.onPressed: (event)=> {
|
||||
if(header.activeFocus) return;
|
||||
if(event.key == Qt.Key_Up || event.key == Qt.Key_Down){
|
||||
if (currentIndex == 0 && event.key == Qt.Key_Up) {
|
||||
if( headerItem.list.count > 0) {
|
||||
mainItem.highlightFollowsCurrentItem = false
|
||||
currentIndex = -1
|
||||
headerItem.list.currentIndex = headerItem.list.count -1
|
||||
var item = headerItem.list.itemAtIndex(headerItem.list.currentIndex)
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
headerItem.updatePosition()
|
||||
event.accepted = true;
|
||||
}else{
|
||||
mainItem.currentIndex = mainItem.count - 1
|
||||
var item = itemAtIndex(mainItem.currentIndex)
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
event.accepted = true;
|
||||
}
|
||||
}else if(currentIndex >= mainItem.count -1 && event.key == Qt.Key_Down){
|
||||
if( headerItem.list.count > 0) {
|
||||
mainItem.highlightFollowsCurrentItem = false
|
||||
mainItem.currentIndex = -1
|
||||
headerItem.list.currentIndex = 0
|
||||
var item = headerItem.list.itemAtIndex(headerItem.list.currentIndex)
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
headerItem.updatePosition()
|
||||
event.accepted = true;
|
||||
}else{
|
||||
mainItem.currentIndex = 0
|
||||
var item = itemAtIndex(mainItem.currentIndex)
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
event.accepted = true;
|
||||
}
|
||||
}else if(event.key == Qt.Key_Up){
|
||||
mainItem.highlightFollowsCurrentItem = true
|
||||
var item = itemAtIndex(--mainItem.currentIndex)
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
event.accepted = true;
|
||||
}else if(event.key == Qt.Key_Down){
|
||||
mainItem.highlightFollowsCurrentItem = true
|
||||
var item = itemAtIndex(++mainItem.currentIndex)
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (confInfoGui) {
|
||||
for(var i = 0; i < confInfoGui.core.participants.length; ++i) {
|
||||
|
|
@ -99,33 +169,7 @@ ListView {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// strange behaviour with this lines
|
||||
// When a popup opens after clicking on a contact, the selected contact
|
||||
// changes because we lose focus on the list
|
||||
// onActiveFocusChanged: if(activeFocus && (!footerItem || !footerItem.activeFocus)) {
|
||||
// currentIndex = 0
|
||||
// }
|
||||
|
||||
model: MagicSearchProxy {
|
||||
id: magicSearchProxy
|
||||
searchText: mainItem.searchText
|
||||
// This property is needed instead of playing on the delegate visibility
|
||||
// considering its starred status. Otherwise, the row in the list still
|
||||
// exists even if its delegate is not visible, and creates navigation issues
|
||||
showFavoritesOnly: mainItem.showFavoritesOnly
|
||||
aggregationFlag: mainItem.aggregationFlag
|
||||
parentProxy: mainItem.listProxy
|
||||
showLdapContacts: mainItem.showLdapContacts
|
||||
sourceFlags: mainItem.sourceFlags
|
||||
onFriendCreated: (index) => {
|
||||
mainItem.currentIndex = index
|
||||
}
|
||||
onInitialized: {
|
||||
if(mainItem.searchOnInitialization) magicSearchProxy.forceUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: SettingsCpp
|
||||
onLdapConfigChanged: {
|
||||
|
|
@ -136,255 +180,302 @@ ListView {
|
|||
|
||||
Control.ScrollBar.vertical: ScrollBar {
|
||||
id: scrollbar
|
||||
rightPadding: 8 * DefaultStyle.dp
|
||||
topPadding: mainItem.haveFavorites ? 24 * DefaultStyle.dp : 0 // Avoid to be on top of collapse button
|
||||
|
||||
active: true
|
||||
interactive: true
|
||||
// anchors.top: parent.top
|
||||
// anchors.bottom: parent.bottom
|
||||
// anchors.right: parent.right
|
||||
policy: mainItem.contentHeight > mainItem.height ? Control.ScrollBar.AlwaysOn : Control.ScrollBar.AlwaysOff
|
||||
}
|
||||
Keys.onPressed: (event)=>{
|
||||
if(event.key == Qt.Key_Tab && !mainItem.itemAtIndex(mainItem.currentIndex).activeFocus){
|
||||
mainItem.itemAtIndex(mainItem.currentIndex).forceActiveFocus()
|
||||
|
||||
model: MagicSearchProxy {
|
||||
id: magicSearchProxy
|
||||
searchText: mainItem.searchText
|
||||
aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend
|
||||
sourceFlags: mainItem.sourceFlags
|
||||
|
||||
hideSuggestions: mainItem.hideSuggestions
|
||||
initialDisplayItems: 20
|
||||
onLocalFriendCreated: (index) => {
|
||||
var item = itemAtIndex(index)
|
||||
if(item){
|
||||
mainItem.currentIndex = index
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
onInitialized: {
|
||||
mainItem.loading = true
|
||||
magicSearchProxy.forceUpdate()
|
||||
}
|
||||
onModelReset: mainItem.resultsReceived()
|
||||
}
|
||||
delegate: FocusScope {
|
||||
id: itemDelegate
|
||||
height: 56 * DefaultStyle.dp
|
||||
width: mainItem.width
|
||||
property var previousItem : mainItem.model.count > 0 && index > 0 ? mainItem.model.getAt(index-1) : null
|
||||
property var previousDisplayName: previousItem ? previousItem.core.displayName : ""
|
||||
property var displayName: modelData.core.displayName
|
||||
|
||||
Connections {
|
||||
enabled: modelData.core
|
||||
target: modelData.core
|
||||
function onStarredChanged() { mainItem.contactStarredChanged()}
|
||||
|
||||
section.property: "isStored"
|
||||
//section.criteria: ViewSection.FirstCharacter
|
||||
section.delegate: Item{
|
||||
width: mainItem.width
|
||||
height: textItem.implicitHeight + sectionsSpacing * 2
|
||||
required property bool section
|
||||
Text {
|
||||
id: textItem
|
||||
anchors.fill: parent
|
||||
text: section ? qsTr("Contacts") : qsTr("Suggestions")
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font {
|
||||
pixelSize: sectionsPixelSize
|
||||
weight: sectionsWeight
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Text {
|
||||
id: initial
|
||||
anchors.left: parent.left
|
||||
visible: mainItem.initialHeadersVisible && mainItem.model.sourceFlags != LinphoneEnums.MagicSearchSource.All
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: 15 * DefaultStyle.dp
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
width: 20 * DefaultStyle.dp
|
||||
opacity: (!previousItem || !previousDisplayName.toLocaleLowerCase(ConstantsCpp.DefaultLocale).startsWith(displayName[0].toLocaleLowerCase(ConstantsCpp.DefaultLocale))) ? 1 : 0
|
||||
text: displayName[0]
|
||||
color: DefaultStyle.main2_400
|
||||
font {
|
||||
pixelSize: 20 * DefaultStyle.dp
|
||||
weight: 500 * DefaultStyle.dp
|
||||
capitalization: Font.AllUppercase
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
id: contactDelegate
|
||||
anchors.left: initial.visible ? initial.right : parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: 16 * DefaultStyle.dp
|
||||
z: 1
|
||||
Avatar {
|
||||
Layout.leftMargin: 5 * DefaultStyle.dp
|
||||
Layout.preferredWidth: 45 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||
contact: modelData
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
Text {
|
||||
text: UtilsCpp.boldTextPart(itemDelegate.displayName, mainItem.searchBarText)
|
||||
font{
|
||||
pixelSize: mainItem.showDefaultAddress ? 16 * DefaultStyle.dp : 14 * DefaultStyle.dp
|
||||
capitalization: mainItem.displayNameCapitalization ? Font.Capitalize : Font.MixedCase
|
||||
weight: mainItem.showDefaultAddress ? 800 * DefaultStyle.dp : 400 * DefaultStyle.dp
|
||||
}
|
||||
maximumLineCount: 1
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Text {
|
||||
maximumLineCount: 1
|
||||
visible: mainItem.showDefaultAddress
|
||||
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(modelData.core.defaultAddress) : modelData.core.defaultAddress
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 2 * DefaultStyle.dp
|
||||
font {
|
||||
weight: 300 * DefaultStyle.dp
|
||||
pixelSize: 12 * DefaultStyle.dp
|
||||
header: FocusScope{
|
||||
id: headerItem
|
||||
width: mainItem.width
|
||||
height: favoritesContents.implicitHeight
|
||||
property alias list: favoriteList
|
||||
|
||||
// Hack because changing currentindex change focus.
|
||||
Timer{
|
||||
id: focusDelay
|
||||
interval: 10
|
||||
onTriggered: {
|
||||
mainItem.highlightFollowsCurrentItem = !headerItem.activeFocus
|
||||
}
|
||||
}
|
||||
}
|
||||
Item{Layout.fillWidth: true}
|
||||
RowLayout {
|
||||
id: actionsRow
|
||||
z: 1
|
||||
visible: actionButtons || friendPopup.visible || mainItem.multiSelectionEnabled
|
||||
spacing: visible ? 16 * DefaultStyle.dp : 0
|
||||
EffectImage {
|
||||
id: isSelectedCheck
|
||||
// visible: mainItem.multiSelectionEnabled && (mainItem.confInfoGui.core.getParticipantIndex(modelData.core.defaultAddress) != -1)
|
||||
visible: mainItem.multiSelectionEnabled && (mainItem.selectedContacts.indexOf(modelData.core.defaultAddress) != -1)
|
||||
Layout.preferredWidth: 24 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 24 * DefaultStyle.dp
|
||||
imageSource: AppIcons.check
|
||||
colorizationColor: DefaultStyle.main1_500_main
|
||||
Connections {
|
||||
target: mainItem
|
||||
// onParticipantsChanged: isSelectedCheck.visible = mainItem.confInfoGui.core.getParticipantIndex(modelData.core.defaultAddress) != -1
|
||||
function onSelectedContactCountChanged(){ isSelectedCheck.visible = (mainItem.selectedContacts.indexOf(modelData.core.defaultAddress) != -1)}
|
||||
onActiveFocusChanged:focusDelay.restart()
|
||||
//---------------------------------------------------
|
||||
|
||||
function updatePosition(){
|
||||
var item = favoriteList.itemAtIndex(favoriteList.currentIndex)
|
||||
if( item){
|
||||
// For debugging just in case
|
||||
//var listPosition = item.mapToItem(favoriteList, item.x, item.y)
|
||||
//var newPosition = favoriteList.mapToItem(mainItem, listPosition.x, listPosition.y)
|
||||
//console.log("item pos: " +item.x + " / " +item.y)
|
||||
//console.log("fav pos: " +favoriteList.x + " / " +favoriteList.y)
|
||||
//console.log("fav content: " +favoriteList.contentX + " / " +favoriteList.contentY)
|
||||
//console.log("main pos: " +mainItem.x + " / " +mainItem.y)
|
||||
//console.log("main content: " +mainItem.contentX + " / " +mainItem.contentY)
|
||||
//console.log("list pos: " +listPosition.x + " / " +listPosition.y)
|
||||
//console.log("new pos: " +newPosition.x + " / " +newPosition.y)
|
||||
//console.log("header pos: " +headerItem.x + " / " +headerItem.y)
|
||||
//console.log("Moving to " + (headerItem.y+item.y))
|
||||
mainItem.contentY = headerItem.y+item.y
|
||||
}
|
||||
}
|
||||
RowLayout{
|
||||
id: actionButtons
|
||||
visible: mainItem.actionLayoutVisible
|
||||
spacing: visible ? 10 * DefaultStyle.dp : 0
|
||||
Button {
|
||||
id: callButton
|
||||
Layout.preferredWidth: 45 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||
icon.width: 24 * DefaultStyle.dp
|
||||
icon.height: 24 * DefaultStyle.dp
|
||||
icon.source: AppIcons.phone
|
||||
contentImageColor: DefaultStyle.main2_600
|
||||
focus: visible
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 40 * DefaultStyle.dp
|
||||
color: DefaultStyle.main2_200
|
||||
}
|
||||
onClicked: UtilsCpp.createCall(modelData.core.defaultAddress)
|
||||
KeyNavigation.right: chatButton
|
||||
KeyNavigation.left: chatButton
|
||||
}
|
||||
Button {
|
||||
id: chatButton
|
||||
visible: actionButtons.visible && !SettingsCpp.disableChatFeature
|
||||
Layout.preferredWidth: 45 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||
icon.width: 24 * DefaultStyle.dp
|
||||
icon.height: 24 * DefaultStyle.dp
|
||||
icon.source: AppIcons.chatTeardropText
|
||||
focus: visible && !callButton.visible
|
||||
contentImageColor: DefaultStyle.main2_500main
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 40 * DefaultStyle.dp
|
||||
color: DefaultStyle.main2_200
|
||||
}
|
||||
KeyNavigation.right: callButton
|
||||
KeyNavigation.left: callButton
|
||||
}
|
||||
}
|
||||
PopupButton {
|
||||
id: friendPopup
|
||||
z: 1
|
||||
// Layout.rightMargin: 13 * DefaultStyle.dp
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.rightMargin: 8 * DefaultStyle.dp
|
||||
popup.x: 0
|
||||
popup.padding: 10 * DefaultStyle.dp
|
||||
hoverEnabled: mainItem.hoverEnabled
|
||||
visible: mainItem.contactMenuVisible && (contactArea.containsMouse || hovered || popup.opened) && (!mainItem.delegateButtons || mainItem.delegateButtons.length === 0)
|
||||
|
||||
popup.contentItem: ColumnLayout {
|
||||
Button {
|
||||
text: $modelData.core.starred ? qsTr("Enlever des favoris") : qsTr("Mettre en favori")
|
||||
background: Item{}
|
||||
icon.source: modelData.core.starred ? AppIcons.heartFill : AppIcons.heart
|
||||
icon.width: 24 * DefaultStyle.dp
|
||||
icon.height: 24 * DefaultStyle.dp
|
||||
spacing: 10 * DefaultStyle.dp
|
||||
textSize: 14 * DefaultStyle.dp
|
||||
textWeight: 400 * DefaultStyle.dp
|
||||
textColor: DefaultStyle.main2_500main
|
||||
contentImageColor: modelData.core.starred ? DefaultStyle.danger_500main : DefaultStyle.main2_600
|
||||
onClicked: {
|
||||
modelData.core.lSetStarred(!modelData.core.starred)
|
||||
friendPopup.close()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: favoritesContents
|
||||
width: parent.width
|
||||
spacing: mainItem.haveFavorites ? sectionsSpacing : 0
|
||||
BusyIndicator {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.preferredHeight: visible ? 60 * DefaultStyle.dp : 0
|
||||
Layout.preferredWidth: 60 * DefaultStyle.dp
|
||||
indicatorHeight: 60 * DefaultStyle.dp
|
||||
indicatorWidth: 60 * DefaultStyle.dp
|
||||
visible: mainItem.loading
|
||||
indicatorColor: DefaultStyle.main1_500_main
|
||||
|
||||
}
|
||||
Item{// Do not use directly RowLayout : there is an issue where the layout doesn't update on visible
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: mainItem.haveFavorites ? favoriteTitle.implicitHeight : 0
|
||||
RowLayout {
|
||||
id: favoriteTitle
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// Need this because it can stay at 0 on display without manual relayouting (moving position, resize)
|
||||
visible: mainItem.haveFavorites
|
||||
onVisibleChanged: if(visible) {
|
||||
Qt.callLater(mainItem.positionViewAtBeginning)// If not later, the view will not move to favoris at startup
|
||||
}
|
||||
Text {
|
||||
//Layout.fillHeight: true
|
||||
text: qsTr("Favoris")
|
||||
font {
|
||||
pixelSize: sectionsPixelSize
|
||||
weight: sectionsWeight
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button {
|
||||
id: favoriteExpandButton
|
||||
background: Item{}
|
||||
icon.source: favoriteList.visible ? AppIcons.upArrow : AppIcons.downArrow
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height
|
||||
//Layout.preferredWidth: 24 * DefaultStyle.dp
|
||||
//Layout.preferredHeight: 24 * DefaultStyle.dp
|
||||
Layout.rightMargin: 23 * DefaultStyle.dp
|
||||
icon.width: 24 * DefaultStyle.dp
|
||||
icon.height: 24 * DefaultStyle.dp
|
||||
focus: true
|
||||
onClicked: favoriteList.visible = !favoriteList.visible
|
||||
KeyNavigation.down: favoriteList
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: qsTr("Partager")
|
||||
background: Item{}
|
||||
icon.source: AppIcons.shareNetwork
|
||||
icon.width: 24 * DefaultStyle.dp
|
||||
icon.height: 24 * DefaultStyle.dp
|
||||
spacing: 10 * DefaultStyle.dp
|
||||
textSize: 14 * DefaultStyle.dp
|
||||
textWeight: 400 * DefaultStyle.dp
|
||||
textColor: DefaultStyle.main2_500main
|
||||
onClicked: {
|
||||
var vcard = modelData.core.getVCard()
|
||||
var username = modelData.core.givenName + modelData.core.familyName
|
||||
var filepath = UtilsCpp.createVCardFile(username, vcard)
|
||||
if (filepath == "") UtilsCpp.showInformationPopup(qsTr("Erreur"), qsTr("La création du fichier vcard a échoué"), false)
|
||||
else mainWindow.showInformationPopup(qsTr("VCard créée"), qsTr("VCard du contact enregistrée dans %1").arg(filepath))
|
||||
UtilsCpp.shareByEmail(qsTr("Partage de contact"), vcard, filepath)
|
||||
}
|
||||
ListView{
|
||||
id: favoriteList
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: count > 0 ? contentHeight : 0// Show full and avoid scrolling
|
||||
|
||||
|
||||
|
||||
onCountChanged: mainItem.haveFavorites = count > 0
|
||||
Keys.onPressed: (event)=> {
|
||||
if(event.key == Qt.Key_Up || event.key == Qt.Key_Down) {
|
||||
if (favoriteList.currentIndex == 0 && event.key == Qt.Key_Up) {
|
||||
if( mainItem.count > 0) {
|
||||
mainItem.highlightFollowsCurrentItem = true
|
||||
favoriteList.currentIndex = -1
|
||||
mainItem.currentIndex = mainItem.count-1
|
||||
var item = mainItem.itemAtIndex(mainItem.currentIndex)
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
event.accepted = true;
|
||||
}else{
|
||||
favoriteList.currentIndex = favoriteList.count - 1
|
||||
var item = itemAtIndex(favoriteList.currentIndex)
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
event.accepted = true;
|
||||
}
|
||||
}else if(currentIndex >= favoriteList.count -1 && event.key == Qt.Key_Down) {
|
||||
if( mainItem.count > 0) {
|
||||
mainItem.highlightFollowsCurrentItem = true
|
||||
favoriteList.currentIndex = -1
|
||||
mainItem.currentIndex = 0
|
||||
var item = mainItem.itemAtIndex(mainItem.currentIndex)
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
event.accepted = true;
|
||||
}else{
|
||||
favoriteList.currentIndex = 0
|
||||
var item = itemAtIndex(favoriteList.currentIndex)
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
event.accepted = true;
|
||||
}
|
||||
}else if(event.key == Qt.Key_Up){
|
||||
mainItem.highlightFollowsCurrentItem = false
|
||||
var item = itemAtIndex(--favoriteList.currentIndex)
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
headerItem.updatePosition()
|
||||
event.accepted = true;
|
||||
}else if(event.key == Qt.Key_Down){
|
||||
mainItem.highlightFollowsCurrentItem = false
|
||||
var item = itemAtIndex(++favoriteList.currentIndex)
|
||||
mainItem.selectedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
headerItem.updatePosition()
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: qsTr("Supprimer")
|
||||
background: Item{}
|
||||
icon.source: AppIcons.trashCan
|
||||
icon.width: 24 * DefaultStyle.dp
|
||||
icon.height: 24 * DefaultStyle.dp
|
||||
spacing: 10 * DefaultStyle.dp
|
||||
textSize: 14 * DefaultStyle.dp
|
||||
textWeight: 400 * DefaultStyle.dp
|
||||
textColor: DefaultStyle.danger_500main
|
||||
contentImageColor: DefaultStyle.danger_500main
|
||||
visible: !modelData.core.readOnly
|
||||
onClicked: {
|
||||
mainItem.contactDeletionRequested(modelData)
|
||||
friendPopup.close()
|
||||
property MagicSearchProxy proxy: MagicSearchProxy{
|
||||
parentProxy: mainItem.model
|
||||
showFavoritesOnly: true
|
||||
hideSuggestions: mainItem.hideSuggestions
|
||||
}
|
||||
model : showFavorites && mainItem.searchBarText == '' ? proxy : []
|
||||
delegate: ContactListItem{
|
||||
width: favoriteList.width
|
||||
focus: true
|
||||
|
||||
searchResultItem: $modelData
|
||||
showInitials: mainItem.showInitials
|
||||
showDefaultAddress: mainItem.showDefaultAddress
|
||||
showActions: mainItem.showActions
|
||||
showContactMenu: mainItem.showContactMenu
|
||||
highlightText: mainItem.highlightText
|
||||
|
||||
displayNameCapitalization: mainItem.displayNameCapitalization
|
||||
itemsRightMargin: mainItem.itemsRightMargin
|
||||
selectionEnabled: mainItem.selectionEnabled
|
||||
multiSelectionEnabled: mainItem.multiSelectionEnabled
|
||||
selectedContacts: mainItem.selectedContacts
|
||||
isSelected: mainItem.selectedContact && mainItem.selectedContact.core == searchResultItem.core
|
||||
previousInitial: ''//favoriteList.count > 0 ? favoriteList.itemAtIndex(index-1)?.initial : '' // Binding on count
|
||||
initial: '' // Hide initials but keep space
|
||||
|
||||
onIsSelectedChanged: if(isSelected) favoriteList.currentIndex = index
|
||||
onContactStarredChanged: mainItem.contactStarredChanged()
|
||||
onContactDeletionRequested: (contact) => mainItem.contactDeletionRequested(contact)
|
||||
onClicked: (mouse) => {
|
||||
mainItem.highlightFollowsCurrentItem = false
|
||||
favoriteList.currentIndex = index
|
||||
mainItem.selectedContact = searchResultItem
|
||||
forceActiveFocus()
|
||||
headerItem.updatePosition()
|
||||
if (mainItem.multiSelectionEnabled) {
|
||||
var indexInSelection = mainItem.selectedContacts.indexOf(searchResultItem.core.defaultAddress)
|
||||
if (indexInSelection == -1) {
|
||||
mainItem.addContactToSelection(searchResultItem.core.defaultAddress)
|
||||
}
|
||||
else {
|
||||
mainItem.removeContactFromSelection(indexInSelection, 1)
|
||||
}
|
||||
}
|
||||
mainItem.contactClicked(searchResultItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ContactListItem{
|
||||
id: contactItem
|
||||
width: mainItem.width
|
||||
focus: true
|
||||
|
||||
MouseArea {
|
||||
id: contactArea
|
||||
enabled: mainItem.selectionEnabled
|
||||
hoverEnabled: mainItem.hoverEnabled
|
||||
anchors.fill: contactDelegate
|
||||
height: mainItem.height
|
||||
acceptedButtons: Qt.AllButtons
|
||||
z: -1
|
||||
focus: !actionButtons.visible
|
||||
Rectangle {
|
||||
anchors.fill: contactArea
|
||||
opacity: 0.7
|
||||
radius: 8 * DefaultStyle.dp
|
||||
color: mainItem.currentIndex === index ? DefaultStyle.main2_200 : DefaultStyle.main2_100
|
||||
visible: contactArea.containsMouse || friendPopup.hovered || mainItem.currentIndex === index
|
||||
}
|
||||
Keys.onPressed: (event)=> {
|
||||
if (event.key == Qt.Key_Space || event.key == Qt.Key_Enter || event.key == Qt.Key_Return) {
|
||||
contactArea.clicked(undefined)
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
onClicked: (mouse) => {
|
||||
if (mouse && mouse.button == Qt.RightButton) {
|
||||
friendPopup.open()
|
||||
} else {
|
||||
mainItem.forceActiveFocus()
|
||||
mainItem.currentIndex = index
|
||||
if (mainItem.multiSelectionEnabled) {
|
||||
var indexInSelection = mainItem.selectedContacts.indexOf(modelData.core.defaultAddress)
|
||||
if (indexInSelection == -1) {
|
||||
mainItem.addContactToSelection(modelData.core.defaultAddress)
|
||||
}
|
||||
else {
|
||||
mainItem.removeContactFromSelection(indexInSelection, 1)
|
||||
}
|
||||
}
|
||||
mainItem.contactClicked(modelData)
|
||||
}
|
||||
}
|
||||
searchResultItem: $modelData
|
||||
showInitials: mainItem.showInitials && searchResultItem.core.isStored
|
||||
showDefaultAddress: mainItem.showDefaultAddress
|
||||
showActions: mainItem.showActions
|
||||
showContactMenu: searchResultItem.core.isStored
|
||||
highlightText: mainItem.highlightText
|
||||
|
||||
displayNameCapitalization: mainItem.displayNameCapitalization
|
||||
itemsRightMargin: mainItem.itemsRightMargin
|
||||
|
||||
selectionEnabled: mainItem.selectionEnabled
|
||||
multiSelectionEnabled: mainItem.multiSelectionEnabled
|
||||
selectedContacts: mainItem.selectedContacts
|
||||
isSelected: mainItem.selectedContact && mainItem.selectedContact.core == searchResultItem.core
|
||||
previousInitial: mainItem.itemAtIndex(index-1)?.initial
|
||||
|
||||
onIsSelectedChanged: if(isSelected) mainItem.currentIndex = index
|
||||
onContactStarredChanged: mainItem.contactStarredChanged()
|
||||
onContactDeletionRequested: (contact) => mainItem.contactDeletionRequested(contact)
|
||||
onClicked: (mouse) => {
|
||||
mainItem.highlightFollowsCurrentItem = true
|
||||
if (mouse && mouse.button == Qt.RightButton) {
|
||||
friendPopup.open()
|
||||
} else {
|
||||
forceActiveFocus()
|
||||
if(mainItem.selectedContact && mainItem.selectedContact.core != contactItem.searchResultItem.core)
|
||||
headerItem.list.currentIndex = -1
|
||||
mainItem.selectedContact = contactItem.searchResultItem
|
||||
if (mainItem.multiSelectionEnabled) {
|
||||
var indexInSelection = mainItem.selectedContacts.indexOf(searchResultItem.core.defaultAddress)
|
||||
if (indexInSelection == -1) {
|
||||
mainItem.addContactToSelection(searchResultItem.core.defaultAddress)
|
||||
}
|
||||
else {
|
||||
mainItem.removeContactFromSelection(indexInSelection, 1)
|
||||
}
|
||||
}
|
||||
mainItem.contactClicked(searchResultItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,14 @@ FocusScope {
|
|||
property int textInputWidth: 350 * DefaultStyle.dp
|
||||
property color borderColor: "transparent"
|
||||
property color focusedBorderColor: DefaultStyle.main2_500main
|
||||
property string text: textField.text
|
||||
property string text: textField.searchText
|
||||
property bool magnifierVisible: true
|
||||
property var validator: RegularExpressionValidator{}
|
||||
property Control.Popup numericPadPopup
|
||||
property alias numericPadButton: dialerButton
|
||||
readonly property bool hasActiveFocus: textField.activeFocus
|
||||
property alias color: backgroundItem.color
|
||||
property bool delaySearch: true // Wait some idle time after typing to start searching
|
||||
|
||||
signal openNumericPadRequested()// Useful for redirection before displaying numeric pad.
|
||||
|
||||
|
|
@ -62,6 +63,9 @@ FocusScope {
|
|||
anchors.leftMargin: magnifier.visible ? 0 : 10 * DefaultStyle.dp
|
||||
anchors.right: clearTextButton.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
property string searchText
|
||||
|
||||
focus: true
|
||||
placeholderText: mainItem.placeholderText
|
||||
placeholderTextColor: mainItem.placeholderTextColor
|
||||
|
|
@ -75,6 +79,7 @@ FocusScope {
|
|||
color: DefaultStyle.main2_600
|
||||
selectByMouse: true
|
||||
validator: mainItem.validator
|
||||
onTextChanged: mainItem.delaySearch ? delayTimer.restart() : searchText = text
|
||||
background: Item {
|
||||
opacity: 0.
|
||||
}
|
||||
|
|
@ -83,6 +88,12 @@ FocusScope {
|
|||
color: DefaultStyle.main2_500main
|
||||
width: 1 * DefaultStyle.dp
|
||||
}
|
||||
Timer{
|
||||
id: delayTimer
|
||||
interval: 300
|
||||
repeat: false
|
||||
onTriggered: textField.searchText = textField.text
|
||||
}
|
||||
}
|
||||
Button {
|
||||
id: dialerButton
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ FocusScope {
|
|||
signal transferCallToAnotherRequested(CallGui dest)
|
||||
signal contactClicked(FriendGui contact)
|
||||
clip: true
|
||||
onVisibleChanged: if (numPadPopup.opened) numPadPopup.close()
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
|
@ -71,7 +70,7 @@ FocusScope {
|
|||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 39 * DefaultStyle.dp
|
||||
Layout.maximumWidth: mainItem.width
|
||||
//Layout.maximumWidth: mainItem.width
|
||||
focus: true
|
||||
color: mainItem.searchBarColor
|
||||
borderColor: mainItem.searchBarBorderColor
|
||||
|
|
@ -79,127 +78,72 @@ FocusScope {
|
|||
numericPadPopup: mainItem.numPadPopup
|
||||
KeyNavigation.down: grouCallButton
|
||||
}
|
||||
Flickable {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: 25 * DefaultStyle.dp
|
||||
contentWidth: width
|
||||
contentHeight: content.height
|
||||
clip: true
|
||||
Control.ScrollBar.vertical: ScrollBar {
|
||||
active: true
|
||||
interactive: true
|
||||
policy: Control.ScrollBar.AsNeeded
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8 * DefaultStyle.dp
|
||||
ColumnLayout {
|
||||
id: content
|
||||
spacing: 32 * DefaultStyle.dp
|
||||
Button {
|
||||
id: grouCallButton
|
||||
visible: mainItem.groupCallVisible && !SettingsCpp.disableMeetingsFeature
|
||||
Layout.preferredWidth: 320 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 44 * DefaultStyle.dp
|
||||
padding: 0
|
||||
KeyNavigation.up: searchBar
|
||||
KeyNavigation.down: contactLoader.item
|
||||
onClicked: mainItem.groupCallCreationRequested()
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 50 * DefaultStyle.dp
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0.0; color: DefaultStyle.main2_100}
|
||||
GradientStop { position: 1.0; color: DefaultStyle.grey_0}
|
||||
}
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
spacing: 16 * DefaultStyle.dp
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Image {
|
||||
source: AppIcons.groupCall
|
||||
Layout.preferredWidth: 44 * DefaultStyle.dp
|
||||
sourceSize.width: 44 * DefaultStyle.dp
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
Text {
|
||||
text: "Appel de groupe"
|
||||
color: DefaultStyle.grey_1000
|
||||
font {
|
||||
pixelSize: 16 * DefaultStyle.dp
|
||||
weight: 800 * DefaultStyle.dp
|
||||
underline: grouCallButton.shadowEnabled
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Image {
|
||||
source: AppIcons.rightArrow
|
||||
Layout.preferredWidth: 24 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 24 * DefaultStyle.dp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
spacing: 32 * DefaultStyle.dp
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 39 * DefaultStyle.dp
|
||||
Button {
|
||||
id: grouCallButton
|
||||
visible: mainItem.groupCallVisible && !SettingsCpp.disableMeetingsFeature
|
||||
Layout.preferredWidth: 320 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 44 * DefaultStyle.dp
|
||||
padding: 0
|
||||
KeyNavigation.up: searchBar
|
||||
KeyNavigation.down: contactList.count >0 ? contactList : searchList
|
||||
onClicked: mainItem.groupCallCreationRequested()
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 50 * DefaultStyle.dp
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0.0; color: DefaultStyle.main2_100}
|
||||
GradientStop { position: 1.0; color: DefaultStyle.grey_0}
|
||||
}
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
spacing: 16 * DefaultStyle.dp
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Image {
|
||||
source: AppIcons.groupCall
|
||||
Layout.preferredWidth: 44 * DefaultStyle.dp
|
||||
sourceSize.width: 44 * DefaultStyle.dp
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
Text {
|
||||
text: "Appel de groupe"
|
||||
color: DefaultStyle.grey_1000
|
||||
font {
|
||||
pixelSize: 16 * DefaultStyle.dp
|
||||
weight: 800 * DefaultStyle.dp
|
||||
underline: grouCallButton.shadowEnabled
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Image {
|
||||
source: AppIcons.rightArrow
|
||||
Layout.preferredWidth: 24 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 24 * DefaultStyle.dp
|
||||
}
|
||||
}
|
||||
Loader{
|
||||
// This is a hack for an incomprehensible behavior on sections title where they doesn't match with their delegate and can be unordered after resetting models.
|
||||
id: contactLoader
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
property string t: searchBar.text
|
||||
onTChanged: {
|
||||
contactLoader.active = false
|
||||
Qt.callLater(function(){contactLoader.active=true})
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 18 * DefaultStyle.dp
|
||||
visible: contactList.contentHeight > 0
|
||||
Text {
|
||||
text: qsTr("Contacts")
|
||||
font {
|
||||
pixelSize: 16 * DefaultStyle.dp
|
||||
weight: 800 * DefaultStyle.dp
|
||||
}
|
||||
//-------------------------------------------------------------
|
||||
sourceComponent: ContactListView{
|
||||
id: contactList
|
||||
searchBarText: searchBar.text
|
||||
onContactClicked: (contact) => {
|
||||
mainItem.contactClicked(contact)
|
||||
}
|
||||
ContactListView{
|
||||
id: contactList
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: contentHeight
|
||||
Control.ScrollBar.vertical.visible: false
|
||||
contactMenuVisible: false
|
||||
searchOnInitialization: true
|
||||
searchBarText: searchBar.text
|
||||
onContactClicked: (contact) => {
|
||||
mainItem.contactClicked(contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 18 * DefaultStyle.dp
|
||||
visible: searchList.count > 0
|
||||
Text {
|
||||
text: qsTr("Suggestions")
|
||||
font {
|
||||
pixelSize: 16 * DefaultStyle.dp
|
||||
weight: 800 * DefaultStyle.dp
|
||||
}
|
||||
}
|
||||
ContactListView{
|
||||
id: searchList
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredHeight: contentHeight
|
||||
contactMenuVisible: false
|
||||
Control.ScrollBar.vertical.visible: false
|
||||
initialHeadersVisible: false
|
||||
displayNameCapitalization: false
|
||||
searchBarText: searchBar.text
|
||||
sourceFlags: LinphoneEnums.MagicSearchSource.All
|
||||
hideListProxy: contactList.model
|
||||
onContactClicked: (contact) => {
|
||||
mainItem.contactClicked(contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ FocusScope{
|
|||
id: mainItem
|
||||
|
||||
property string placeHolderText: qsTr("Rechercher des contacts")
|
||||
property list<string> selectedParticipants: suggestionList.selectedContacts
|
||||
property list<string> selectedParticipants//: contactLoader.item ? contactLoader.item.selectedContacts
|
||||
property int selectedParticipantsCount: selectedParticipants.length
|
||||
property ConferenceInfoGui conferenceInfoGui
|
||||
property color searchBarColor: DefaultStyle.grey_100
|
||||
|
|
@ -30,7 +30,7 @@ FocusScope{
|
|||
Layout.preferredHeight: contentHeight
|
||||
Layout.maximumHeight: mainItem.height / 3
|
||||
width: mainItem.width
|
||||
model: suggestionList.selectedContacts
|
||||
model: mainItem.selectedParticipants
|
||||
clip: true
|
||||
focus: participantList.count > 0
|
||||
Keys.onPressed: (event) => {
|
||||
|
|
@ -40,7 +40,7 @@ FocusScope{
|
|||
}
|
||||
delegate: FocusScope {
|
||||
height: 56 * DefaultStyle.dp
|
||||
width: participantList.width - scrollbar.implicitWidth - 12 * DefaultStyle.dp
|
||||
width: participantList.width - scrollbar.implicitWidth - 28 * DefaultStyle.dp
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 10 * DefaultStyle.dp
|
||||
|
|
@ -67,7 +67,7 @@ FocusScope{
|
|||
icon.height: 24 * DefaultStyle.dp
|
||||
focus: true
|
||||
contentImageColor: DefaultStyle.main1_500_main
|
||||
onClicked: suggestionList.removeSelectedContactByAddress(modelData)
|
||||
onClicked: if(contactLoader.item) contactLoader.item.removeSelectedContactByAddress(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ FocusScope{
|
|||
}
|
||||
}
|
||||
SearchBar {
|
||||
id: searchbar
|
||||
id: searchBar
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 6 * DefaultStyle.dp
|
||||
Layout.rightMargin: 28 * DefaultStyle.dp
|
||||
|
|
@ -95,86 +95,49 @@ FocusScope{
|
|||
KeyNavigation.up: participantList.count > 0
|
||||
? participantList
|
||||
: nextItemInFocusChain(false)
|
||||
KeyNavigation.down: contactList
|
||||
KeyNavigation.down: contactLoader.item
|
||||
}
|
||||
Flickable {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
contentWidth: width
|
||||
contentHeight: content.height
|
||||
clip: true
|
||||
Control.ScrollBar.vertical: ScrollBar {
|
||||
id: contactsScrollBar
|
||||
active: true
|
||||
interactive: true
|
||||
policy: Control.ScrollBar.AsNeeded
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8 * DefaultStyle.dp
|
||||
ColumnLayout {
|
||||
id: content
|
||||
spacing: 15 * DefaultStyle.dp
|
||||
Text {
|
||||
visible: !contactLoader.item?.loading && contactLoader.item?.count === 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 137 * DefaultStyle.dp
|
||||
text: qsTr("Aucun contact%1").arg(searchBar.text.length !== 0 ? " correspondant" : "")
|
||||
font {
|
||||
pixelSize: 16 * DefaultStyle.dp
|
||||
weight: 800 * DefaultStyle.dp
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: contactsScrollBar.implicitWidth + 12 * DefaultStyle.dp
|
||||
Text {
|
||||
Layout.topMargin: 6 * DefaultStyle.dp
|
||||
text: qsTr("Contacts")
|
||||
font {
|
||||
pixelSize: 16 * DefaultStyle.dp
|
||||
weight: 800 * DefaultStyle.dp
|
||||
}
|
||||
Loader{
|
||||
// This is a hack for an incomprehensible behavior on sections title where they doesn't match with their delegate and can be unordered after resetting models.
|
||||
id: contactLoader
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
property string t: searchBar.text
|
||||
onTChanged: {
|
||||
contactLoader.active = false
|
||||
Qt.callLater(function(){contactLoader.active=true})
|
||||
}
|
||||
ContactListView {
|
||||
//-------------------------------------------------------------
|
||||
sourceComponent: ContactListView{
|
||||
id: contactList
|
||||
visible: contentHeight > 0 || searchbar.text.length > 0
|
||||
Layout.fillWidth: true
|
||||
// Layout.fillHeight: true
|
||||
Layout.topMargin: 8 * DefaultStyle.dp
|
||||
Layout.preferredHeight: contentHeight
|
||||
multiSelectionEnabled: true
|
||||
contactMenuVisible: false
|
||||
confInfoGui: mainItem.conferenceInfoGui
|
||||
searchBarText: searchbar.text
|
||||
searchOnInitialization: true
|
||||
onContactAddedToSelection: (address) => {
|
||||
suggestionList.addContactToSelection(address)
|
||||
}
|
||||
onContactRemovedFromSelection: (address) => suggestionList.removeSelectedContactByAddress(address)
|
||||
Control.ScrollBar.vertical.visible: false
|
||||
}
|
||||
Text {
|
||||
Layout.topMargin: 6 * DefaultStyle.dp
|
||||
text: qsTr("Suggestions")
|
||||
font {
|
||||
pixelSize: 16 * DefaultStyle.dp
|
||||
weight: 800 * DefaultStyle.dp
|
||||
}
|
||||
}
|
||||
ContactListView {
|
||||
id: suggestionList
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredHeight: contentHeight
|
||||
Control.ScrollBar.vertical.visible: false
|
||||
contactMenuVisible: false
|
||||
searchBarText: searchbar.text
|
||||
sourceFlags: LinphoneEnums.MagicSearchSource.All
|
||||
itemsRightMargin: 28 * DefaultStyle.dp
|
||||
multiSelectionEnabled: true
|
||||
displayNameCapitalization: false
|
||||
hideListProxy: contactList.model
|
||||
showContactMenu: false
|
||||
confInfoGui: mainItem.conferenceInfoGui
|
||||
selectedContacts: mainItem.selectedParticipants
|
||||
onSelectedContactsChanged: Qt.callLater(function(){mainItem.selectedParticipants = selectedContacts})
|
||||
searchBarText: searchBar.text
|
||||
onContactAddedToSelection: (address) => {
|
||||
contactList.addContactToSelection(address)
|
||||
participantList.positionViewAtEnd()
|
||||
}
|
||||
onContactRemovedFromSelection: (address) => contactList.removeSelectedContactByAddress(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Item {
|
||||
// Layout.fillHeight: true
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,8 +171,8 @@ Item {
|
|||
if (text.length != 0) listPopup.open()
|
||||
else listPopup.close()
|
||||
}
|
||||
KeyNavigation.down: contactList.count > 0 ? contactList : contactList.footerItem
|
||||
KeyNavigation.up: contactList.footerItem
|
||||
KeyNavigation.down: contactLoader.item?.count > 0 || !contactLoader.item?.footerItem? contactLoader.item : contactLoader.item?.footerItem
|
||||
KeyNavigation.up: contactLoader.item?.footerItem ? contactLoader.item?.footerItem : contactLoader.item
|
||||
|
||||
component MagicSearchButton: Button {
|
||||
id: button
|
||||
|
|
@ -196,13 +196,13 @@ Item {
|
|||
id: listPopup
|
||||
width: magicSearchBar.width
|
||||
property int maxHeight: 400 * DefaultStyle.dp
|
||||
property bool displayScrollbar: contactList.contentHeight + topPadding + bottomPadding> maxHeight
|
||||
height: Math.min(contactList.contentHeight + topPadding + bottomPadding, maxHeight)
|
||||
property bool displayScrollbar: contactLoader.item?.contentHeight + topPadding + bottomPadding> maxHeight
|
||||
height: Math.min(contactLoader.item?.contentHeight + topPadding + bottomPadding, maxHeight)
|
||||
y: magicSearchBar.height
|
||||
// closePolicy: Popup.NoAutoClose
|
||||
topPadding: 20 * DefaultStyle.dp
|
||||
bottomPadding: 20 * DefaultStyle.dp
|
||||
rightPadding: 20 * DefaultStyle.dp
|
||||
rightPadding: 10 * DefaultStyle.dp
|
||||
leftPadding: 20 * DefaultStyle.dp
|
||||
|
||||
background: Item {
|
||||
|
|
@ -213,7 +213,7 @@ Item {
|
|||
color: DefaultStyle.grey_0
|
||||
anchors.fill: parent
|
||||
border.color: DefaultStyle.main1_500_main
|
||||
border.width: contactList.activeFocus ? 2 : 0
|
||||
border.width: contactLoader.item?.activeFocus ? 2 : 0
|
||||
|
||||
}
|
||||
MultiEffect {
|
||||
|
|
@ -237,125 +237,138 @@ Item {
|
|||
|
||||
}
|
||||
}
|
||||
contentItem: ContactListView {
|
||||
id: contactList
|
||||
visible: magicSearchBar.text.length != 0
|
||||
Layout.preferredHeight: contentHeight
|
||||
contentItem: Loader{
|
||||
// This is a hack for an incomprehensible behavior on sections title where they doesn't match with their delegate and can be unordered after resetting models.
|
||||
id: contactLoader
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 5 * DefaultStyle.dp
|
||||
initialHeadersVisible: false
|
||||
contactMenuVisible: false
|
||||
actionLayoutVisible: true
|
||||
selectionEnabled: false
|
||||
showDefaultAddress: true
|
||||
showLdapContacts: true
|
||||
Control.ScrollBar.vertical: scrollbar
|
||||
searchText: magicSearchBar.text
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if(event.key == Qt.Key_Down){
|
||||
if(contactList.currentIndex == contactList.count -1) {
|
||||
contactList.currentIndex = -1
|
||||
contactList.footerItem.forceActiveFocus()
|
||||
event.accepted = true
|
||||
}
|
||||
} else if(event.key == Qt.Key_Up){
|
||||
if(contactList.currentIndex <= 0) {
|
||||
contactList.currentIndex = -1
|
||||
contactList.footerItem.forceActiveFocus()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
Layout.fillHeight: true
|
||||
property bool deactivate: false
|
||||
active: !deactivate && magicSearchBar.text != ''
|
||||
property string t: magicSearchBar.text
|
||||
onTChanged: {
|
||||
contactLoader.deactivate = true
|
||||
Qt.callLater(function(){contactLoader.deactivate=false})
|
||||
}
|
||||
header: Text {
|
||||
visible: contactList.count > 0
|
||||
text: qsTr("Contact")
|
||||
color: DefaultStyle.main2_500main
|
||||
font {
|
||||
pixelSize: 13 * DefaultStyle.dp
|
||||
weight: 700 * DefaultStyle.dp
|
||||
}
|
||||
}
|
||||
footer: FocusScope{
|
||||
id: suggestionFocusScope
|
||||
width: contactList.width
|
||||
height: visible ? content.implicitHeight : 0
|
||||
onActiveFocusChanged: if(activeFocus) contactList.positionViewAtEnd()
|
||||
visible: !contactList.haveAddress(suggestionText.text)
|
||||
Rectangle{
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: suggestionRow.implicitHeight
|
||||
color: suggestionFocusScope.activeFocus ? DefaultStyle.numericPadPressedButtonColor : 'transparent'
|
||||
}
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 5 * DefaultStyle.dp
|
||||
|
||||
spacing: 10 * DefaultStyle.dp
|
||||
Text {
|
||||
text: qsTr("Suggestion")
|
||||
color: DefaultStyle.main2_500main
|
||||
font {
|
||||
pixelSize: 13 * DefaultStyle.dp
|
||||
weight: 700 * DefaultStyle.dp
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if(contactList.count <= 0) return;
|
||||
if(event.key == Qt.Key_Down){
|
||||
contactList.currentIndex = 0
|
||||
//-------------------------------------------------------------
|
||||
sourceComponent: ContactListView {
|
||||
id: contactList
|
||||
visible: magicSearchBar.text.length != 0
|
||||
Layout.preferredHeight: item?.contentHeight
|
||||
Layout.fillWidth: true
|
||||
itemsRightMargin: 5 * DefaultStyle.dp //(Actions have already 10 of margin)
|
||||
showInitials: false
|
||||
showContactMenu: false
|
||||
showActions: true
|
||||
showFavorites: false
|
||||
selectionEnabled: false
|
||||
showDefaultAddress: true
|
||||
hideSuggestions: true
|
||||
|
||||
sectionsPixelSize: 13 * DefaultStyle.dp
|
||||
sectionsWeight: 700 * DefaultStyle.dp
|
||||
sectionsSpacing: 5 * DefaultStyle.dp
|
||||
|
||||
Control.ScrollBar.vertical: scrollbar
|
||||
searchBarText: magicSearchBar.text
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if(event.key == Qt.Key_Down){
|
||||
if(contactList.currentIndex == contactList.count -1) {
|
||||
contactList.currentIndex = -1
|
||||
contactList.footerItem.forceActiveFocus()
|
||||
event.accepted = true
|
||||
} else if(event.key == Qt.Key_Up){
|
||||
contactList.currentIndex = contactList.count - 1
|
||||
}
|
||||
} else if(event.key == Qt.Key_Up){
|
||||
if(contactList.currentIndex <= 0) {
|
||||
contactList.currentIndex = -1
|
||||
contactList.footerItem.forceActiveFocus()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
id: suggestionRow
|
||||
spacing: 10 * DefaultStyle.dp
|
||||
}
|
||||
|
||||
footer: FocusScope{
|
||||
id: suggestionFocusScope
|
||||
width: contactList.width
|
||||
height: visible ? content.implicitHeight : 0
|
||||
onActiveFocusChanged: if(activeFocus) contactList.positionViewAtEnd()
|
||||
visible: !contactList.haveAddress(suggestionText.text)
|
||||
Rectangle{
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: suggestionRow.implicitHeight
|
||||
color: suggestionFocusScope.activeFocus ? DefaultStyle.numericPadPressedButtonColor : 'transparent'
|
||||
}
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 5 * DefaultStyle.dp
|
||||
anchors.rightMargin: 15 * DefaultStyle.dp
|
||||
|
||||
Avatar {
|
||||
Layout.preferredWidth: 45 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||
_address: magicSearchBar.text
|
||||
}
|
||||
spacing: 10 * DefaultStyle.dp
|
||||
Text {
|
||||
id: suggestionText
|
||||
property var urlObj: UtilsCpp.interpretUrl(magicSearchBar.text)
|
||||
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(urlObj?.value) : urlObj?.value
|
||||
text: qsTr("Suggestion")
|
||||
color: DefaultStyle.main2_500main
|
||||
font {
|
||||
pixelSize: 12 * DefaultStyle.dp
|
||||
weight: 300 * DefaultStyle.dp
|
||||
pixelSize: 13 * DefaultStyle.dp
|
||||
weight: 700 * DefaultStyle.dp
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
MagicSearchButton {
|
||||
id: callButton
|
||||
Layout.preferredWidth: 45 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||
icon.source: AppIcons.phone
|
||||
focus: true
|
||||
onClicked: {
|
||||
UtilsCpp.createCall(magicSearchBar.text)
|
||||
magicSearchBar.clearText()
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if(contactList.count <= 0) return;
|
||||
if(event.key == Qt.Key_Down){
|
||||
contactList.currentIndex = 0
|
||||
event.accepted = true
|
||||
} else if(event.key == Qt.Key_Up){
|
||||
contactList.currentIndex = contactList.count - 1
|
||||
event.accepted = true
|
||||
}
|
||||
KeyNavigation.right: chatButton
|
||||
KeyNavigation.left: chatButton
|
||||
}
|
||||
MagicSearchButton {
|
||||
id: chatButton
|
||||
visible: !SettingsCpp.disableChatFeature
|
||||
Layout.preferredWidth: 45 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||
icon.source: AppIcons.chatTeardropText
|
||||
KeyNavigation.right: callButton
|
||||
KeyNavigation.left: callButton
|
||||
RowLayout {
|
||||
id: suggestionRow
|
||||
spacing: 10 * DefaultStyle.dp
|
||||
|
||||
Avatar {
|
||||
Layout.preferredWidth: 45 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||
_address: magicSearchBar.text
|
||||
}
|
||||
Text {
|
||||
id: suggestionText
|
||||
property var urlObj: UtilsCpp.interpretUrl(magicSearchBar.text)
|
||||
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(urlObj?.value) : urlObj?.value
|
||||
font {
|
||||
pixelSize: 12 * DefaultStyle.dp
|
||||
weight: 300 * DefaultStyle.dp
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
MagicSearchButton {
|
||||
id: callButton
|
||||
Layout.preferredWidth: 45 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||
icon.source: AppIcons.phone
|
||||
focus: true
|
||||
onClicked: {
|
||||
UtilsCpp.createCall(magicSearchBar.text)
|
||||
magicSearchBar.clearText()
|
||||
}
|
||||
KeyNavigation.right: chatButton
|
||||
KeyNavigation.left: chatButton
|
||||
}
|
||||
MagicSearchButton {
|
||||
id: chatButton
|
||||
visible: !SettingsCpp.disableChatFeature
|
||||
Layout.preferredWidth: 45 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||
icon.source: AppIcons.chatTeardropText
|
||||
KeyNavigation.right: callButton
|
||||
KeyNavigation.left: callButton
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ AbstractMainPage {
|
|||
|
||||
onNoItemButtonPressed: goToNewCall()
|
||||
|
||||
showDefaultItem: listStackView.currentItem && listStackView.currentItem.objectName == "historyListItem" && listStackView.currentItem.listView.count === 0
|
||||
showDefaultItem: listStackView.currentItem && listStackView.currentItem.objectName == "historyListItem" && listStackView.currentItem.listView.count === 0 || false
|
||||
|
||||
function goToNewCall() {
|
||||
if (listStackView.currentItem && listStackView.currentItem.objectName != "newCallItem") listStackView.push(newCallItem)
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ AbstractMainPage {
|
|||
|
||||
onVisibleChanged: if (!visible) {
|
||||
rightPanelStackView.clear()
|
||||
contactList.currentIndex = -1
|
||||
favoriteList.currentIndex = -1
|
||||
if(contactLoader.item) contactLoader.item.currentIndex = -1
|
||||
}
|
||||
|
||||
onSelectedContactChanged: {
|
||||
|
|
@ -52,13 +51,8 @@ AbstractMainPage {
|
|||
|
||||
// rightPanelStackView.initialItem: contactDetail
|
||||
|
||||
showDefaultItem: rightPanelStackView.depth == 0 && leftPanelNoItemText.visible && searchBar.text.length === 0
|
||||
showDefaultItem: rightPanelStackView.depth == 0 && contactLoader.item?.count === 0 && searchBar.text.length === 0
|
||||
|
||||
MagicSearchProxy {
|
||||
id: allFriends
|
||||
showLdapContacts: SettingsCpp.syncLdapContacts
|
||||
}
|
||||
|
||||
function deleteContact(contact) {
|
||||
if (!contact) return
|
||||
var mainWin = UtilsCpp.getMainWindow()
|
||||
|
|
@ -211,170 +205,60 @@ AbstractMainPage {
|
|||
spacing: 38 * DefaultStyle.dp
|
||||
SearchBar {
|
||||
id: searchBar
|
||||
visible: contactList.count != 0 || text.length !== 0
|
||||
visible: !contactLoader.item || contactLoader.item.loading || contactLoader.item.count != 0 || text.length !== 0
|
||||
Layout.leftMargin: leftPanel.leftMargin
|
||||
Layout.rightMargin: leftPanel.rightMargin
|
||||
Layout.topMargin: 18 * DefaultStyle.dp
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Rechercher un contact")
|
||||
KeyNavigation.up: createContactButton
|
||||
KeyNavigation.down: favoriteList.contentHeight > 0 ? favoriteExpandButton : contactExpandButton
|
||||
KeyNavigation.down: contactLoader.item
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Flickable {
|
||||
id: listLayout
|
||||
contentWidth: width
|
||||
contentHeight: content.height
|
||||
clip: true
|
||||
Control.ScrollBar.vertical: contactsScrollbar
|
||||
ColumnLayout {
|
||||
id: content
|
||||
spacing: 15 * DefaultStyle.dp
|
||||
Text {
|
||||
visible: contactLoader.item && !contactLoader.item.loading && contactLoader.item.count === 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 137 * DefaultStyle.dp
|
||||
text: qsTr("Aucun contact%1").arg(searchBar.text.length !== 0 ? " correspondant" : "")
|
||||
font {
|
||||
pixelSize: 16 * DefaultStyle.dp
|
||||
weight: 800 * DefaultStyle.dp
|
||||
}
|
||||
}
|
||||
Loader{
|
||||
// This is a hack for an incomprehensible behavior on sections title where they doesn't match with their delegate and can be unordered after resetting models.
|
||||
id: contactLoader
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
width: parent.width
|
||||
spacing: 15 * DefaultStyle.dp
|
||||
Text {
|
||||
id: leftPanelNoItemText
|
||||
visible: contactList.count === 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 137 * DefaultStyle.dp
|
||||
text: qsTr("Aucun contact%1").arg(searchBar.text.length !== 0 ? " correspondant" : "")
|
||||
font {
|
||||
pixelSize: 16 * DefaultStyle.dp
|
||||
weight: 800 * DefaultStyle.dp
|
||||
}
|
||||
Layout.leftMargin: 45 * DefaultStyle.dp
|
||||
property string t: searchBar.text
|
||||
active: leftPanel.visible
|
||||
onTChanged: {
|
||||
contactLoader.active = false
|
||||
Qt.callLater(function(){contactLoader.active=true})
|
||||
}
|
||||
//-------------------------------------------------------------
|
||||
sourceComponent: ContactListView{
|
||||
id: contactList
|
||||
searchBarText: searchBar.text
|
||||
hideSuggestions: true
|
||||
sourceFlags: LinphoneEnums.MagicSearchSource.Friends | LinphoneEnums.MagicSearchSource.FavoriteFriends | LinphoneEnums.MagicSearchSource.LdapServers
|
||||
|
||||
onSelectedContactChanged: {
|
||||
mainItem.selectedContact = selectedContact
|
||||
}
|
||||
ColumnLayout {
|
||||
visible: favoriteList.contentHeight > 0
|
||||
onVisibleChanged: if (visible && !favoriteList.visible) favoriteList.visible = true
|
||||
Layout.leftMargin: leftPanel.leftMargin
|
||||
Layout.rightMargin: leftPanel.rightMargin
|
||||
spacing: 18 * DefaultStyle.dp
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
Text {
|
||||
text: qsTr("Favoris")
|
||||
font {
|
||||
pixelSize: 16 * DefaultStyle.dp
|
||||
weight: 800 * DefaultStyle.dp
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button {
|
||||
id: favoriteExpandButton
|
||||
background: Item{}
|
||||
icon.source: favoriteList.visible ? AppIcons.upArrow : AppIcons.downArrow
|
||||
Layout.preferredWidth: 24 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 24 * DefaultStyle.dp
|
||||
icon.width: 24 * DefaultStyle.dp
|
||||
icon.height: 24 * DefaultStyle.dp
|
||||
onClicked: favoriteList.visible = !favoriteList.visible
|
||||
KeyNavigation.up: searchBar
|
||||
KeyNavigation.down: favoriteList
|
||||
}
|
||||
}
|
||||
ContactListView{
|
||||
id: favoriteList
|
||||
hoverEnabled: mainItem.leftPanelEnabled
|
||||
highlightFollowsCurrentItem: true
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: contentHeight
|
||||
Control.ScrollBar.vertical.visible: false
|
||||
showFavoritesOnly: true
|
||||
searchOnInitialization: true
|
||||
contactMenuVisible: true
|
||||
searchBarText: searchBar.text
|
||||
listProxy: allFriends
|
||||
onSelectedContactChanged: {
|
||||
if (selectedContact) {
|
||||
contactList.currentIndex = -1
|
||||
}
|
||||
mainItem.selectedContact = selectedContact
|
||||
}
|
||||
onContactDeletionRequested: (contact) => {
|
||||
mainItem.deleteContact(contact)
|
||||
}
|
||||
}
|
||||
onContactDeletionRequested: (contact) => {
|
||||
mainItem.deleteContact(contact)
|
||||
}
|
||||
ColumnLayout {
|
||||
visible: contactList.contentHeight > 0
|
||||
onVisibleChanged: if (visible && !contactList.visible) contactList.visible = true
|
||||
Layout.leftMargin: leftPanel.leftMargin
|
||||
Layout.rightMargin: leftPanel.rightMargin
|
||||
spacing: 16 * DefaultStyle.dp
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
Text {
|
||||
text: qsTr("Contacts")
|
||||
font {
|
||||
pixelSize: 16 * DefaultStyle.dp
|
||||
weight: 800 * DefaultStyle.dp
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button {
|
||||
id: contactExpandButton
|
||||
background: Item{}
|
||||
icon.source: contactList.visible ? AppIcons.upArrow : AppIcons.downArrow
|
||||
Layout.preferredWidth: 24 * DefaultStyle.dp
|
||||
Layout.preferredHeight: 24 * DefaultStyle.dp
|
||||
icon.width: 24 * DefaultStyle.dp
|
||||
icon.height: 24 * DefaultStyle.dp
|
||||
onClicked: contactList.visible = !contactList.visible
|
||||
KeyNavigation.up: favoriteList.visible ? favoriteList : searchBar
|
||||
KeyNavigation.down: contactList
|
||||
}
|
||||
}
|
||||
ContactListView{
|
||||
id: contactList
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: contentHeight
|
||||
Control.ScrollBar.vertical.visible: false
|
||||
hoverEnabled: mainItem.leftPanelEnabled
|
||||
contactMenuVisible: true
|
||||
searchOnInitialization: true
|
||||
highlightFollowsCurrentItem: true
|
||||
searchBarText: searchBar.text
|
||||
listProxy: allFriends
|
||||
onCountChanged: {
|
||||
if (initialFriendToDisplay.length !== 0) {
|
||||
if (selectContact(initialFriendToDisplay) != -1) initialFriendToDisplay = ""
|
||||
}
|
||||
}
|
||||
onSelectedContactChanged: {
|
||||
if (selectedContact) {
|
||||
favoriteList.currentIndex = -1
|
||||
}
|
||||
mainItem.selectedContact = selectedContact
|
||||
}
|
||||
onContactDeletionRequested: (contact) => {
|
||||
mainItem.deleteContact(contact)
|
||||
}
|
||||
Connections {
|
||||
target: contactList.model
|
||||
function onFriendCreated(index) {
|
||||
contactList.currentIndex = index
|
||||
}
|
||||
}
|
||||
onCountChanged: {
|
||||
if (initialFriendToDisplay.length !== 0) {
|
||||
if (selectContact(initialFriendToDisplay) != -1) initialFriendToDisplay = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ScrollBar {
|
||||
id: contactsScrollbar
|
||||
Layout.fillHeight: true
|
||||
Layout.rightMargin: 8 * DefaultStyle.dp
|
||||
height: listLayout.height
|
||||
active: true
|
||||
interactive: true
|
||||
policy: Control.ScrollBar.AsNeeded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -493,8 +377,6 @@ AbstractMainPage {
|
|||
spacing: 0
|
||||
Text {
|
||||
text: contactDetail.contactName
|
||||
Layout.fillWidth: true
|
||||
maximumLineCount: 1
|
||||
font {
|
||||
pixelSize: 29 * DefaultStyle.dp
|
||||
weight: 800 * DefaultStyle.dp
|
||||
|
|
@ -579,6 +461,7 @@ AbstractMainPage {
|
|||
model: mainItem.selectedContact ? mainItem.selectedContact.core.allAddresses : []
|
||||
}
|
||||
delegate: Item {
|
||||
property var listViewModelData: modelData
|
||||
width: addrList.width
|
||||
height: 46 * DefaultStyle.dp
|
||||
|
||||
|
|
@ -595,7 +478,7 @@ AbstractMainPage {
|
|||
Layout.fillWidth: true
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: modelData.label
|
||||
text: listViewModelData.label
|
||||
font {
|
||||
pixelSize: 13 * DefaultStyle.dp
|
||||
weight: 700 * DefaultStyle.dp
|
||||
|
|
@ -603,7 +486,7 @@ AbstractMainPage {
|
|||
}
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
property string _text: modelData.address
|
||||
property string _text: listViewModelData.address
|
||||
text: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(_text) : _text
|
||||
font {
|
||||
pixelSize: 14 * DefaultStyle.dp
|
||||
|
|
@ -624,7 +507,7 @@ AbstractMainPage {
|
|||
icon.width: 24 * DefaultStyle.dp
|
||||
icon.height: 24 * DefaultStyle.dp
|
||||
onClicked: {
|
||||
UtilsCpp.createCall(modelData.address)
|
||||
UtilsCpp.createCall(listViewModelData.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -761,16 +644,17 @@ AbstractMainPage {
|
|||
id: deviceDelegate
|
||||
width: deviceList.width
|
||||
height: 30 * DefaultStyle.dp
|
||||
property var listViewModelData: modelData
|
||||
property var callObj
|
||||
property CallGui deviceCall: callObj ? callObj.value : null
|
||||
property string deviceName: modelData.name.length != 0 ? modelData.name : qsTr("Appareil sans nom")
|
||||
property string deviceName: listViewModelData.name.length != 0 ? listViewModelData.name : qsTr("Appareil sans nom")
|
||||
Text {
|
||||
text: deviceDelegate.deviceName
|
||||
font.pixelSize: 14 * DefaultStyle.dp
|
||||
}
|
||||
Item{Layout.fillWidth: true}
|
||||
Image{
|
||||
visible: modelData.securityLevel === LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified
|
||||
visible: listViewModelData.securityLevel === LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified
|
||||
source: AppIcons.trusted
|
||||
width: 22 * DefaultStyle.dp
|
||||
height: 22 * DefaultStyle.dp
|
||||
|
|
@ -778,7 +662,7 @@ AbstractMainPage {
|
|||
|
||||
Button {
|
||||
Layout.preferredHeight: 30 * DefaultStyle.dp
|
||||
visible: modelData.securityLevel != LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified
|
||||
visible: listViewModelData.securityLevel != LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified
|
||||
color: DefaultStyle.main1_100
|
||||
icon.source: AppIcons.warningCircle
|
||||
icon.height: 14 * DefaultStyle.dp
|
||||
|
|
@ -794,12 +678,12 @@ AbstractMainPage {
|
|||
onClicked: {
|
||||
if (SettingsCpp.getDisplayDeviceCheckConfirmation()) {
|
||||
verifyDevicePopup.deviceName = deviceDelegate.deviceName
|
||||
verifyDevicePopup.deviceAddress = modelData.address
|
||||
verifyDevicePopup.deviceAddress = listViewModelData.address
|
||||
verifyDevicePopup.open()
|
||||
}
|
||||
else {
|
||||
UtilsCpp.createCall(modelData.address, {}, LinphoneEnums.MediaEncryption.Zrtp)
|
||||
parent.callObj = UtilsCpp.getCallByAddress(modelData.address)
|
||||
UtilsCpp.createCall(listViewModelData.address, {}, LinphoneEnums.MediaEncryption.Zrtp)
|
||||
parent.callObj = UtilsCpp.getCallByAddress(listViewModelData.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue