contact list

fixes:
generic VariantList
FriendModel resetAddresses
check null default account
address list update on save
generic item for white background lists
ui
fix set photo friend
protect friendmodel setters
remove main splitview to stick to the mock-up (keeping it commented cause it may be useful to be able to resize the panels)
default image avatar
fix crash when address not set
This commit is contained in:
Gaelle Braud 2024-01-18 11:52:01 +01:00
parent c33b35724a
commit 82b5d6a008
43 changed files with 2341 additions and 542 deletions

View file

@ -50,6 +50,7 @@
#include "core/phone-number/PhoneNumberProxy.hpp"
#include "core/search/MagicSearchProxy.hpp"
#include "core/singleapplication/singleapplication.h"
#include "core/variant/VariantList.hpp"
#include "model/object/VariantObject.hpp"
#include "tool/Constants.hpp"
#include "tool/EnumsToString.hpp"
@ -161,6 +162,7 @@ void App::initCppInterfaces() {
qmlRegisterUncreatableType<CallCore>(Constants::MainQmlUri, 1, 0, "CallCore", QLatin1String("Uncreatable"));
qmlRegisterType<CallProxy>(Constants::MainQmlUri, 1, 0, "CallProxy");
qmlRegisterType<CallHistoryProxy>(Constants::MainQmlUri, 1, 0, "CallHistoryProxy");
qmlRegisterType<VariantList>(Constants::MainQmlUri, 1, 0, "VariantList");
qmlRegisterType<CallGui>(Constants::MainQmlUri, 1, 0, "CallGui");
qmlRegisterType<FriendGui>(Constants::MainQmlUri, 1, 0, "FriendGui");
qmlRegisterUncreatableType<FriendCore>(Constants::MainQmlUri, 1, 0, "FriendCore", QLatin1String("Uncreatable"));

View file

@ -33,6 +33,8 @@ list(APPEND _LINPHONEAPP_SOURCES
core/proxy/ListProxy.cpp
core/proxy/Proxy.cpp
core/proxy/SortFilterProxy.cpp
core/variant/VariantList.cpp
)
## Single Application

View file

@ -61,7 +61,10 @@ void CallHistoryList::setSelf(QSharedPointer<CallHistoryList> me) {
// Avoid copy to lambdas
QList<QSharedPointer<CallHistoryCore>> *callLogs = new QList<QSharedPointer<CallHistoryCore>>();
mustBeInLinphoneThread(getClassName());
auto linphoneCallLogs = CoreModel::getInstance()->getCore()->getCallLogs();
std::list<std::shared_ptr<linphone::CallLog>> linphoneCallLogs;
if (auto account = CoreModel::getInstance()->getCore()->getDefaultAccount()) {
linphoneCallLogs = account->getCallLogs();
}
for (auto it : linphoneCallLogs) {
auto model = createCallHistoryCore(it);
callLogs->push_back(model);
@ -74,7 +77,8 @@ void CallHistoryList::setSelf(QSharedPointer<CallHistoryList> me) {
});
});
});
mModelConnection->makeConnectToModel(&CoreModel::defaultAccountChanged,
[this]() { mModelConnection->invokeToCore([this]() { lUpdate(); }); });
mModelConnection->makeConnectToModel(&CoreModel::callLogUpdated,
[this]() { mModelConnection->invokeToCore([this]() { lUpdate(); }); });
lUpdate();

View file

@ -122,7 +122,6 @@ signals:
void stateChanged(LinphoneEnums::CallState state);
void dirChanged(LinphoneEnums::CallDir dir);
void lastErrorMessageChanged();
void peerAddressChanged();
void durationChanged(int duration);
void speakerMutedChanged();
void microphoneMutedChanged();

View file

@ -20,13 +20,23 @@
#include "FriendCore.hpp"
#include "core/App.hpp"
#include "model/object/VariantObject.hpp"
#include "core/proxy/ListProxy.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/Utils.hpp"
#include "tool/thread/SafeConnection.hpp"
DEFINE_ABSTRACT_OBJECT(FriendCore)
const QString addressLabel = FriendCore::tr("Adresse SIP");
const QString phoneLabel = FriendCore::tr("Téléphone");
QVariant createFriendAddressVariant(const QString &label, const QString &address) {
QVariantMap map;
map.insert("label", label);
map.insert("address", address);
return map;
}
QSharedPointer<FriendCore> FriendCore::create(const std::shared_ptr<linphone::Friend> &contact) {
auto sharedPointer = QSharedPointer<FriendCore>(new FriendCore(contact), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
@ -43,28 +53,51 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObje
mConsolidatedPresence = LinphoneEnums::fromLinphone(contact->getConsolidatedPresence());
mPresenceTimestamp = mFriendModel->getPresenceTimestamp();
mPictureUri = Utils::coreStringToAppString(contact->getPhoto());
auto address = contact->getAddress();
mAddress = address ? Utils::coreStringToAppString(contact->getAddress()->asStringUriOnly()) : "NoAddress";
auto name = contact->getName();
mName =
name.empty() ? Utils::getDisplayName(mAddress)->getValue().toString() : Utils::coreStringToAppString(name);
auto vcard = contact->getVcard();
mOrganization = Utils::coreStringToAppString(vcard->getOrganization());
mJob = Utils::coreStringToAppString(vcard->getJobTitle());
mGivenName = Utils::coreStringToAppString(vcard->getGivenName());
mFamilyName = Utils::coreStringToAppString(vcard->getFamilyName());
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();
auto phoneNumbers = contact->getPhoneNumbersWithLabel();
for (auto &phoneNumber : phoneNumbers) {
mPhoneNumberList.append(
createFriendAddressVariant(Utils::coreStringToAppString(phoneNumber->getLabel()),
Utils::coreStringToAppString(phoneNumber->getPhoneNumber())));
}
mStarred = contact->getStarred();
mIsSaved = true;
} else {
mIsSaved = false;
mStarred = false;
}
connect(this, &FriendCore::addressChanged, &FriendCore::allAddressesChanged);
connect(this, &FriendCore::phoneNumberChanged, &FriendCore::allAddressesChanged);
}
FriendCore::FriendCore(const FriendCore &friendCore) {
// Only copy friend values without models for lambda using and avoid concurrencies.
mAddress = friendCore.mAddress;
mAddressList = friendCore.mAddressList;
mPhoneNumberList = friendCore.mPhoneNumberList;
mDefaultAddress = friendCore.mDefaultAddress;
mGivenName = friendCore.mGivenName;
mFamilyName = friendCore.mFamilyName;
mOrganization = friendCore.mOrganization;
mJob = friendCore.mJob;
mPictureUri = friendCore.mPictureUri;
mIsSaved = friendCore.mIsSaved;
}
FriendCore::~FriendCore() {
mustBeInMainThread("~" + getClassName());
emit mFriendModel->removeListener();
if (mFriendModel) emit mFriendModel->removeListener();
}
void FriendCore::setSelf(SafeSharedPointer<FriendCore> me) {
@ -84,20 +117,47 @@ void FriendCore::setSelf(QSharedPointer<FriendCore> me) {
setPresenceTimestamp(presenceTimestamp);
});
});
mFriendModelConnection->makeConnectToModel(&FriendModel::pictureUriChanged, [this](QString uri) {
mFriendModelConnection->makeConnectToModel(&FriendModel::pictureUriChanged, [this](const QString &uri) {
mFriendModelConnection->invokeToCore([this, uri]() { this->onPictureUriChanged(uri); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::starredChanged, [this](bool starred) {
mFriendModelConnection->invokeToCore([this, starred]() { this->onStarredChanged(starred); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::givenNameChanged, [this](const QString &name) {
mFriendModelConnection->invokeToCore([this, name]() { setGivenName(name); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::familyNameChanged, [this](const QString &name) {
mFriendModelConnection->invokeToCore([this, name]() { setFamilyName(name); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::organizationChanged, [this](const QString &orga) {
mFriendModelConnection->invokeToCore([this, orga]() { setOrganization(orga); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::jobChanged, [this](const QString &job) {
mFriendModelConnection->invokeToCore([this, job]() { setJob(job); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::addressesChanged, [this]() {
auto numbers = mFriendModel->getAddresses();
QList<QVariant> addr;
for (auto &num : numbers) {
addr.append(
createFriendAddressVariant(addressLabel, Utils::coreStringToAppString(num->asStringUriOnly())));
}
mFriendModelConnection->invokeToCore([this, addr]() { resetPhoneNumbers(addr); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::phoneNumbersChanged, [this]() {
auto numbers = mFriendModel->getPhoneNumbers();
QList<QVariant> addr;
for (auto &num : numbers) {
addr.append(
createFriendAddressVariant(phoneLabel, Utils::coreStringToAppString(num->getPhoneNumber())));
}
mFriendModelConnection->invokeToCore([this, addr]() { resetPhoneNumbers(addr); });
});
mFriendModelConnection->makeConnectToModel(
&FriendModel::objectNameChanged,
[this](const QString &objectName) { qDebug() << "object name changed" << objectName; });
// From GUI
mFriendModelConnection->makeConnectToCore(&FriendCore::lSetPictureUri, [this](QString uri) {
mFriendModelConnection->invokeToModel([this, uri]() { mFriendModel->setPictureUri(uri); });
});
mFriendModelConnection->makeConnectToCore(&FriendCore::lSetStarred, [this](bool starred) {
mFriendModelConnection->invokeToModel([this, starred]() { mFriendModel->setStarred(starred); });
});
@ -110,19 +170,67 @@ void FriendCore::setSelf(QSharedPointer<FriendCore> me) {
}
void FriendCore::reset(const FriendCore &contact) {
setAddress(contact.getAddress());
setName(contact.getName());
resetAddresses(contact.getAddresses());
resetPhoneNumbers(contact.getPhoneNumbers());
setDefaultAddress(contact.getDefaultAddress());
setGivenName(contact.getGivenName());
setFamilyName(contact.getFamilyName());
setOrganization(contact.getOrganization());
setJob(contact.getJob());
setPictureUri(contact.getPictureUri());
setIsSaved(mFriendModel != nullptr);
}
QString FriendCore::getName() const {
return mName;
QString FriendCore::getDisplayName() const {
return mGivenName + " " + mFamilyName;
}
void FriendCore::setName(QString data) {
if (mName != data) {
mName = data;
emit addressChanged(mName);
QString FriendCore::getGivenName() const {
return mGivenName;
}
void FriendCore::setGivenName(const QString &name) {
if (mGivenName != name) {
mGivenName = name;
emit givenNameChanged(name);
emit displayNameChanged();
setIsSaved(false);
}
}
QString FriendCore::getOrganization() const {
return mOrganization;
}
void FriendCore::setOrganization(const QString &orga) {
if (mOrganization != orga) {
mOrganization = orga;
emit organizationChanged();
setIsSaved(false);
}
}
QString FriendCore::getJob() const {
return mJob;
}
void FriendCore::setJob(const QString &job) {
if (mJob != job) {
mJob = job;
emit jobChanged();
setIsSaved(false);
}
}
QString FriendCore::getFamilyName() const {
return mFamilyName;
}
void FriendCore::setFamilyName(const QString &name) {
if (mFamilyName != name) {
mFamilyName = name;
emit familyNameChanged(name);
emit displayNameChanged();
setIsSaved(false);
}
}
@ -137,18 +245,91 @@ void FriendCore::onStarredChanged(bool starred) {
emit starredChanged();
}
QString FriendCore::getAddress() const {
return mAddress;
QList<QVariant> FriendCore::getPhoneNumbers() const {
return mPhoneNumberList;
}
void FriendCore::setAddress(QString address) {
if (mAddress != address) {
mAddress = address;
emit addressChanged(mAddress);
QVariant FriendCore::getPhoneNumberAt(int index) const {
if (index < 0 || index >= mPhoneNumberList.count()) return QVariant();
return mPhoneNumberList[index];
}
void FriendCore::setPhoneNumberAt(int index, const QString &label, const QString &phoneNumber) {
if (index < 0 || index >= mPhoneNumberList.count()) return;
auto map = mPhoneNumberList[index].toMap();
auto oldLabel = map["label"].toString();
if (/*oldLabel != label || */ map["address"] != phoneNumber) {
mPhoneNumberList.replace(index, createFriendAddressVariant(label.isEmpty() ? oldLabel : label, phoneNumber));
emit phoneNumberChanged();
setIsSaved(false);
}
}
void FriendCore::removePhoneNumber(int index) {
if (index != -1) mPhoneNumberList.remove(index);
emit phoneNumberChanged();
}
void FriendCore::appendPhoneNumber(const QString &label, const QString &number) {
mPhoneNumberList.append(createFriendAddressVariant(label, number));
emit phoneNumberChanged();
}
void FriendCore::resetPhoneNumbers(QList<QVariant> newList) {
mPhoneNumberList = newList;
emit phoneNumberChanged();
}
QList<QVariant> FriendCore::getAddresses() const {
return mAddressList;
}
QVariant FriendCore::getAddressAt(int index) const {
if (index < 0 || index >= mAddressList.count()) return QVariant();
return mAddressList[index];
}
void FriendCore::setAddressAt(int index, const QString &label, const QString &address) {
if (index < 0 || index >= mAddressList.count()) return;
auto map = mAddressList[index].toMap();
auto oldLabel = map["label"].toString();
if (/*oldLabel != label || */ map["address"] != address) {
mAddressList.replace(index, createFriendAddressVariant(label.isEmpty() ? oldLabel : label, address));
emit addressChanged();
setIsSaved(false);
}
}
void FriendCore::removeAddress(int index) {
if (index != -1) mAddressList.remove(index);
emit addressChanged();
}
void FriendCore::appendAddress(const QString &addr) {
mAddressList.append(createFriendAddressVariant(addressLabel, addr));
emit addressChanged();
}
void FriendCore::resetAddresses(QList<QVariant> newList) {
mAddressList = newList;
emit addressChanged();
}
QList<QVariant> FriendCore::getAllAddresses() const {
return mAddressList + mPhoneNumberList;
}
QString FriendCore::getDefaultAddress() const {
return mDefaultAddress;
}
void FriendCore::setDefaultAddress(const QString &address) {
if (mDefaultAddress != address) {
mDefaultAddress = address;
emit defaultAddressChanged();
}
}
LinphoneEnums::ConsolidatedPresence FriendCore::getConsolidatedPresence() const {
return mConsolidatedPresence;
}
@ -177,6 +358,13 @@ QString FriendCore::getPictureUri() const {
return mPictureUri;
}
void FriendCore::setPictureUri(const QString &uri) {
if (mPictureUri != uri) {
mPictureUri = uri;
emit pictureUriChanged();
}
}
void FriendCore::onPictureUriChanged(QString uri) {
mPictureUri = uri;
emit pictureUriChanged();
@ -192,21 +380,63 @@ void FriendCore::setIsSaved(bool data) {
}
}
void FriendCore::writeInto(std::shared_ptr<linphone::Friend> contact) const {
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);
auto core = CoreModel::getInstance()->getCore();
auto newAddress = core->createAddress(Utils::appStringToCoreString(mAddress));
contact->edit();
if (newAddress) contact->setAddress(newAddress);
else qDebug() << "Bad address : " << mAddress;
contact->done();
std::list<std::shared_ptr<linphone::Address>> addresses;
for (auto &addr : mAddressList) {
auto friendAddress = addr.toMap();
auto num =
linphone::Factory::get()->createAddress(Utils::appStringToCoreString(friendAddress["address"].toString()));
addresses.push_back(num);
}
model->resetAddresses(addresses);
model->setAddress(ToolModel::interpretUrl(mDefaultAddress));
std::list<std::shared_ptr<linphone::FriendPhoneNumber>> phones;
for (auto &number : mPhoneNumberList) {
auto friendAddress = number.toMap();
auto num = linphone::Factory::get()->createFriendPhoneNumber(
Utils::appStringToCoreString(friendAddress["address"].toString()),
Utils::appStringToCoreString(friendAddress["label"].toString()));
phones.push_back(num);
}
model->resetPhoneNumbers(phones);
model->setGivenName(mGivenName);
model->setFamilyName(mFamilyName);
model->setOrganization(mOrganization);
model->setJob(mJob);
model->setPictureUri(mPictureUri);
model->getFriend()->done();
}
void FriendCore::writeFrom(const std::shared_ptr<linphone::Friend> &contact) {
void FriendCore::writeFromModel(const std::shared_ptr<FriendModel> &model) {
mustBeInLinphoneThread(QString("[") + gClassName + "] " + Q_FUNC_INFO);
auto address = contact->getAddress();
mAddress = (address ? Utils::coreStringToAppString(address->asString()) : "");
mName = Utils::coreStringToAppString(contact->getName());
QList<QVariant> addresses;
for (auto &addr : model->getAddresses()) {
addresses.append(
createFriendAddressVariant(addressLabel, Utils::coreStringToAppString(addr->asStringUriOnly())));
}
mAddressList = addresses;
QList<QVariant> phones;
for (auto &number : model->getPhoneNumbers()) {
phones.append(createFriendAddressVariant(Utils::coreStringToAppString(number->getLabel()),
Utils::coreStringToAppString(number->getPhoneNumber())));
}
mPhoneNumberList = phones;
mGivenName = model->getGivenName();
mFamilyName = model->getFamilyName();
mOrganization = model->getOrganization();
mJob = model->getJob();
mPictureUri = model->getPictureUri();
}
void FriendCore::remove() {
@ -225,33 +455,40 @@ void FriendCore::save() { // Save Values to model
if (mFriendModel) {
mFriendModelConnection->invokeToModel([this, thisCopy]() { // Copy values to avoid concurrency
auto contact = mFriendModel->getFriend();
thisCopy->writeInto(contact);
thisCopy->writeIntoModel(mFriendModel);
thisCopy->deleteLater();
mFriendModelConnection->invokeToCore([this]() { saved(); });
setIsSaved(true);
});
} else {
mCoreModelConnection->invokeToModel([this, thisCopy]() {
auto linphoneAddr = ToolModel::interpretUrl(mAddress);
std::shared_ptr<linphone::Friend> contact;
auto core = CoreModel::getInstance()->getCore();
auto contact = core->findFriend(linphoneAddr);
auto friendExists = contact != nullptr;
for (auto &addr : mAddressList) {
auto friendAddress = addr.toMap();
auto linphoneAddr = ToolModel::interpretUrl(friendAddress["address"].toString());
contact = core->findFriend(linphoneAddr);
if (contact) break;
}
if (contact != nullptr) {
thisCopy->writeInto(contact);
auto friendModel = Utils::makeQObject_ptr<FriendModel>(contact);
friendModel->setSelf(friendModel);
thisCopy->writeIntoModel(friendModel);
thisCopy->deleteLater();
if (mFriendModelConnection) mFriendModelConnection->invokeToCore([this] { saved(); });
else mCoreModelConnection->invokeToCore([this] { saved(); });
} else {
auto contact = core->createFriend();
thisCopy->writeInto(contact);
std::shared_ptr<FriendModel> friendModel;
friendModel = Utils::makeQObject_ptr<FriendModel>(contact);
friendModel->setSelf(friendModel);
thisCopy->writeIntoModel(friendModel);
thisCopy->deleteLater();
bool created = (core->getDefaultFriendList()->addFriend(contact) == linphone::FriendList::Status::OK);
if (created) {
mFriendModel = Utils::makeQObject_ptr<FriendModel>(contact);
mFriendModel->setSelf(mFriendModel);
core->getDefaultFriendList()->updateSubscriptions();
emit CoreModel::getInstance()->friendAdded();
}
emit CoreModel::getInstance()->friendAdded();
mCoreModelConnection->invokeToCore([this, created]() {
if (created) setSelf(mCoreModelConnection->mCore);
setIsSaved(created);
@ -259,38 +496,18 @@ void FriendCore::save() { // Save Values to model
}
});
}
// if (mFriendModel) { // Update
// } else { // Creation
// mCoreModelConnection->invokeToModel([this, thisCopy]() {
// auto core = CoreModel::getInstance()->getCore();
// auto contact = core->createFriend();
// thisCopy->writeInto(contact);
// thisCopy->deleteLater();
// bool created = (core->getDefaultFriendList()->addFriend(contact) == linphone::FriendList::Status::OK);
// if (created) {
// mFriendModel = Utils::makeQObject_ptr<FriendModel>(contact);
// mFriendModel->setSelf(mFriendModel);
// core->getDefaultFriendList()->updateSubscriptions();
// }
// emit CoreModel::getInstance()->friendAdded();
// mCoreModelConnection->invokeToCore([this, created]() {
// if (created) setSelf(mCoreModelConnection->mCore);
// setIsSaved(created);
// });
// });
// }
}
void FriendCore::undo() { // Retrieve values from model
if (mFriendModel) {
mFriendModelConnection->invokeToModel([this]() {
FriendCore *contact = new FriendCore(*this);
contact->writeFrom(mFriendModel->getFriend());
mFriendModelConnection->invokeToCore([this, contact]() {
contact->writeFromModel(mFriendModel);
contact->moveToThread(App::getInstance()->thread());
mFriendModelConnection->invokeToCore([this, contact]() mutable {
this->reset(*contact);
contact->deleteLater();
});
});
}
}
}

View file

@ -21,31 +21,43 @@
#ifndef FRIEND_CORE_H_
#define FRIEND_CORE_H_
// #include "FriendAddressList.hpp"
#include "core/variant/VariantList.hpp"
#include "model/friend/FriendModel.hpp"
#include "tool/LinphoneEnums.hpp"
#include "tool/thread/SafeConnection.hpp"
#include "tool/thread/SafeSharedPointer.hpp"
#include <linphone++/linphone.hh>
#include <QDateTime>
#include <QMap>
#include <QObject>
#include <QSharedPointer>
#include <linphone++/linphone.hh>
// This object is defferent from usual Core. It set internal data from directly from GUI.
// Values are saved on request.
// This allow revert feature.
class CoreModel;
class FriendCore;
class FriendCore : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString address READ getAddress WRITE setAddress NOTIFY addressChanged)
Q_PROPERTY(QList<QVariant> allAdresses READ getAllAddresses NOTIFY allAddressesChanged)
Q_PROPERTY(QList<QVariant> phoneNumbers READ getPhoneNumbers NOTIFY phoneNumberChanged)
Q_PROPERTY(QList<QVariant> addresses READ getAddresses NOTIFY addressChanged)
Q_PROPERTY(QString givenName READ getGivenName WRITE setGivenName NOTIFY givenNameChanged)
Q_PROPERTY(QString familyName READ getFamilyName WRITE setFamilyName NOTIFY familyNameChanged)
Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged)
Q_PROPERTY(QString organization READ getOrganization WRITE setOrganization NOTIFY organizationChanged)
Q_PROPERTY(QString job READ getJob WRITE setJob NOTIFY jobChanged)
Q_PROPERTY(QString defaultAddress READ getDefaultAddress WRITE setDefaultAddress NOTIFY defaultAddressChanged)
Q_PROPERTY(QDateTime presenceTimestamp READ getPresenceTimestamp NOTIFY presenceTimestampChanged)
Q_PROPERTY(LinphoneEnums::ConsolidatedPresence consolidatedPresence READ getConsolidatedPresence NOTIFY
consolidatedPresenceChanged)
Q_PROPERTY(bool isSaved READ getIsSaved NOTIFY isSavedChanged)
Q_PROPERTY(QString pictureUri READ getPictureUri WRITE lSetPictureUri NOTIFY pictureUriChanged)
Q_PROPERTY(QString pictureUri READ getPictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
Q_PROPERTY(bool starred READ getStarred WRITE lSetStarred NOTIFY starredChanged)
public:
@ -58,14 +70,41 @@ public:
void setSelf(SafeSharedPointer<FriendCore> me);
void reset(const FriendCore &contact);
QString getName() const;
void setName(QString data);
QString getDisplayName() const;
QString getFamilyName() const;
void setFamilyName(const QString &name);
QString getGivenName() const;
void setGivenName(const QString &name);
QString getOrganization() const;
void setOrganization(const QString &name);
QString getJob() const;
void setJob(const QString &name);
bool getStarred() const;
void onStarredChanged(bool starred);
QString getAddress() const;
void setAddress(QString address);
QList<QVariant> getPhoneNumbers() const;
QVariant getPhoneNumberAt(int index) const;
Q_INVOKABLE void appendPhoneNumber(const QString &label, const QString &number);
Q_INVOKABLE void removePhoneNumber(int index);
Q_INVOKABLE void setPhoneNumberAt(int index, const QString &label, const QString &phoneNumber);
void resetPhoneNumbers(QList<QVariant> newList);
QList<QVariant> getAddresses() const;
QVariant getAddressAt(int index) const;
Q_INVOKABLE void appendAddress(const QString &addr);
Q_INVOKABLE void removeAddress(int index);
Q_INVOKABLE void setAddressAt(int index, const QString &label, const QString &address);
void resetAddresses(QList<QVariant> newList);
void setDefaultAddress(const QString &address);
QString getDefaultAddress() const;
QList<QVariant> getAllAddresses() const;
LinphoneEnums::ConsolidatedPresence getConsolidatedPresence() const;
void setConsolidatedPresence(LinphoneEnums::ConsolidatedPresence presence);
@ -77,6 +116,7 @@ public:
void setIsSaved(bool isSaved);
QString getPictureUri() const;
void setPictureUri(const QString &uri);
void onPictureUriChanged(QString uri);
void onPresenceReceived(LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp);
@ -87,30 +127,39 @@ public:
signals:
void contactUpdated();
void nameChanged(QString name);
void displayNameChanged();
void givenNameChanged(const QString &name);
void familyNameChanged(const QString &name);
void starredChanged();
void addressChanged(QString address);
void phoneNumberChanged();
void addressChanged();
void organizationChanged();
void jobChanged();
void consolidatedPresenceChanged(LinphoneEnums::ConsolidatedPresence level);
void presenceTimestampChanged(QDateTime presenceTimestamp);
void sipAddressAdded(const QString &sipAddress);
void sipAddressRemoved(const QString &sipAddress);
void pictureUriChanged();
void saved();
void isSavedChanged(bool isSaved);
void removed(FriendCore *contact);
void defaultAddressChanged();
void allAddressesChanged();
void lSetPictureUri(QString pictureUri);
void lSetStarred(bool starred);
protected:
void writeInto(std::shared_ptr<linphone::Friend> contact) const;
void writeFrom(const std::shared_ptr<linphone::Friend> &contact);
void writeIntoModel(std::shared_ptr<FriendModel> model) const;
void writeFromModel(const std::shared_ptr<FriendModel> &model);
LinphoneEnums::ConsolidatedPresence mConsolidatedPresence = LinphoneEnums::ConsolidatedPresence::Offline;
QDateTime mPresenceTimestamp;
QString mName;
QString mGivenName;
QString mFamilyName;
QString mOrganization;
QString mJob;
bool mStarred;
QString mAddress;
QList<QVariant> mPhoneNumberList;
QList<QVariant> mAddressList;
QString mDefaultAddress;
QString mPictureUri;
bool mIsSaved;
std::shared_ptr<FriendModel> mFriendModel;
@ -119,5 +168,6 @@ protected:
DECLARE_ABSTRACT_OBJECT
};
Q_DECLARE_METATYPE(FriendCore *)
#endif

View file

@ -58,7 +58,8 @@ bool FriendInitialProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sour
QRegularExpression search(mFilterText, QRegularExpression::CaseInsensitiveOption |
QRegularExpression::UseUnicodePropertiesOption);
auto friendData = sourceModel()->data(sourceModel()->index(sourceRow, 0, sourceParent)).value<FriendGui *>();
show = friendData->getCore()->getName().indexOf(search) == 0;
auto friendCore = friendData->getCore();
show = friendCore->getGivenName().indexOf(search) == 0 || friendCore->getFamilyName().indexOf(search) == 0;
}
return show;

View file

@ -53,7 +53,7 @@ public:
}
virtual T getAt(const int &index) const {
if (index < 0 || index >= mList.count()) return nullptr;
if (index < 0 || index >= mList.count()) return T();
else return mList[index];
}

View file

@ -26,6 +26,9 @@ MagicSearchProxy::MagicSearchProxy(QObject *parent) : SortFilterProxy(parent) {
connect(mList.get(), &MagicSearchList::sourceFlagsChanged, this, &MagicSearchProxy::sourceFlagsChanged);
connect(mList.get(), &MagicSearchList::aggregationFlagChanged, this, &MagicSearchProxy::aggregationFlagChanged);
setSourceModel(mList.get());
connect(CoreModel::getInstance().get(), &CoreModel::friendRemoved, this,
[this] { emit mList->lSearch(mSearchText); });
connect(this, &MagicSearchProxy::forceUpdate, [this] { emit mList->lSearch(mSearchText); });
sort(0);
}

View file

@ -48,10 +48,13 @@ public:
LinphoneEnums::MagicSearchAggregation getAggregationFlag() const;
void setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag);
// Q_INVOKABLE forceUpdate();
signals:
void searchTextChanged();
void sourceFlagsChanged(int sourceFlags);
void aggregationFlagChanged(LinphoneEnums::MagicSearchAggregation aggregationFlag);
void forceUpdate();
protected:
QString mSearchText;

View file

@ -0,0 +1,55 @@
// /*
// * Copyright (c) 2010-2024 Belledonne Communications SARL.
// *
// * This file is part of linphone-desktop
// * (see https://www.linphone.org).
// *
// * This program is free software: you can redistribute it and/or modify
// * it under the terms of the GNU General Public License as published by
// * the Free Software Foundation, either version 3 of the License, or
// * (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have received a copy of the GNU General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// */
#include "VariantList.hpp"
DEFINE_ABSTRACT_OBJECT(VariantList)
VariantList::VariantList(QObject *parent) {
}
VariantList::VariantList(QList<QVariant> list, QObject *parent) {
set(list);
}
VariantList::~VariantList() {
}
int VariantList::rowCount(const QModelIndex &parent) const {
return mList.count();
}
void VariantList::set(QList<QVariant> list) {
beginResetModel();
mList = list;
endResetModel();
emit listModelChanged();
}
void VariantList::replace(int index, QVariant newValue) {
mList.replace(index, newValue);
}
QVariant VariantList::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 mList[row];
return QVariant();
}

View file

@ -0,0 +1,56 @@
// /*
// * Copyright (c) 2010-2024 Belledonne Communications SARL.
// *
// * This file is part of linphone-desktop
// * (see https://www.linphone.org).
// *
// * This program is free software: you can redistribute it and/or modify
// * it under the terms of the GNU General Public License as published by
// * the Free Software Foundation, either version 3 of the License, or
// * (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have received a copy of the GNU General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// */
// // This object is defferent from usual Core. It set internal data from directly from GUI.
// // Values are saved on request.
// // This allow revert feature.
#ifndef VARIANT_LIST_H_
#define VARIANT_LIST_H_
#include "core/proxy/AbstractListProxy.hpp"
#include "tool/AbstractObject.hpp"
// ///////////////////////////// ADDRESS LIST /////////////////////////////
class VariantList : public AbstractListProxy<QVariant>, public AbstractObject {
Q_OBJECT
Q_PROPERTY(QList<QVariant> model WRITE set NOTIFY listModelChanged)
public:
VariantList(QObject *parent = Q_NULLPTR);
VariantList(QList<QVariant> list, QObject *parent = Q_NULLPTR);
~VariantList();
void set(QList<QVariant> list);
void replace(int index, QVariant newValue);
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
void listModelChanged();
private:
DECLARE_ABSTRACT_OBJECT
};
Q_DECLARE_METATYPE(VariantList *)
#endif

View file

@ -52,6 +52,7 @@ list(APPEND _LINPHONEAPP_RC_FILES data/assistant/use-app-sip-account.rc
"data/image/outgoing_call_rejected.svg"
"data/image/microphone.svg"
"data/image/microphone-slash.svg"
"data/image/camera.svg"
"data/image/video-camera.svg"
"data/image/video-camera-slash.svg"
"data/image/speaker-high.svg"
@ -66,6 +67,10 @@ list(APPEND _LINPHONEAPP_RC_FILES data/assistant/use-app-sip-account.rc
"data/image/heart.svg"
"data/image/heart-fill.svg"
"data/image/record-fill.svg"
"data/image/pencil-simple.svg"
"data/image/share-network.svg"
"data/image/bell-simple.svg"
"data/image/bell-simple-slash.svg"
"data/image/media_encryption_zrtp_pq.svg"
data/shaders/roundEffect.vert.qsb

View file

@ -28,10 +28,20 @@
DEFINE_ABSTRACT_OBJECT(FriendModel)
FriendModel::FriendModel(const std::shared_ptr<linphone::Friend> &contact, QObject *parent)
FriendModel::FriendModel(const std::shared_ptr<linphone::Friend> &contact, const QString &name, QObject *parent)
: ::Listener<linphone::Friend, linphone::FriendListener>(contact, parent) {
mustBeInLinphoneThread(getClassName());
}
connect(this, &FriendModel::addressesChanged, [this] {
if (mMonitor->getAddresses().size() == 0) return;
if (!mMonitor->getAddress()) mMonitor->setAddress(*mMonitor->getAddresses().begin());
});
connect(this, &FriendModel::defaultAddressChanged, [this] {
if (mMonitor->getAddresses().size() == 0) return;
if (!mMonitor->getAddress()) mMonitor->setAddress(*mMonitor->getAddresses().begin());
});
if (!contact->getName().empty() || !name.isEmpty())
mMonitor->setName(contact->getName().empty() ? Utils::appStringToCoreString(name) : contact->getName());
};
FriendModel::~FriendModel() {
mustBeInLinphoneThread("~" + getClassName());
@ -49,14 +59,163 @@ QDateTime FriendModel::getPresenceTimestamp() const {
} else return QDateTime();
}
QString FriendModel::getAddress() const {
return Utils::coreStringToAppString(mMonitor->getAddress()->asStringUriOnly());
void FriendModel::setAddress(const std::shared_ptr<linphone::Address> &address) {
if (address) {
mMonitor->setAddress(address);
emit defaultAddressChanged();
}
}
std::list<std::shared_ptr<linphone::FriendPhoneNumber>> FriendModel::getPhoneNumbers() const {
return mMonitor->getPhoneNumbersWithLabel();
}
void FriendModel::appendPhoneNumber(const std::shared_ptr<linphone::FriendPhoneNumber> &number) {
if (number) {
mMonitor->addPhoneNumberWithLabel(number);
emit phoneNumbersChanged();
}
}
void FriendModel::appendPhoneNumbers(const std::list<std::shared_ptr<linphone::FriendPhoneNumber>> &numbers) {
for (auto &num : numbers)
if (num) mMonitor->addPhoneNumberWithLabel(num);
emit phoneNumbersChanged();
}
void FriendModel::resetPhoneNumbers(const std::list<std::shared_ptr<linphone::FriendPhoneNumber>> &numbers) {
for (auto &num : mMonitor->getPhoneNumbers())
mMonitor->removePhoneNumber(num);
for (auto &num : numbers)
if (num) mMonitor->addPhoneNumberWithLabel(num);
emit phoneNumbersChanged();
}
void FriendModel::removePhoneNumber(const QString &number) {
mMonitor->removePhoneNumber(Utils::appStringToCoreString(number));
emit phoneNumbersChanged();
}
void FriendModel::clearPhoneNumbers() {
for (auto &number : mMonitor->getPhoneNumbers())
mMonitor->removePhoneNumber(number);
emit phoneNumbersChanged();
}
std::list<std::shared_ptr<linphone::Address>> FriendModel::getAddresses() const {
return mMonitor->getAddresses();
}
void FriendModel::appendAddress(const std::shared_ptr<linphone::Address> &addr) {
if (addr) {
mMonitor->addAddress(addr);
emit addressesChanged();
}
}
void FriendModel::appendAddresses(const std::list<std::shared_ptr<linphone::Address>> &addresses) {
for (auto &addr : addresses)
if (addr) mMonitor->addAddress(addr);
emit addressesChanged();
}
void FriendModel::resetAddresses(const std::list<std::shared_ptr<linphone::Address>> &addresses) {
for (auto &addr : mMonitor->getAddresses())
mMonitor->removeAddress(addr);
for (auto &addr : addresses)
if (addr) mMonitor->addAddress(addr);
emit addressesChanged();
}
void FriendModel::removeAddress(const std::shared_ptr<linphone::Address> &addr) {
if (addr) {
mMonitor->removeAddress(addr);
emit addressesChanged();
}
}
void FriendModel::clearAddresses() {
for (auto &addr : mMonitor->getAddresses())
if (addr) mMonitor->removeAddress(addr);
emit addressesChanged();
}
QString FriendModel::getName() const {
return Utils::coreStringToAppString(mMonitor->getName());
}
void FriendModel::setName(const QString &name) {
mMonitor->setName(Utils::appStringToCoreString(name));
}
QString FriendModel::getGivenName() const {
auto vcard = mMonitor->getVcard();
if (!vcard) {
mMonitor->createVcard(mMonitor->getName());
}
return Utils::coreStringToAppString(mMonitor->getVcard()->getGivenName());
}
void FriendModel::setGivenName(const QString &name) {
auto vcard = mMonitor->getVcard();
if (!vcard) {
mMonitor->createVcard(mMonitor->getName());
}
mMonitor->getVcard()->setGivenName(Utils::appStringToCoreString(name));
emit givenNameChanged(name);
}
QString FriendModel::getFamilyName() const {
auto vcard = mMonitor->getVcard();
if (!vcard) {
mMonitor->createVcard(mMonitor->getName());
}
return Utils::coreStringToAppString(mMonitor->getVcard()->getFamilyName());
}
void FriendModel::setFamilyName(const QString &name) {
auto vcard = mMonitor->getVcard();
if (!vcard) {
mMonitor->createVcard(mMonitor->getName());
}
mMonitor->getVcard()->setFamilyName(Utils::appStringToCoreString(name));
emit familyNameChanged(name);
}
QString FriendModel::getOrganization() const {
auto vcard = mMonitor->getVcard();
if (!vcard) {
mMonitor->createVcard(mMonitor->getName());
}
return Utils::coreStringToAppString(mMonitor->getVcard()->getOrganization());
}
void FriendModel::setOrganization(const QString &orga) {
auto vcard = mMonitor->getVcard();
if (!vcard) {
mMonitor->createVcard(mMonitor->getName());
}
mMonitor->getVcard()->setOrganization(Utils::appStringToCoreString(orga));
emit organizationChanged(orga);
}
QString FriendModel::getJob() const {
auto vcard = mMonitor->getVcard();
if (!vcard) {
mMonitor->createVcard(mMonitor->getName());
}
return Utils::coreStringToAppString(mMonitor->getVcard()->getJobTitle());
}
void FriendModel::setJob(const QString &job) {
auto vcard = mMonitor->getVcard();
if (!vcard) {
mMonitor->createVcard(mMonitor->getName());
}
mMonitor->getVcard()->setJobTitle(Utils::appStringToCoreString(job));
emit jobChanged(job);
}
bool FriendModel::getStarred() const {
return mMonitor->getStarred();
}
@ -70,11 +229,17 @@ void FriendModel::onPresenceReceived(const std::shared_ptr<linphone::Friend> &co
emit presenceReceived(LinphoneEnums::fromLinphone(contact->getConsolidatedPresence()), getPresenceTimestamp());
}
void FriendModel::setPictureUri(QString uri) {
QString FriendModel::getPictureUri() const {
auto vcard = mMonitor->getVcard();
if (!vcard) {
mMonitor->createVcard(mMonitor->getName());
}
return Utils::coreStringToAppString(mMonitor->getVcard()->getPhoto());
}
void FriendModel::setPictureUri(const QString &uri) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto account = std::dynamic_pointer_cast<linphone::Account>(mMonitor);
auto params = account->getParams()->clone();
auto oldPictureUri = Utils::coreStringToAppString(params->getPictureUri());
auto oldPictureUri = Utils::coreStringToAppString(mMonitor->getPhoto());
if (!oldPictureUri.isEmpty()) {
QString appPrefix = QStringLiteral("image://%1/").arg(AvatarProvider::ProviderId);
if (oldPictureUri.startsWith(appPrefix)) {
@ -83,7 +248,6 @@ void FriendModel::setPictureUri(QString uri) {
QFile oldPicture(oldPictureUri);
if (!oldPicture.remove()) qWarning() << log().arg("Cannot delete old avatar file at " + oldPictureUri);
}
params->setPictureUri(Utils::appStringToCoreString(uri));
account->setParams(params);
mMonitor->setPhoto(Utils::appStringToCoreString(uri));
emit pictureUriChanged(uri);
}

View file

@ -34,22 +34,60 @@ class FriendModel : public ::Listener<linphone::Friend, linphone::FriendListener
public linphone::FriendListener,
public AbstractObject {
Q_OBJECT
friend class FriendCore;
public:
FriendModel(const std::shared_ptr<linphone::Friend> &contact, QObject *parent = nullptr);
FriendModel(const std::shared_ptr<linphone::Friend> &contact,
const QString &name = QString(),
QObject *parent = nullptr);
~FriendModel();
QDateTime getPresenceTimestamp() const;
QString getAddress() const;
std::list<std::shared_ptr<linphone::FriendPhoneNumber>> getPhoneNumbers() const;
std::list<std::shared_ptr<linphone::Address>> getAddresses() const;
QString getName() const;
QString getGivenName() const;
QString getFamilyName() const;
QString getOrganization() const;
QString getJob() const;
bool getStarred() const;
std::shared_ptr<linphone::Friend> getFriend() const;
QString getPictureUri() const;
void setPictureUri(QString uri);
protected:
void setAddress(const std::shared_ptr<linphone::Address> &address);
void appendPhoneNumber(const std::shared_ptr<linphone::FriendPhoneNumber> &number);
void appendPhoneNumbers(const std::list<std::shared_ptr<linphone::FriendPhoneNumber>> &numbers);
void resetPhoneNumbers(const std::list<std::shared_ptr<linphone::FriendPhoneNumber>> &numbers);
void removePhoneNumber(const QString &number);
void clearPhoneNumbers();
void appendAddress(const std::shared_ptr<linphone::Address> &addr);
void appendAddresses(const std::list<std::shared_ptr<linphone::Address>> &addresses);
void resetAddresses(const std::list<std::shared_ptr<linphone::Address>> &addresses);
void removeAddress(const std::shared_ptr<linphone::Address> &addr);
void clearAddresses();
void setName(const QString &name);
void setGivenName(const QString &name);
void setFamilyName(const QString &name);
void setOrganization(const QString &orga);
void setJob(const QString &job);
void setPictureUri(const QString &uri);
void setStarred(bool starred);
signals:
void pictureUriChanged(QString uri);
void pictureUriChanged(const QString &uri);
void starredChanged(bool starred);
void addressesChanged();
void defaultAddressChanged();
void phoneNumbersChanged();
// void nameChanged(const QString &name);
void givenNameChanged(const QString &name);
void familyNameChanged(const QString &name);
void organizationChanged(const QString &orga);
void jobChanged(const QString &job);
private:
DECLARE_ABSTRACT_OBJECT

View file

@ -48,6 +48,14 @@ std::shared_ptr<linphone::Address> ToolModel::interpretUrl(const QString &addres
return interpretedAddress;
}
std::shared_ptr<linphone::FriendPhoneNumber> ToolModel::makeLinphoneNumber(const QString &label,
const QString &number) {
auto linphoneNumber = std::make_shared<linphone::FriendPhoneNumber>(nullptr);
linphoneNumber->setLabel(Utils::appStringToCoreString(label));
linphoneNumber->setLabel(Utils::appStringToCoreString(number));
return linphoneNumber;
}
QString ToolModel::getDisplayName(const std::shared_ptr<const linphone::Address> &address) {
QString displayName;
if (address) {

View file

@ -35,6 +35,7 @@ public:
~ToolModel();
static std::shared_ptr<linphone::Address> interpretUrl(const QString &address);
static std::shared_ptr<linphone::FriendPhoneNumber> makeLinphoneNumber(const QString &label, const QString &number);
static QString getDisplayName(const std::shared_ptr<const linphone::Address> &address);
static QString getDisplayName(QString address);

View file

@ -22,7 +22,6 @@
#include "core/App.hpp"
#include "model/call/CallModel.hpp"
#include "model/object/VariantObject.hpp"
#include "model/tool/ToolModel.hpp"
// =============================================================================

View file

@ -26,6 +26,7 @@
#include "model/object/VariantObject.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/providers/AvatarProvider.hpp"
#include <QClipboard>
#include <QImageReader>
#include <QQuickWindow>
#include <QRandomGenerator>
@ -49,6 +50,7 @@ VariantObject *Utils::getDisplayName(const QString &address) {
QStringList splitted = address.split(":");
if (splitted.size() > 0 && splitted[0] == "sip") splitted.removeFirst();
VariantObject *data = new VariantObject(splitted.first().split("@").first()); // Scope : GUI
if (!data) return nullptr;
data->makeRequest([address]() {
QString displayName = ToolModel::getDisplayName(address);
return displayName;
@ -57,6 +59,19 @@ VariantObject *Utils::getDisplayName(const QString &address) {
return data;
}
QString Utils::getGivenNameFromFullName(const QString &fullName) {
if (fullName.isEmpty()) return QString();
auto nameSplitted = fullName.split(" ");
return nameSplitted[0];
}
QString Utils::getFamilyNameFromFullName(const QString &fullName) {
if (fullName.isEmpty()) return QString();
auto nameSplitted = fullName.split(" ");
nameSplitted.removeFirst();
return nameSplitted.join(" ");
}
QString Utils::getInitials(const QString &username) {
if (username.isEmpty()) return "";
@ -81,7 +96,7 @@ VariantObject *Utils::createCall(const QString &sipAddress,
const QString &prepareTransfertAddress,
const QHash<QString, QString> &headers) {
VariantObject *data = new VariantObject(QVariant()); // Scope : GUI
if (!data) return nullptr;
data->makeRequest([sipAddress, prepareTransfertAddress, headers]() {
auto call = ToolModel::createCall(sipAddress, prepareTransfertAddress, headers);
if (call) {
@ -90,10 +105,14 @@ VariantObject *Utils::createCall(const QString &sipAddress,
auto app = App::getInstance();
auto window = app->getCallsWindow(callGui);
smartShowWindow(window);
qDebug() << "Utils : call created" << callGui;
// callGui.value<CallGui *>()->getCore()->lSetCameraEnabled(true);
});
return callGui;
} else return QVariant();
} else {
qDebug() << "Utils : failed to create call";
return QVariant();
}
});
data->requestValue();
@ -131,7 +150,7 @@ QQuickWindow *Utils::getMainWindow() {
VariantObject *Utils::haveAccount() {
VariantObject *result = new VariantObject();
if (!result) return nullptr;
// Using connect ensure to have sender() and receiver() alive.
result->makeRequest([]() {
// Model
@ -287,4 +306,8 @@ QStringList Utils::generateSecurityLettersArray(int arraySize, int correctIndex,
int Utils::getRandomIndex(int size) {
return QRandomGenerator::global()->bounded(size);
}
void Utils::copyToClipboard(const QString &text) {
QApplication::clipboard()->setText(text);
}

View file

@ -52,6 +52,8 @@ public:
}
Q_INVOKABLE static VariantObject *getDisplayName(const QString &address);
Q_INVOKABLE static QString getGivenNameFromFullName(const QString &fullName);
Q_INVOKABLE static QString getFamilyNameFromFullName(const QString &fullName);
Q_INVOKABLE static QString getInitials(const QString &username); // Support UTF32
Q_INVOKABLE static VariantObject *createCall(const QString &sipAddress,
@ -72,6 +74,7 @@ public:
Q_INVOKABLE static QString formatDateElapsedTime(const QDateTime &date);
Q_INVOKABLE static QStringList generateSecurityLettersArray(int arraySize, int correctIndex, QString correctCode);
Q_INVOKABLE static int getRandomIndex(int size);
Q_INVOKABLE static void copyToClipboard(const QString &text);
static QString generateSavedFilename(const QString &from, const QString &to);
static inline QString coreStringToAppString(const std::string &str) {

View file

@ -208,7 +208,6 @@ Window {
spacing: 10 * DefaultStyle.dp
EffectImage {
id: callStatusIcon
fillMode: Image.PreserveAspectFit
width: 15 * DefaultStyle.dp
height: 15 * DefaultStyle.dp
source:(mainWindow.call.core.state === LinphoneEnums.CallState.End

View file

@ -0,0 +1,163 @@
import QtQuick 2.15
import QtQuick.Effects
import QtQuick.Layouts
import QtQuick.Controls as Control
import Linphone
import UtilsCpp 1.0
ColumnLayout {
id: mainItem
spacing: 30 * DefaultStyle.dp
property var contact
property string contactAddress: contact && contact.core.defaultAddress || ""
property string contactName: contact && contact.core.displayName || ""
property bool addressVisible: true
property alias buttonContent: rightButton.data
property alias detailContent: detailControl.data
component LabelButton: ColumnLayout {
id: labelButton
property alias image: buttonImg
property alias button: button
property string label
spacing: 8 * DefaultStyle.dp
Button {
id: button
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 56 * DefaultStyle.dp
Layout.preferredHeight: 56 * DefaultStyle.dp
topPadding: 16 * DefaultStyle.dp
bottomPadding: 16 * DefaultStyle.dp
leftPadding: 16 * DefaultStyle.dp
rightPadding: 16 * DefaultStyle.dp
background: Rectangle {
anchors.fill: parent
radius: 40 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
contentItem: Image {
id: buttonImg
source: labelButton.source
width: 24 * DefaultStyle.dp
height: 24 * DefaultStyle.dp
fillMode: Image.PreserveAspectFit
sourceSize.width: 24 * DefaultStyle.dp
sourceSize.height: 24 * DefaultStyle.dp
}
}
Text {
Layout.alignment: Qt.AlignHCenter
text: labelButton.label
font {
pixelSize: 14 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
}
}
Item {
Layout.preferredWidth: mainItem.implicitWidth
Layout.preferredHeight: detailAvatar.height
// Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Avatar {
// TODO : find friend and pass contact argument
id: detailAvatar
anchors.horizontalCenter: parent.horizontalCenter
width: 100 * DefaultStyle.dp
height: 100 * DefaultStyle.dp
contact: mainItem.contact || null
address: !contact && mainItem.contactAddress || mainItem.contactName
}
Item {
id: rightButton
anchors.right: parent.right
anchors.verticalCenter: detailAvatar.verticalCenter
anchors.rightMargin: 20 * DefaultStyle.dp
width: 30 * DefaultStyle.dp
height: 30 * DefaultStyle.dp
}
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
// Layout.fillWidth: true
Text {
Layout.alignment: Qt.AlignHCenter
text: mainItem.contactName
horizontalAlignment: Text.AlignHCenter
font {
pixelSize: 14 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
}
Text {
id: contactAddress
visible: mainItem.addressVisible
text: mainItem.contactAddress
horizontalAlignment: Text.AlignHCenter
font {
pixelSize: 12 * DefaultStyle.dp
weight: 300 * DefaultStyle.dp
}
}
Text {
// connection status
}
}
Item {
// spacing: 10 * DefaultStyle.dp
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: mainItem.implicitWidth
Layout.preferredHeight: childrenRect.height
// Layout.fillHeight: true
LabelButton {
anchors.left: parent.left
// width: 24 * DefaultStyle.dp//image.width
// height: image.height
image.source: AppIcons.phone
label: qsTr("Appel")
button.onClicked: {
var addr = mainItem.contact.core.defaultAddress
var addressEnd = "@sip.linphone.org"
if (!addr.endsWith(addressEnd)) addr += addressEnd
UtilsCpp.createCall(addr)
}
}
LabelButton {
anchors.horizontalCenter: parent.horizontalCenter
// Layout.preferredWidth: image.width
// Layout.preferredHeight: image.height
image.source: AppIcons.chatTeardropText
label: qsTr("Message")
button.onClicked: console.debug("[CallPage.qml] TODO : open conversation")
}
LabelButton {
id: videoCall
anchors.right: parent.right
// Layout.preferredWidth: image.width
// Layout.preferredHeight: image.height
image.source: AppIcons.videoCamera
label: qsTr("Appel Video")
button.onClicked: {
var addr = mainItem.contact.core.defaultAddress
var addressEnd = "@sip.linphone.org"
if(!addr.endsWith(addressEnd)) addr += addressEnd
UtilsCpp.createCall(addr)
console.log("[CallPage.qml] TODO : enable video")
}
}
// Item {Layout.fillWidth: true}
}
ColumnLayout {
id: detailControl
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 30 * DefaultStyle.dp
}
}

View file

@ -160,7 +160,7 @@ Item {
CallPage {
id: callPage
}
//ContactPage{}
ContactPage{}
//ConversationPage{}
//MeetingPage{}
}

View file

@ -2,6 +2,7 @@
list(APPEND _LINPHONEAPP_QML_FILES
view/App/Main.qml
view/App/CallsWindow.qml
view/App/Layout/ContactLayout.qml
view/App/Layout/LoginLayout.qml
view/App/Layout/MainLayout.qml
@ -18,6 +19,8 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Item/Contact/Avatar.qml
view/Item/Contact/Contact.qml
view/Item/Contact/ContactDescription.qml
view/Item/Contact/ContactEdition.qml
view/Item/Contact/ContactsList.qml
view/Item/Contact/Sticker.qml
view/Item/BusyIndicator.qml
@ -25,12 +28,12 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Item/Carousel.qml
view/Item/CheckBox.qml
view/Item/ComboBox.qml
view/Item/ContactsList.qml
view/Item/DesktopPopup.qml
view/Item/Dialog.qml
view/Item/DigitInput.qml
view/Item/EffectImage.qml
view/Item/ErrorText.qml
view/Item/IconLabelButton.qml
view/Item/MovableMouseArea.qml
view/Item/NumericPad.qml
view/Item/PhoneNumberComboBox.qml
@ -39,6 +42,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Item/PopupButton.qml
view/Item/RadioButton.qml
view/Item/RectangleTest.qml
view/Item/RoundedBackgroundControl.qml
view/Item/SearchBar.qml
view/Item/TabBar.qml
view/Item/Text.qml
@ -60,6 +64,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Page/Main/AbstractMainPage.qml
view/Page/Main/CallPage.qml
view/Page/Main/ContactPage.qml
# Prototypes
view/Prototype/PhoneNumberPrototype.qml

View file

@ -57,7 +57,7 @@ Item {
Layout.fillWidth: true
Layout.topMargin: mainItem.spacing
Layout.bottomMargin: mainItem.spacing
height: 1
height: 1 * DefaultStyle.dp
color: DefaultStyle.main2_300
}
MouseArea{

View file

@ -20,6 +20,13 @@ Control.Button {
topPadding: 11 * DefaultStyle.dp
bottomPadding: 11 * DefaultStyle.dp
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: hovered ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.NoButton
}
background: Item {
Rectangle {
anchors.fill: parent

View file

@ -20,7 +20,7 @@ Item {
id: startCallPopup
property FriendGui contact
onContactChanged: {
console.log("contact changed", contact)
}
underlineColor: DefaultStyle.main1_500_main
anchors.centerIn: parent
@ -53,56 +53,83 @@ Item {
onClicked: startCallPopup.close()
}
}
Repeater {
id: adresses
model: [{label: "SIP", address: startCallPopup.contact ? startCallPopup.contact.core.address : ""}
// {label: "Work", address: "06000000000"},
// {label: "Personal", address: "060000000"}
] //account.adresses
Button {
id: channel
// required property int index
leftPadding: 0
rightPadding: 0
// topPadding: 0
bottomPadding: 0
Layout.fillWidth: true
background: Item{}
contentItem: ColumnLayout {
RowLayout {
ColumnLayout {
Text {
Layout.leftMargin: 5 * DefaultStyle.dp
Layout.rightMargin: 5 * DefaultStyle.dp
text: modelData.label
font {
pixelSize: 14 * DefaultStyle.dp
weight: 700 * DefaultStyle.dp
}
}
Text {
Layout.leftMargin: 5 * DefaultStyle.dp
Layout.rightMargin: 5 * DefaultStyle.dp
text: modelData.address
font {
pixelSize: 13 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
component AddressButton: Button {
property int index
property string label
property string address
id: channel
// required property int index
leftPadding: 0
rightPadding: 0
// topPadding: 0
bottomPadding: 0
Layout.fillWidth: true
background: Item{}
contentItem: ColumnLayout {
RowLayout {
ColumnLayout {
Text {
Layout.leftMargin: 5 * DefaultStyle.dp
Layout.rightMargin: 5 * DefaultStyle.dp
text: label
// TODO : change this with domain
font {
pixelSize: 14 * DefaultStyle.dp
weight: 700 * DefaultStyle.dp
}
}
Item {
Layout.fillWidth: true
Text {
Layout.leftMargin: 5 * DefaultStyle.dp
Layout.rightMargin: 5 * DefaultStyle.dp
text: address
font {
pixelSize: 13 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
}
}
Rectangle {
visible: index < adresses.count - 1
Item {
Layout.fillWidth: true
Layout.preferredHeight: 1 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
}
onClicked: mainItem.callButtonPressed(modelData.address)
Rectangle {
visible: index < selectedContactAddresses.count - 1
Layout.fillWidth: true
Layout.preferredHeight: 1 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
}
onClicked: mainItem.callButtonPressed(address)
}
Repeater {
id: selectedContactAddresses
model: VariantList {
model: startCallPopup.contact && startCallPopup.contact.core.addresses || []
}
// model: startCallPopup.contact ? startCallPopup.contact.core.addresses : ""
// {label: "Work", address: "06000000000"},
// {label: "Personal", address: "060000000"}
//account.adresses
delegate: AddressButton {
// property int index
// property string label
// property string address
}
}
Repeater {
id: selectedContactPhoneNumbers
model: VariantList {
model: startCallPopup.contact && startCallPopup.contact.core.phoneNumbers || []
}
// model: startCallPopup.contact ? startCallPopup.contact.core.addresses : ""
// {label: "Work", address: "06000000000"},
// {label: "Personal", address: "060000000"}
//account.adresses
delegate: AddressButton {
// property int index
// property string label
// property string address
}
}
}
@ -113,7 +140,7 @@ Item {
id: contactsScrollbar
active: true
interactive: true
policy: Control.ScrollBar.AlwaysOn
policy: Control.ScrollBar.AsNeeded
// Layout.fillWidth: true
anchors.top: parent.top
anchors.bottom: parent.bottom
@ -233,9 +260,11 @@ Item {
}
ContactsList{
Layout.fillWidth: true
contactMenuVisible: false
id: contactList
searchBarText: searchBar.text
onContactSelected: (contact) => {
console.log("contact selected", contact)
startCallPopup.contact = contact
startCallPopup.open()
}
@ -251,6 +280,7 @@ Item {
}
ContactsList{
contactMenuVisible: false
Layout.fillWidth: true
Layout.fillHeight: true
initialHeadersVisible: false
model: MagicSearchProxy {

View file

@ -10,7 +10,7 @@ import UtilsCpp
// Initials will be displayed if there isn't any avatar.
// TODO : get FriendGui from Call.
StackView{
StackView {
id: mainItem
property AccountGui account: null
property FriendGui contact: null
@ -20,9 +20,10 @@ StackView{
: call
? call.core.peerAddress
: contact
? contact.core.address
? contact.core.defaultAddress
: ''
property var displayNameObj: UtilsCpp.getDisplayName(address)
property string displayNameVal: displayNameObj ? displayNameObj.value : ""
property bool haveAvatar: (account && account.core.pictureUri )
|| (contact && contact.core.pictureUri)
@ -57,7 +58,7 @@ StackView{
id: initials
Rectangle {
id: initialItem
property string initials: displayNameObj ? UtilsCpp.getInitials(mainItem.displayNameObj.value) : ""
property string initials: UtilsCpp.getInitials(mainItem.displayNameVal)
radius: width / 2
color: DefaultStyle.main2_200
height: mainItem.height
@ -74,6 +75,13 @@ StackView{
capitalization: Font.AllUppercase
}
}
Image {
visible: initialItem.initials.length === 0
width: mainItem.width/3
height: width
source: AppIcons.profile
anchors.centerIn: parent
}
}
}
Component{
@ -92,7 +100,9 @@ StackView{
sourceSize.height: avatarItem.height
fillMode: Image.PreserveAspectCrop
anchors.centerIn: parent
source: mainItem.account ? mainItem.account.core.pictureUri : mainItem.contact.core.pictureUri
source: mainItem.account && mainItem.account.core.pictureUri
|| mainItem.contact && mainItem.contact.core.pictureUri
|| ""
mipmap: true
}
ShaderEffect {

View file

@ -8,9 +8,9 @@ import UtilsCpp
ColumnLayout{
id: mainItem
property AccountGui account: null
property var displayName: account ? UtilsCpp.getDisplayName(account.core.identityAddress) : ''
property string topText: displayName ? displayName.value : ''
property string bottomText: account ? account.core.identityAddress : ''
property var displayName: account ? UtilsCpp.getDisplayName(account.core.identityAddress) : ""
property string topText: displayName ? displayName.value : ""
property string bottomText: account ? account.core.identityAddress : ""
spacing: 0
width: topTextItem.implicitWidth
Text {

View file

@ -0,0 +1,250 @@
import QtCore
import QtQuick 2.15
import QtQuick.Controls as Control
import QtQuick.Dialogs
import QtQuick.Effects
import QtQuick.Layouts
import Linphone
import UtilsCpp 1.0
ColumnLayout {
id: mainItem
property FriendGui contact
property string title: qsTr("Modifier contact")
property string saveButtonText: qsTr("Enregistrer")
property string oldPictureUri
signal closeEdition()
Rectangle {
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.fillWidth: true
Layout.preferredHeight: 40 * DefaultStyle.dp
Text {
anchors.left: parent.left
anchors.leftMargin: 10 * DefaultStyle.dp
text: mainItem.title
font {
pixelSize: 20 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
Button {
background: Item{}
anchors.right: parent.right
anchors.rightMargin: 10 * DefaultStyle.dp
width: 24 * DefaultStyle.dp
height: 24 * DefaultStyle.dp
contentItem: Image {
anchors.fill: parent
source: AppIcons.closeX
}
onClicked: {
// contact.core.pictureUri = mainItem.oldPictureUri
mainItem.contact.core.undo()
mainItem.closeEdition()
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 69 * DefaultStyle.dp
Avatar {
contact: mainItem.contact
Layout.preferredWidth: 72 * DefaultStyle.dp
Layout.preferredHeight: 72 * DefaultStyle.dp
Layout.alignment: Qt.AlignHCenter
}
IconLabelButton {
visible: !mainItem.contact || mainItem.contact.core.pictureUri.length === 0
Layout.preferredWidth: width
Layout.preferredHeight: 17 * DefaultStyle.dp
iconSource: AppIcons.camera
iconSize: 17 * DefaultStyle.dp
text: qsTr("Ajouter une image")
onClicked: fileDialog.open()
}
RowLayout {
visible: mainItem.contact && mainItem.contact.core.pictureUri.length != 0
Layout.alignment: Qt.AlignHCenter
IconLabelButton {
Layout.preferredWidth: width
Layout.preferredHeight: 17 * DefaultStyle.dp
iconSource: AppIcons.pencil
iconSize: 17 * DefaultStyle.dp
text: qsTr("Modifier")
onClicked: fileDialog.open()
}
FileDialog {
id: fileDialog
currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
onAccepted: {
mainItem.oldPictureUri = mainItem.contact.core.pictureUri
var avatarPath = UtilsCpp.createAvatar( selectedFile )
if(avatarPath){
mainItem.contact.core.pictureUri = avatarPath
}
}
}
IconLabelButton {
Layout.preferredHeight: 17 * DefaultStyle.dp
Layout.preferredWidth: width
iconSize: 17 * DefaultStyle.dp
iconSource: AppIcons.trashCan
text: qsTr("Supprimer")
onClicked: mainItem.contact.core.pictureUri = ""
}
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
Layout.fillHeight: true
Layout.fillWidth: true
spacing: 100 * DefaultStyle.dp
Layout.topMargin: 50 * DefaultStyle.dp
Layout.bottomMargin: 50 * DefaultStyle.dp
ColumnLayout {
spacing: 20 * DefaultStyle.dp
TextInput {
label: qsTr("Prénom")
initialText: contact.core.givenName
onEditingFinished: contact.core.givenName = text
backgroundColor: DefaultStyle.grey_0
}
TextInput {
label: qsTr("Nom")
initialText: contact.core.familyName
onEditingFinished: contact.core.familyName = text
backgroundColor: DefaultStyle.grey_0
}
TextInput {
label: qsTr("Entreprise")
initialText: contact.core.organization
onEditingFinished: contact.core.organization = text
backgroundColor: DefaultStyle.grey_0
}
TextInput {
label: qsTr("Fonction")
initialText: contact.core.job
onEditingFinished: contact.core.job = text
backgroundColor: DefaultStyle.grey_0
}
Item{Layout.fillHeight: true}
}
Control.ScrollView {
Layout.fillHeight: true
contentHeight: content.height
ColumnLayout {
id: content
anchors.rightMargin: 10 * DefaultStyle.dp
spacing: 20 * DefaultStyle.dp
Repeater {
model: VariantList {
model: mainItem.contact && mainItem.contact.core.addresses || []
}
delegate: RowLayout {
TextInput {
label: modelData.label
onEditingFinished: {
if (text.length != 0) mainItem.contact.core.setAddressAt(index, text)
}
initialText: modelData.address
backgroundColor: DefaultStyle.grey_0
}
Button {
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
background: Item{}
contentItem: Image {
anchors.fill: parent
source: AppIcons.closeX
}
onClicked: mainItem.contact.core.removeAddress(index)
}
}
}
RowLayout {
TextInput {
label: qsTr("Adresse SIP")
backgroundColor: DefaultStyle.grey_0
onEditingFinished: {
if (text.length != 0) mainItem.contact.core.appendAddress(text)
setText("")
}
}
Item {
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
}
}
Repeater {
// phone numbers
model: VariantList {
model: mainItem.contact && mainItem.contact.core.phoneNumbers || []
}
delegate: RowLayout {
TextInput {
label: modelData.label
initialText: modelData.address
onEditingFinished: {
if (text.length != 0) mainItem.contact.core.setPhoneNumberAt(index, qsTr("Téléphone"), text)
}
backgroundColor: DefaultStyle.grey_0
}
Button {
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
background: Item{}
contentItem: Image {
anchors.fill: parent
source: AppIcons.closeX
}
onClicked: mainItem.contact.core.removePhoneNumber(index)
}
}
}
RowLayout {
TextInput {
label: qsTr("Phone")
backgroundColor: DefaultStyle.grey_0
onEditingFinished: {
if (text.length != 0) mainItem.contact.core.appendPhoneNumber(label, text)
setText("")
}
}
Item {
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
}
}
Item{Layout.fillHeight: true}
}
Control.ScrollBar.vertical: Control.ScrollBar{
id: scrollbar
active: true
interactive: true
policy: Control.ScrollBar.AsNeeded
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.leftMargin: 15 * DefaultStyle.dp
}
Control.ScrollBar.horizontal: Control.ScrollBar{
visible: false
}
}
}
Button {
Layout.bottomMargin: 100 * DefaultStyle.dp
Layout.preferredWidth: 165 * DefaultStyle.dp
Layout.alignment: Qt.AlignHCenter
enabled: mainItem.contact && mainItem.contact.core.givenName.length > 0
text: mainItem.saveButtonText
onClicked: {
mainItem.contact.core.save()
mainItem.closeEdition()
}
}
}

View file

@ -0,0 +1,164 @@
import QtQuick 2.7
import QtQuick.Layouts 1.3
import Linphone
import UtilsCpp 1.0
ListView {
id: mainItem
Layout.preferredHeight: contentHeight
height: contentHeight
visible: count > 0
property string searchBarText
property bool hoverEnabled: true
property bool contactMenuVisible: true
property bool initialHeadersVisible: true
property FriendGui selectedContact: model.getAt(currentIndex) || null
onCurrentIndexChanged: selectedContact = model.getAt(currentIndex) || null
onCountChanged: {
selectedContact = model.getAt(currentIndex) || null
}
signal contactSelected(var contact)
signal contactStarredChanged()
onContactStarredChanged: model.forceUpdate()
model: MagicSearchProxy {
searchText: searchBarText.length === 0 ? "*" : searchBarText
}
delegate: Item {
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 {
target: modelData.core
onStarredChanged: mainItem.contactStarredChanged()
}
Text {
id: initial
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
Layout.preferredWidth: 20 * DefaultStyle.dp
opacity: (!previousItem || !previousDisplayName.startsWith(displayName[0])) ? 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.right
anchors.leftMargin: 10 * DefaultStyle.dp
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
z: 1
Avatar {
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
contact: modelData
}
Text {
text: itemDelegate.displayName
font.pixelSize: 14 * DefaultStyle.dp
font.capitalization: Font.Capitalize
}
Item {
Layout.fillWidth: true
}
}
PopupButton {
id: friendPopup
z: 1
hoverEnabled: mainItem.hoverEnabled
visible: mainItem.contactMenuVisible && (contactArea.containsMouse || hovered || popup.opened)
popup.x: 0
popup.padding: 10 * DefaultStyle.dp
anchors.right: parent.right
anchors.rightMargin: 5 * DefaultStyle.dp
anchors.verticalCenter: parent.verticalCenter
popup.contentItem: ColumnLayout {
Button {
background: Item{}
contentItem: RowLayout {
Image {
source: modelData.core.starred ? AppIcons.heartFill : AppIcons.heart
fillMode: Image.PreserveAspectFit
width: 24 * DefaultStyle.dp
height: 24 * DefaultStyle.dp
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
}
Text {
text: modelData.core.starred ? qsTr("Enlever des favoris") : qsTr("Mettre en favori")
color: DefaultStyle.main2_500main
font {
pixelSize: 14 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
}
}
onClicked: {
modelData.core.lSetStarred(!modelData.core.starred)
friendPopup.close()
}
}
Button {
background: Item{}
contentItem: RowLayout {
EffectImage {
source: AppIcons.trashCan
width: 24 * DefaultStyle.dp
height: 24 * DefaultStyle.dp
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
fillMode: Image.PreserveAspectFit
colorizationColor: DefaultStyle.danger_500main
}
Text {
text: qsTr("Supprimmer")
color: DefaultStyle.danger_500main
font {
pixelSize: 14 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
}
}
onClicked: {
modelData.core.remove()
friendPopup.close()
}
}
}
}
MouseArea {
id: contactArea
hoverEnabled: mainItem.hoverEnabled
anchors.fill: contactDelegate
height: mainItem.height
Rectangle {
anchors.fill: contactArea
opacity: 0.7
color: DefaultStyle.main2_100
visible: contactArea.containsMouse || friendPopup.hovered || mainItem.currentIndex === index
}
onClicked: {
mainItem.currentIndex = index
mainItem.contactSelected(modelData)
}
}
}
}

View file

@ -20,7 +20,8 @@ Item {
onEnablePersonalCameraChanged: console.log ("enable camera", enablePersonalCamera)
property color color: DefaultStyle.grey_600
property int radius: 15 * DefaultStyle.dp
property var peerAddress: call ? UtilsCpp.getDisplayName(call.core.peerAddress) : null
property var peerAddressObj: call ? UtilsCpp.getDisplayName(call.core.peerAddress) : null
property string peerAddress: peerAddressObj ? peerAddressObj.value : ""
property var identityAddress: account ? UtilsCpp.getDisplayName(account.core.identityAddress) : null
Rectangle {
@ -42,7 +43,7 @@ Item {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 15 * DefaultStyle.dp
visible: mainItem.call && mainItem.call != undefined
text: mainItem.peerAddress ? mainItem.peerAddress.value : ""
text: mainItem.peerAddress
color: DefaultStyle.grey_0
font {
pixelSize: 22 * DefaultStyle.dp
@ -99,8 +100,8 @@ Item {
anchors.leftMargin: 10 * DefaultStyle.dp
anchors.bottomMargin: 10 * DefaultStyle.dp
width: txtMeter.width
text: mainItem.call && mainItem.peerAddress
? mainItem.peerAddress.value
text: mainItem.peerAddress.length != 0
? mainItem.peerAddress
: mainItem.account && mainItem.identityAddress
? mainItem.identityAddress.value
: ""

View file

@ -1,140 +0,0 @@
import QtQuick 2.7
import QtQuick.Layouts 1.3
import Linphone
import UtilsCpp 1.0
ListView {
id: mainItem
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
height: contentHeight
visible: count > 0
property string searchBarText
property bool contactMenuVisible: true
property bool initialHeadersVisible: true
signal contactSelected(var contact)
model: MagicSearchProxy {
searchText: searchBarText.length === 0 ? "*" : searchBarText
}
delegate: RowLayout {
id: itemDelegate
property var previousItem : mainItem.model.count > 0 && index > 0 ? mainItem.model.getAt(index-1) : null
property var previousDisplayName: previousItem ? UtilsCpp.getDisplayName(previousItem.core.address) : null
property string previousDisplayNameText: previousDisplayName ? previousDisplayName.value : ""
property var displayName: UtilsCpp.getDisplayName(modelData.core.address)
property string displayNameText: displayName ? displayName.value : ""
Text {
Layout.preferredWidth: 20 * DefaultStyle.dp
opacity: (!previousItem || !previousDisplayNameText.startsWith(displayNameText[0])) ? 1 : 0
text: displayNameText[0]
color: DefaultStyle.main2_400
font {
pixelSize: 20 * DefaultStyle.dp
weight: 500 * DefaultStyle.dp
capitalization: Font.AllUppercase
}
}
Item {
width: mainItem.width
Layout.preferredWidth: mainItem.width
height: 56 * DefaultStyle.dp
RowLayout {
anchors.fill: parent
z: 1
Avatar {
Layout.preferredWidth: 45 * DefaultStyle.dp
Layout.preferredHeight: 45 * DefaultStyle.dp
contact: modelData
}
Text {
text: itemDelegate.displayNameText
font.pixelSize: 14 * DefaultStyle.dp
font.capitalization: Font.Capitalize
}
Item {
Layout.fillWidth: true
}
PopupButton {
id: friendPopup
hoverEnabled: true
visible: mainItem.contactMenuVisible && (contactArea.containsMouse || hovered || popup.opened)
popup.x: 0
popup.padding: 10 * DefaultStyle.dp
popup.contentItem: ColumnLayout {
Button {
background: Item{}
contentItem: RowLayout {
Image {
source: modelData.core.starred ? AppIcons.heartFill : AppIcons.heart
fillMode: Image.PreserveAspectFit
width: 24 * DefaultStyle.dp
height: 24 * DefaultStyle.dp
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
}
Text {
text: modelData.core.starred ? qsTr("Enlever des favoris") : qsTr("Mettre en favori")
color: DefaultStyle.main2_500main
font {
pixelSize: 14 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
}
}
onClicked: {
modelData.core.lSetStarred(!modelData.core.starred)
friendPopup.close()
}
}
Button {
background: Item{}
contentItem: RowLayout {
EffectImage {
source: AppIcons.trashCan
width: 24 * DefaultStyle.dp
height: 24 * DefaultStyle.dp
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
fillMode: Image.PreserveAspectFit
colorizationColor: DefaultStyle.danger_500main
}
Text {
text: qsTr("Supprimmer")
color: DefaultStyle.danger_500main
font {
pixelSize: 14 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
}
}
onClicked: {
modelData.core.remove()
friendPopup.close()
}
}
}
}
}
MouseArea {
id: contactArea
hoverEnabled: true
anchors.fill: parent
Rectangle {
anchors.fill: contactArea
opacity: 0.1
color: DefaultStyle.main2_500main
visible: contactArea.containsMouse || friendPopup.hovered
}
onClicked: {
mainItem.contactSelected(modelData)
}
}
}
}
}

View file

@ -0,0 +1,39 @@
import QtQuick 2.15
import QtQuick.Effects
import QtQuick.Layouts
import Linphone
MouseArea {
id: mainItem
property string iconSource
property string text
property color color: DefaultStyle.main2_600
property int iconSize: 17 * DefaultStyle.dp
property int textSize: 14 * DefaultStyle.dp
property int textWeight: 400 * DefaultStyle.dp
hoverEnabled: true
cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor
width: content.implicitWidth
RowLayout {
id: content
anchors.verticalCenter: parent.verticalCenter
EffectImage {
Layout.preferredWidth: mainItem.iconSize
Layout.preferredHeight: mainItem.iconSize
width: mainItem.iconSize
height: mainItem.iconSize
source: mainItem.iconSource
colorizationColor: mainItem.color
}
Text {
width: implicitWidth
Layout.fillWidth: true
text: mainItem.text
color: mainItem.color
font {
pixelSize: mainItem.textSize
weight: mainItem.textWeight
}
}
}
}

View file

@ -24,15 +24,16 @@ Control.Popup {
color: DefaultStyle.grey_100
radius: 20 * DefaultStyle.dp
}
MultiEffect {
id: effect
anchors.fill: parent
source: numPadBackground
shadowEnabled: true
shadowColor: DefaultStyle.grey_1000
shadowOpacity: 0.8
shadowBlur: 1
}
// MultiEffect {
// id: effect
// anchors.fill: parent
// source: numPadBackground
// shadowEnabled: true
// shadowColor: DefaultStyle.grey_1000
// shadowOpacity: 0.1
// shadowVerticalOffset: -200 * DefaultStyle.dp
// shadowBlur: 1
// }
Rectangle {
width: parent.width
height: parent.height / 2

View file

@ -0,0 +1,17 @@
import QtQuick
import QtQuick.Controls as Control
import Linphone
Control.Control {
width: 360 * DefaultStyle.dp
property color backgroundColor
topPadding: 10 * DefaultStyle.dp
bottomPadding: 10 * DefaultStyle.dp
leftPadding: 10 * DefaultStyle.dp
rightPadding: 10 * DefaultStyle.dp
background: Rectangle {
anchors.fill: parent
radius: 15 * DefaultStyle.dp
color: mainItem.backgroundColor ? mainItem.backgroundColor : DefaultStyle.grey_0
}
}

View file

@ -15,6 +15,8 @@ ColumnLayout {
property var validator: RegularExpressionValidator{}
property bool fillWidth: false
property bool enableBackgroundColors: true
property color backgroundColor: DefaultStyle.grey_100
property color backgroundBorderColor: DefaultStyle.grey_200
property string initialText
property bool enableErrorText: false
@ -26,7 +28,11 @@ ColumnLayout {
readonly property string text: textField.text
readonly property bool hasActiveFocus: textField.activeFocus
Component.onCompleted: setText(initialText)
signal editingFinished()
Component.onCompleted: {
setText(initialText)
}
function setText(text) {
textField.text = text
@ -60,14 +66,12 @@ ColumnLayout {
Layout.preferredWidth: mainItem.textInputWidth
Layout.preferredHeight: 49 * DefaultStyle.dp
radius: 79 * DefaultStyle.dp
color: mainItem.enableBackgroundColors ? DefaultStyle.grey_100 : "transparent"
border.color: mainItem.enableBackgroundColors
? mainItem.errorTextVisible
? DefaultStyle.danger_500main
: textField.activeFocus
? DefaultStyle.main1_500_main
: DefaultStyle.grey_200
: "transparent"
color: mainItem.backgroundColor
border.color: mainItem.errorTextVisible
? DefaultStyle.danger_500main
: textField.activeFocus
? DefaultStyle.main1_500_main
: mainItem.backgroundBorderColor
Control.TextField {
id: textField
@ -94,6 +98,15 @@ ColumnLayout {
color: DefaultStyle.main1_500_main
width: 2 * DefaultStyle.dp
}
onEditingFinished: {
mainItem.editingFinished()
}
Keys.onPressed: (event)=> {
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
textField.focus = false
}
}
}
Control.Button {
id: eyeButton

View file

@ -19,23 +19,124 @@ Item {
property bool showDefaultItem: true
signal noItemButtonPressed()
Control.SplitView {
id: splitView
anchors.fill: parent
// Control.SplitView {
// id: splitView
// anchors.fill: parent
// anchors.topMargin: 10 * DefaultStyle.dp
handle: Rectangle {
implicitWidth: 8 * DefaultStyle.dp
color: Control.SplitHandle.hovered ? DefaultStyle.grey_200 : DefaultStyle.grey_100
}
// handle: Rectangle {
// implicitWidth: 8 * DefaultStyle.dp
// color: Control.SplitHandle.hovered ? DefaultStyle.grey_200 : DefaultStyle.grey_100
// }
// ColumnLayout {
// id: leftPanel
// Control.SplitView.preferredWidth: 350 * DefaultStyle.dp
// Control.SplitView.minimumWidth: 350 * DefaultStyle.dp
// }
// Rectangle {
// id: rightPanel
// clip: true
// color: DefaultStyle.grey_100
// StackLayout {
// currentIndex: mainItem.showDefaultItem ? 0 : 1
// anchors.fill: parent
// ColumnLayout {
// id: defaultItem
// Layout.fillWidth: true
// Layout.fillHeight: true
// RowLayout {
// Layout.fillHeight: true
// Layout.fillWidth: true
// Layout.alignment: Qt.AlignHCenter
// Item {
// Layout.fillWidth: true
// }
// ColumnLayout {
// spacing: 30 * DefaultStyle.dp
// Item {
// Layout.fillHeight: true
// }
// Image {
// Layout.alignment: Qt.AlignHCenter
// source: AppIcons.noItemImage
// Layout.preferredWidth: 359 * DefaultStyle.dp
// Layout.preferredHeight: 314 * DefaultStyle.dp
// fillMode: Image.PreserveAspectFit
// }
// Text {
// text: mainItem.emptyListText
// Layout.alignment: Qt.AlignHCenter
// font {
// pixelSize: 22 * DefaultStyle.dp
// weight: 800 * DefaultStyle.dp
// }
// }
// Button {
// Layout.alignment: Qt.AlignHCenter
// contentItem: RowLayout {
// Layout.alignment: Qt.AlignVCenter
// EffectImage {
// colorizationColor: DefaultStyle.grey_0
// source: mainItem.newItemIconSource
// width: 24 * DefaultStyle.dp
// height: 24 * DefaultStyle.dp
// fillMode: Image.PreserveAspectFit
// }
// Text {
// text: mainItem.noItemButtonText
// wrapMode: Text.WordWrap
// color: DefaultStyle.grey_0
// font {
// weight: 600 * DefaultStyle.dp
// pixelSize: 18 * DefaultStyle.dp
// family: DefaultStyle.defaultFont
// }
// }
// }
// onPressed: mainItem.noItemButtonPressed()
// }
// Item {
// Layout.fillHeight: true
// }
// }
// Item {
// Layout.fillWidth: true
// }
// }
// }
// ColumnLayout {
// id: rightPanelItem
// Layout.fillWidth: true
// Layout.fillHeight: true
// }
// }
// }
// }
RowLayout {
anchors.fill: parent
anchors.topMargin: 10 * DefaultStyle.dp
spacing: 0
ColumnLayout {
id: leftPanel
Control.SplitView.preferredWidth: 350 * DefaultStyle.dp
Control.SplitView.minimumWidth: 350 * DefaultStyle.dp
Layout.preferredWidth: 403 * DefaultStyle.dp
Layout.minimumWidth: 403 * DefaultStyle.dp
Layout.fillHeight: true
Layout.fillWidth: false
}
Rectangle {
Layout.fillHeight: true
Layout.preferredWidth: 1 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
Rectangle {
id: rightPanel
clip: true
color: DefaultStyle.grey_100
Layout.fillWidth: true
Layout.fillHeight: true
StackLayout {
currentIndex: mainItem.showDefaultItem ? 0 : 1
anchors.fill: parent

View file

@ -17,7 +17,7 @@ AbstractMainPage {
onNoItemButtonPressed: goToNewCall()
showDefaultItem: listStackView.currentItem.listView ? listStackView.currentItem.listView.count === 0 : true
showDefaultItem: listStackView.currentItem.listView && listStackView.currentItem.listView.count === 0 && listStackView.currentItem.listView.model.sourceModel.count === 0 || false
function goToNewCall() {
listStackView.push(newCallItem)
@ -51,6 +51,7 @@ AbstractMainPage {
id: historyListItem
ColumnLayout {
property alias listView: historyListView
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: listStackView.sideMargin
@ -91,7 +92,9 @@ AbstractMainPage {
}
Connections {
target: deleteHistoryPopup
onAccepted: historyListView.model.removeAllEntries()
onAccepted: {
historyListView.model.removeAllEntries()
}
}
onClicked: {
removeHistory.close()
@ -156,6 +159,7 @@ AbstractMainPage {
model: CallHistoryProxy{
filterText: searchBar.text
}
currentIndex: -1
spacing: 10 * DefaultStyle.dp
@ -205,7 +209,7 @@ AbstractMainPage {
anchors.verticalCenter: parent.verticalCenter
Text {
id: friendAddress
property var remoteAddress: modelData ? UtilsCpp.getDisplayName(modelData.core.remoteAddress) : undefined
property var remoteAddress: modelData ? UtilsCpp.getDisplayName(modelData.core.remoteAddress) : null
text: remoteAddress ? remoteAddress.value : ""
font {
pixelSize: 14 * DefaultStyle.dp
@ -288,18 +292,13 @@ AbstractMainPage {
onCurrentIndexChanged: {
mainItem.selectedRowHistoryGui = model.getAt(currentIndex)
}
onCountChanged: {
mainItem.showDefaultItem = historyListView.count === 0 && historyListView.visible
}
onVisibleChanged: {
mainItem.showDefaultItem = historyListView.count === 0 && historyListView.visible
if (!visible) currentIndex = -1
console.log("visible", visible)
}
Connections {
target: mainItem
onShowDefaultItemChanged: mainItem.showDefaultItem = mainItem.showDefaultItem && historyListView.count === 0 && historyListView.visible
onListViewUpdated: {
historyListView.model.updateView()
}
@ -312,7 +311,7 @@ AbstractMainPage {
Control.ScrollBar {
id: scrollbar
active: true
policy: Control.ScrollBar.AlwaysOn
policy: Control.ScrollBar.AsNeeded
Layout.fillHeight: true
}
}
@ -322,10 +321,6 @@ AbstractMainPage {
Component {
id: newCallItem
ColumnLayout {
Control.StackView.onActivating: {
mainItem.showDefaultItem = false
}
Control.StackView.onDeactivating: mainItem.showDefaultItem = true
RowLayout {
Layout.leftMargin: listStackView.sideMargin
Layout.rightMargin: listStackView.sideMargin
@ -350,214 +345,141 @@ AbstractMainPage {
Layout.fillWidth: true
}
}
RowLayout {
CallContactsLists {
Layout.fillWidth: true
Layout.fillHeight: true
// Layout.maximumWidth: parent.width
CallContactsLists {
Layout.fillWidth: true
Layout.fillHeight: true
// Layout.leftMargin: listStackView.sideMargin
// Layout.rightMargin: listStackView.sideMargin
groupCallVisible: true
searchBarColor: DefaultStyle.grey_100
onCallButtonPressed: (address) => {
var addressEnd = "@sip.linphone.org"
if (!address.endsWith(addressEnd)) address += addressEnd
UtilsCpp.createCall(address)
// var window = UtilsCpp.getCallsWindow()
}
// Layout.leftMargin: listStackView.sideMargin
// Layout.rightMargin: listStackView.sideMargin
groupCallVisible: true
searchBarColor: DefaultStyle.grey_100
onCallButtonPressed: (address) => {
var addressEnd = "@sip.linphone.org"
if (!address.endsWith(addressEnd)) address += addressEnd
UtilsCpp.createCall(address)
// var window = UtilsCpp.getCallsWindow()
}
}
}
}
}
rightPanelContent: RowLayout {
rightPanelContent: Control.StackView {
id: rightPanelStackView
Layout.fillWidth: true
Layout.fillHeight: true
visible: mainItem.selectedRowHistoryGui != undefined
Layout.topMargin: 45 * DefaultStyle.dp
Item {
Layout.fillWidth: true
Layout.fillHeight: true
initialItem: emptySelection
Connections {
target: mainItem
onSelectedRowHistoryGuiChanged: {
if (mainItem.selectedRowHistoryGui) rightPanelStackView.replace(contactDetailComp, Control.StackView.Immediate)
else rightPanelStackView.replace(emptySelection, Control.StackView.Immediate)
}
}
ColumnLayout {
spacing: 30 * DefaultStyle.dp
Layout.fillWidth: true
Layout.fillHeight: true
Item {
Layout.preferredHeight: detailAvatar.height
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Avatar {
id: detailAvatar
anchors.centerIn: parent
width: 100 * DefaultStyle.dp
height: 100 * DefaultStyle.dp
address: mainItem.selectedRowHistoryGui && mainItem.selectedRowHistoryGui.core.remoteAddress || ""
}
PopupButton {
id: detailOptions
anchors.left: detailAvatar.right
anchors.leftMargin: 30 * DefaultStyle.dp
anchors.verticalCenter: parent.verticalCenter
popup.x: width
popup.contentItem: ColumnLayout {
Button {
background: Item {}
contentItem: IconLabel {
text: qsTr("Ajouter aux contacts")
iconSource: AppIcons.plusCircle
}
onClicked: {
// console.debug("[CallPage.qml] TODO : add to contact")
var friendGui = Qt.createQmlObject('import Linphone
FriendGui{}', detailAvatar)
friendGui.core.name = contactName.text
friendGui.core.address = contactAddress.text
friendGui.core.save()
}
onCurrentItemChanged: {
}
}
Component{
id: emptySelection
Item{}
}
Component {
id: contactDetailComp
ContactLayout {
id: contactDetail
visible: mainItem.selectedRowHistoryGui != undefined
property var remoteName: mainItem.selectedRowHistoryGui ? UtilsCpp.getDisplayName(mainItem.selectedRowHistoryGui.core.remoteAddress) : null
contactAddress: mainItem.selectedRowHistoryGui && mainItem.selectedRowHistoryGui.core.remoteAddress || ""
contactName: remoteName ? remoteName.value : ""
anchors.top: rightPanelStackView.top
anchors.bottom: rightPanelStackView.bottom
anchors.topMargin: 45 * DefaultStyle.dp
anchors.bottomMargin: 45 * DefaultStyle.dp
buttonContent: PopupButton {
id: detailOptions
anchors.right: parent.right
anchors.rightMargin: 30 * DefaultStyle.dp
anchors.verticalCenter: parent.verticalCenter
popup.x: width
popup.contentItem: ColumnLayout {
Button {
background: Item {}
contentItem: IconLabel {
text: qsTr("Ajouter aux contacts")
iconSource: AppIcons.plusCircle
}
Button {
background: Item {}
contentItem: IconLabel {
text: qsTr("Copier l'adresse SIP")
iconSource: AppIcons.copy
}
onClicked: console.debug("[CallPage.qml] TODO : copy SIP address")
onClicked: {
// console.debug("[CallPage.qml] TODO : add to contact")
var friendGui = Qt.createQmlObject('import Linphone
FriendGui{
}', contactDetail)
detailOptions.close()
friendGui.core.givenName = UtilsCpp.getGivenNameFromFullName(contactDetail.contactName)
friendGui.core.familyName = UtilsCpp.getFamilyNameFromFullName(contactDetail.contactName)
friendGui.core.appendAddress(contactDetail.contactAddress)
friendGui.core.defaultAddress = contactDetail.contactAddress
rightPanelStackView.push(editContact, {"contact": friendGui, "title": qsTr("Ajouter contact"), "saveButtonText": qsTr("Créer")})
}
Button {
background: Item {}
contentItem: IconLabel {
text: qsTr("Bloquer")
iconSource: AppIcons.empty
}
onClicked: console.debug("[CallPage.qml] TODO : block user")
}
Button {
background: Item {}
contentItem: IconLabel {
text: qsTr("Copier l'adresse SIP")
iconSource: AppIcons.copy
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 2 * DefaultStyle.dp
color: DefaultStyle.main2_400
onClicked: UtilsCpp.copyToClipboard(mainItem.selectedRowHistoryGui && mainItem.selectedRowHistoryGui.core.remoteAddress)
}
Button {
background: Item {}
enabled: false
contentItem: IconLabel {
text: qsTr("Bloquer")
iconSource: AppIcons.empty
}
Button {
background: Item {}
contentItem: IconLabel {
text: qsTr("Supprimer l'historique")
iconSource: AppIcons.trashCan
colorizationColor: DefaultStyle.danger_500main
}
Connections {
target: deleteForUserPopup
onAccepted: detailListView.model.removeEntriesWithFilter()
}
onClicked: {
detailOptions.close()
deleteForUserPopup.open()
}
onClicked: console.debug("[CallPage.qml] TODO : block user")
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 2 * DefaultStyle.dp
color: DefaultStyle.main2_400
}
Button {
background: Item {}
contentItem: IconLabel {
text: qsTr("Supprimer l'historique")
iconSource: AppIcons.trashCan
colorizationColor: DefaultStyle.danger_500main
}
Connections {
target: deleteForUserPopup
onAccepted: detailListView.model.removeEntriesWithFilter()
}
onClicked: {
detailOptions.close()
deleteForUserPopup.open()
}
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Text {
id: contactName
property var remoteAddress: mainItem.selectedRowHistoryGui ? UtilsCpp.getDisplayName(mainItem.selectedRowHistoryGui.core.remoteAddress) : undefined
Layout.alignment: Qt.AlignHCenter
text: remoteAddress ? remoteAddress.value : ""
horizontalAlignment: Text.AlignHCenter
font {
pixelSize: 14 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
}
Text {
id: contactAddress
text: mainItem.selectedRowHistoryGui ? mainItem.selectedRowHistoryGui.core.remoteAddress : ""
horizontalAlignment: Text.AlignHCenter
font {
pixelSize: 12 * DefaultStyle.dp
weight: 300 * DefaultStyle.dp
}
}
Text {
// connection status
}
}
RowLayout {
spacing: 40 * DefaultStyle.dp
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
// Layout.fillHeight: true
Item {Layout.fillWidth: true}
LabelButton {
Layout.preferredWidth: 24 * DefaultStyle.dp//image.width
Layout.preferredHeight: image.height
image.source: AppIcons.phone
label: qsTr("Appel")
button.onClicked: {
var addr = mainItem.selectedRowHistoryGui.core.remoteAddress
var addressEnd = "@sip.linphone.org"
if (!addr.endsWith(addressEnd)) addr += addressEnd
UtilsCpp.createCall(addr)
}
}
LabelButton {
Layout.preferredWidth: image.width
Layout.preferredHeight: image.height
image.source: AppIcons.chatTeardropText
label: qsTr("Message")
button.onClicked: console.debug("[CallPage.qml] TODO : open conversation")
}
LabelButton {
Layout.preferredWidth: image.width
Layout.preferredHeight: image.height
image.source: AppIcons.videoCamera
label: qsTr("Appel Video")
button.onClicked: {
var addr = mainItem.selectedRowHistoryGui.core.remoteAddress
var addressEnd = "@sip.linphone.org"
if(!addr.endsWith(addressEnd)) addr += addressEnd
UtilsCpp.createCall(addr)
console.log("[CallPage.qml] TODO : enable video")
}
}
Item {Layout.fillWidth: true}
}
Control.Control {
detailContent: Control.Control {
id: detailControl
Layout.preferredWidth: detailListView.width + leftPadding + rightPadding
implicitHeight: 430 * DefaultStyle.dp + topPadding + bottomPadding
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 30 * DefaultStyle.dp
topPadding: 16 * DefaultStyle.dp
bottomPadding: 21 * DefaultStyle.dp
leftPadding: 17 * DefaultStyle.dp
rightPadding: 17 * DefaultStyle.dp
// Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredWidth: 360 * DefaultStyle.dp
background: Rectangle {
id: detailListBackground
width: parent.width
height: detailListView.height
color: DefaultStyle.grey_0
radius: 15 * DefaultStyle.dp
anchors.fill: detailListView
}
ListView {
contentItem: ListView {
id: detailListView
width: 360 * DefaultStyle.dp
height: Math.min(detailControl.implicitHeight, contentHeight)
anchors.centerIn: detailListBackground
anchors.bottomMargin: 21 * DefaultStyle.dp
spacing: 10 * DefaultStyle.dp
width: parent.width
height: Math.min(detailControl.height, contentHeight)
spacing: 20 * DefaultStyle.dp
clip: true
onCountChanged: {
@ -570,10 +492,11 @@ AbstractMainPage {
delegate: Item {
width:detailListView.width
height: 56 * DefaultStyle.dp
// anchors.topMargin: 5 * DefaultStyle.dp
// anchors.bottomMargin: 5 * DefaultStyle.dp
RowLayout {
anchors.fill: parent
anchors.leftMargin: 20 * DefaultStyle.dp
anchors.rightMargin: 20 * DefaultStyle.dp
anchors.verticalCenter: parent.verticalCenter
ColumnLayout {
Layout.alignment: Qt.AlignVCenter
RowLayout {
@ -619,6 +542,7 @@ AbstractMainPage {
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
}
Text {
@ -633,9 +557,11 @@ AbstractMainPage {
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
Component {
id: editContact
ContactEdition {
onCloseEdition: rightPanelStackView.pop(Control.StackView.Immediate)
}
}

View file

@ -0,0 +1,574 @@
import QtQuick 2.15
import QtQuick.Effects
import QtQuick.Layouts
import QtQuick.Controls as Control
import Linphone
import UtilsCpp 1.0
AbstractMainPage {
id: mainItem
noItemButtonText: qsTr("Ajouter un contact")
emptyListText: qsTr("Aucun contact pour le moment")
newItemIconSource: AppIcons.newCall
// disable left panel contact list interaction while a contact is being edited
property bool leftPanelEnabled: true
property FriendGui selectedContact
signal forceListsUpdate()
onNoItemButtonPressed: createNewContact()
function createNewContact() {
console.debug("[ContactPage]User: create new contact")
var friendGui = Qt.createQmlObject('import Linphone
FriendGui{
}', contactDetail)
rightPanelStackView.replace(editContact, {"contact": friendGui, "title": qsTr("Nouveau contact"), "saveButtonText": qsTr("Créer")})
}
showDefaultItem: contactList.model.sourceModel.count === 0
function goToNewCall() {
listStackView.replace(newCallItem)
}
leftPanelContent: ColumnLayout {
id: leftPanel
Layout.fillWidth: true
Layout.fillHeight: true
property int sideMargin: 25 * DefaultStyle.dp
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: leftPanel.sideMargin
Layout.rightMargin: leftPanel.sideMargin
Text {
text: qsTr("Contacts")
color: DefaultStyle.main2_700
font.pixelSize: 29 * DefaultStyle.dp
font.weight: 800 * DefaultStyle.dp
}
Item {
Layout.fillWidth: true
}
Control.Button {
background: Item {
visible: false
}
contentItem: Image {
source: AppIcons.plusCircle
width: 30 * DefaultStyle.dp
sourceSize.width: 30 * DefaultStyle.dp
fillMode: Image.PreserveAspectFit
}
onClicked: {
mainItem.createNewContact()
}
}
}
ColumnLayout {
Layout.topMargin: 30 * DefaultStyle.dp
Layout.leftMargin: leftPanel.sideMargin
enabled: mainItem.leftPanelEnabled
SearchBar {
id: searchBar
Layout.rightMargin: leftPanel.sideMargin
Layout.fillWidth: true
placeholderText: qsTr("Rechercher un contact")
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Control.ScrollBar {
id: contactsScrollbar
active: true
interactive: true
policy: Control.ScrollBar.AsNeeded
// Layout.fillWidth: true
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
// Layout.alignment: Qt.AlignRight
// x: mainItem.x + mainItem.width - width
// anchors.left: control.right
}
Control.ScrollView {
id: listLayout
anchors.fill: parent
Layout.leftMargin: leftPanel.sideMargin
Layout.rightMargin: leftPanel.sideMargin
Layout.topMargin: 25 * DefaultStyle.dp
rightPadding: leftPanel.sideMargin
contentWidth: width - leftPanel.sideMargin
contentHeight: content.height
clip: true
Control.ScrollBar.vertical: contactsScrollbar
ColumnLayout {
id: content
width: parent.width
// anchors.fill: parent
spacing: 15 * DefaultStyle.dp
Text {
text: qsTr("Aucun contact")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
visible: contactList.count === 0 && favoriteList.count === 0
Layout.alignment: Qt.AlignHCenter
}
ColumnLayout {
visible: favoriteList.count > 0
RowLayout {
Text {
text: qsTr("Favoris")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
Item {
Layout.fillWidth: true
}
Button {
background: Item{}
contentItem: Image {
source: favoriteList.visible ? AppIcons.upArrow : AppIcons.downArrow
}
onClicked: favoriteList.visible = !favoriteList.visible
}
}
ContactsList{
id: favoriteList
hoverEnabled: mainItem.leftPanelEnabled
Layout.fillWidth: true
onContactStarredChanged: contactList.model.forceUpdate()
Connections {
target: mainItem
onForceListsUpdate: {
contactList.model.forceUpdate()
}
}
model: MagicSearchProxy {
searchText: searchBar.text.length === 0 ? "*" : searchBar.text
sourceFlags: LinphoneEnums.MagicSearchSource.FavoriteFriends
aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend
}
onSelectedContactChanged: {
if (selectedContact) {
contactList.currentIndex = -1
}
mainItem.selectedContact = selectedContact
}
}
}
ColumnLayout {
visible: contactList.count > 0
RowLayout {
Text {
text: qsTr("All contacts")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
Item {
Layout.fillWidth: true
}
Button {
background: Item{}
contentItem: Image {
source: contactList.visible ? AppIcons.upArrow : AppIcons.downArrow
}
onClicked: contactList.visible = !contactList.visible
}
}
ContactsList{
id: contactList
hoverEnabled: mainItem.leftPanelEnabled
Layout.fillWidth: true
searchBarText: searchBar.text
onContactStarredChanged: favoriteList.model.forceUpdate()
Connections {
target: mainItem
onForceListsUpdate: {
contactList.model.forceUpdate()
}
}
onSelectedContactChanged: {
if (selectedContact) {
favoriteList.currentIndex = -1
}
mainItem.selectedContact = selectedContact
}
}
}
}
}
}
}
}
rightPanelContent: Control.StackView {
id: rightPanelStackView
Layout.fillWidth: true
Layout.fillHeight: true
initialItem: contactDetail
Binding {
mainItem.showDefaultItem: false
when: rightPanelStackView.currentItem.objectName == "contactEdition"
restoreMode: Binding.RestoreBinding
}
}
Component {
id: contactDetail
RowLayout {
visible: mainItem.selectedContact != undefined
Layout.fillWidth: true
Layout.fillHeight: true
Control.StackView.onActivated:
mainItem.leftPanelEnabled = true
Control.StackView.onDeactivated: mainItem.leftPanelEnabled = false
ContactLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.topMargin: 45 * DefaultStyle.dp
Layout.leftMargin: 74 * DefaultStyle.dp
contact: mainItem.selectedContact
Layout.preferredWidth: 360 * DefaultStyle.dp
buttonContent: Button {
width: 24 * DefaultStyle.dp
height: 24 * DefaultStyle.dp
background: Item{}
contentItem: Image {
anchors.fill: parent
source: AppIcons.pencil
}
onClicked: rightPanelStackView.replace(editContact, Control.StackView.Immediate)
}
detailContent: ColumnLayout {
Layout.fillWidth: false
Layout.preferredWidth: 360 * DefaultStyle.dp
spacing: 32 * DefaultStyle.dp
ColumnLayout {
spacing: 15 * DefaultStyle.dp
Text {
text: qsTr("Informations")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
RoundedBackgroundControl {
Layout.preferredHeight: Math.min(226 * DefaultStyle.dp, addrList.contentHeight + topPadding + bottomPadding)
height: Math.min(226 * DefaultStyle.dp, addrList.contentHeight)
Layout.fillWidth: true
contentItem: ListView {
id: addrList
width: 360 * DefaultStyle.dp
height: contentHeight
clip: true
model: VariantList {
model: mainItem.selectedContact ? mainItem.selectedContact.core.allAdresses : []
}
// model: contactDetail.selectedContact && contactDetail.selectedContact.core.addresses
delegate: Item {
width: addrList.width
height: 70 * DefaultStyle.dp
ColumnLayout {
anchors.fill: parent
anchors.topMargin: 5 * DefaultStyle.dp
RowLayout {
Layout.fillWidth: true
// Layout.fillHeight: true
// Layout.alignment: Qt.AlignVCenter
Layout.topMargin: 10 * DefaultStyle.dp
Layout.bottomMargin: 10 * DefaultStyle.dp
ColumnLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
// TODO change with domain
text: modelData.label
font {
pixelSize: 13 * DefaultStyle.dp
weight: 700 * DefaultStyle.dp
}
}
Text {
Layout.fillWidth: true
text: modelData.address
font {
pixelSize: 14 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
}
}
Item {
Layout.fillWidth: true
}
Button {
background: Item{}
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
contentItem: Image {
anchors.fill: parent
source: AppIcons.phone
width: 24 * DefaultStyle.dp
height: 24 * DefaultStyle.dp
}
onClicked: {
UtilsCpp.createCall(modelData.address)
}
}
}
Rectangle {
visible: index != addrList.model.count - 1
Layout.fillWidth: true
Layout.preferredHeight: 1 * DefaultStyle.dp
Layout.rightMargin: 3 * DefaultStyle.dp
Layout.leftMargin: 3 * DefaultStyle.dp
color: DefaultStyle.main2_200
clip: true
}
}
}
}
}
}
RoundedBackgroundControl {
visible: companyText.text.length != 0 || jobText.text.length != 0
Layout.fillWidth: true
// Layout.fillHeight: true
contentItem: ColumnLayout {
// height: 100 * DefaultStyle.dp
RowLayout {
height: 50 * DefaultStyle.dp
Text {
text: qsTr("Company :")
font {
pixelSize: 13 * DefaultStyle.dp
weight: 700 * DefaultStyle.dp
}
}
Text {
id: companyText
text: mainItem.selectedContact && mainItem.selectedContact.core.organization
font {
pixelSize: 14 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
}
}
RowLayout {
height: 50 * DefaultStyle.dp
Text {
text: qsTr("Job :")
font {
pixelSize: 13 * DefaultStyle.dp
weight: 700 * DefaultStyle.dp
}
}
Text {
id: jobText
text: mainItem.selectedContact && mainItem.selectedContact.core.job
font {
pixelSize: 14 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
}
}
}
}
ColumnLayout {
visible: false
Text {
text: qsTr("Medias")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
Button {
Rectangle {
anchors.fill: parent
color: DefaultStyle.grey_0
radius: 15 * DefaultStyle.dp
}
contentItem: RowLayout {
Image {
Layout.preferredWidth: 24 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
source: AppIcons.shareNetwork
}
Text {
text: qsTr("Show media shared")
font {
pixelSize: 14 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp
}
}
}
onClicked: console.debug("TODO : go to shared media")
}
}
}
}
ColumnLayout {
spacing: 10 * DefaultStyle.dp
ColumnLayout {
visible: false
RowLayout {
Text {
text: qsTr("Confiance")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
}
RoundedBackgroundControl {
contentItem: ColumnLayout {
Text {
text: qsTr("Niveau de confiance - Appareils vérifiés")
}
}
}
}
ColumnLayout {
Text {
text: qsTr("Other actions")
font {
pixelSize: 16 * DefaultStyle.dp
weight: 800 * DefaultStyle.dp
}
}
RoundedBackgroundControl {
Layout.preferredWidth: 360 * DefaultStyle.dp
contentItem: ColumnLayout {
width: parent.width
IconLabelButton {
Layout.fillWidth: true
Layout.leftMargin: 15 * DefaultStyle.dp
Layout.rightMargin: 15 * DefaultStyle.dp
Layout.preferredHeight: 50 * DefaultStyle.dp
iconSize: 24 * DefaultStyle.dp
iconSource: AppIcons.pencil
text: qsTr("Edit")
onClicked: rightPanelStackView.replace(editContact, Control.StackView.Immediate)
}
Rectangle {
Layout.fillWidth: true
Layout.leftMargin: 15 * DefaultStyle.dp
Layout.rightMargin: 15 * DefaultStyle.dp
Layout.preferredHeight: 1 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
IconLabelButton {
Layout.fillWidth: true
Layout.leftMargin: 15 * DefaultStyle.dp
Layout.rightMargin: 15 * DefaultStyle.dp
Layout.preferredHeight: 50 * DefaultStyle.dp
iconSize: 24 * DefaultStyle.dp
iconSource: mainItem.selectedContact && mainItem.selectedContact.core.starred ? AppIcons.heartFill : AppIcons.heart
text: mainItem.selectedContact && mainItem.selectedContact.core.starred ? qsTr("Remove from favourites") : qsTr("Add to favourites")
onClicked: if (mainItem.selectedContact) mainItem.selectedContact.core.lSetStarred(!mainItem.selectedContact.core.starred)
}
Rectangle {
Layout.fillWidth: true
Layout.leftMargin: 15 * DefaultStyle.dp
Layout.rightMargin: 15 * DefaultStyle.dp
Layout.preferredHeight: 1 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
IconLabelButton {
Layout.fillWidth: true
Layout.leftMargin: 15 * DefaultStyle.dp
Layout.rightMargin: 15 * DefaultStyle.dp
Layout.preferredHeight: 50 * DefaultStyle.dp
iconSize: 24 * DefaultStyle.dp
iconSource: AppIcons.shareNetwork
text: qsTr("Share")
onClicked: console.log("TODO : share contact")
}
Rectangle {
Layout.fillWidth: true
Layout.leftMargin: 15 * DefaultStyle.dp
Layout.rightMargin: 15 * DefaultStyle.dp
Layout.preferredHeight: 1 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
IconLabelButton {
Layout.fillWidth: true
Layout.leftMargin: 15 * DefaultStyle.dp
Layout.rightMargin: 15 * DefaultStyle.dp
Layout.preferredHeight: 50 * DefaultStyle.dp
iconSize: 24 * DefaultStyle.dp
iconSource: AppIcons.bellSlash
text: qsTr("Mute")
onClicked: console.log("TODO : mute contact")
}
Rectangle {
Layout.fillWidth: true
Layout.leftMargin: 15 * DefaultStyle.dp
Layout.rightMargin: 15 * DefaultStyle.dp
Layout.preferredHeight: 1 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
IconLabelButton {
Layout.fillWidth: true
Layout.leftMargin: 15 * DefaultStyle.dp
Layout.rightMargin: 15 * DefaultStyle.dp
Layout.preferredHeight: 50 * DefaultStyle.dp
iconSize: 24 * DefaultStyle.dp
iconSource: AppIcons.empty
text: qsTr("Block")
onClicked: console.log("TODO : block contact")
}
Rectangle {
Layout.fillWidth: true
Layout.leftMargin: 15 * DefaultStyle.dp
Layout.rightMargin: 15 * DefaultStyle.dp
Layout.preferredHeight: 1 * DefaultStyle.dp
color: DefaultStyle.main2_200
}
IconLabelButton {
Layout.fillWidth: true
Layout.leftMargin: 15 * DefaultStyle.dp
Layout.rightMargin: 15 * DefaultStyle.dp
Layout.preferredHeight: 50 * DefaultStyle.dp
iconSize: 24 * DefaultStyle.dp
iconSource: AppIcons.trashCan
color: DefaultStyle.danger_500main
text: qsTr("Delete this contact")
onClicked: mainItem.selectedContact.core.remove()
}
}
}
}
// TODO : find device by friend
}
}
}
Component {
id: editContact
ContactEdition {
id: contactEdition
property string objectName: "contactEdition"
contact: mainItem.selectedContact
onCloseEdition: {
mainItem.forceListsUpdate()
rightPanelStackView.replace(contactDetail, Control.StackView.Immediate)
}
}
}
}

View file

@ -38,8 +38,8 @@ ListView{
Text{
// Store the VariantObject and use value on this object. Do not use value in one line because of looping signals.
property var displayName: UtilsCpp.getDisplayName($modelData.identityAddress)
text: displayName.value
onTextChanged: console.log('[ProtoAccounts] Async account displayName: ' +$modelData.identityAddress + " => " +text)
text: displayName ? displayName.value : ""
onTextChanged: console.log("[ProtoAccounts] Async account displayName: " +$modelData.identityAddress + " => " +text)
}
Text{
text: $modelData.registrationState == LinphoneEnums.RegistrationState.Ok

View file

@ -18,8 +18,8 @@ Window{
}
TextInput{
placeholderText: 'Name'
initialText: contact.core.name
onTextChanged: contact.core.name = text
initialText: contact.core.givenName
onTextChanged: contact.core.givenName = text
}
TextInput{
placeholderText: 'Address'

View file

@ -51,6 +51,7 @@ QtObject {
property string outgoingCallRejected: "image://internal/outgoing_call_rejected.svg"
property string microphone: "image://internal/microphone.svg"
property string microphoneSlash: "image://internal/microphone-slash.svg"
property string camera: "image://internal/camera.svg"
property string videoCamera: "image://internal/video-camera.svg"
property string videoCameraSlash: "image://internal/video-camera-slash.svg"
property string speaker: "image://internal/speaker-high.svg"
@ -67,4 +68,9 @@ QtObject {
property string heartFill: "image://internal/heart-fill.svg"
property string recordFill: "image://internal/record-fill.svg"
property string mediaEncryptionZrtpPq: "image://internal/media_encryption_zrtp_pq.svg"
}
property string pencil: "image://internal/pencil-simple.svg"
property string shareNetwork: "image://internal/share-network.svg"
property string bell: "image://internal/bell-simple.svg"
property string bellSlash: "image://internal/bell-simple-slash.svg"
}