diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index 4acc3d1a9..82f607be6 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -1149,6 +1149,16 @@ bool App::event(QEvent *event) { } else if (event->type() == QEvent::ApplicationStateChange) { auto state = static_cast(event); if (state->applicationState() == Qt::ApplicationActive) Utils::smartShowWindow(getLastActiveWindow()); + } else if (event->type() == QEvent::ApplicationActivate) { + for (int i = 0; i < getAccountList()->rowCount(); ++i) { + auto accountCore = getAccountList()->getAt(i); + emit accountCore->lSetPresence(LinphoneEnums::Presence::Online, false, false); + } + } else if (event->type() == QEvent::ApplicationDeactivate) { + for (int i = 0; i < getAccountList()->rowCount(); ++i) { + auto accountCore = getAccountList()->getAt(i); + emit accountCore->lSetPresence(LinphoneEnums::Presence::Away, false, false); + } } return SingleApplication::event(event); diff --git a/Linphone/core/account/AccountCore.cpp b/Linphone/core/account/AccountCore.cpp index 2c60f91aa..878617e6d 100644 --- a/Linphone/core/account/AccountCore.cpp +++ b/Linphone/core/account/AccountCore.cpp @@ -85,6 +85,14 @@ AccountCore::AccountCore(const std::shared_ptr &account) : QO // Add listener mAccountModel = Utils::makeQObject_ptr(account); // OK mAccountModel->setSelf(mAccountModel); + mExplicitPresence = LinphoneEnums::fromString( + Utils::coreStringToAppString(CoreModel::getInstance()->getCore()->getConfig()->getString( + ToolModel::configAccountSection(account), "explicit_presence", ""))); + mPresenceNote = Utils::coreStringToAppString(CoreModel::getInstance()->getCore()->getConfig()->getString( + ToolModel::configAccountSection(account), "presence_note", "")); + mMaxPresenceNoteSize = CoreModel::getInstance()->getCore()->getConfig()->getInt( + ToolModel::configAccountSection(account), "max_presence_note_size", 140); + mPresence = mAccountModel->getPresence(); mNotificationsAllowed = mAccountModel->getNotificationsAllowed(); mDialPlan = Utils::createDialPlanVariant("", " "); mDialPlans << mDialPlan; @@ -222,6 +230,16 @@ void AccountCore::setSelf(QSharedPointer me) { mAccountModelConnection->makeConnectToModel( &AccountModel::removed, [this]() { mAccountModelConnection->invokeToCore([this]() { emit removed(); }); }); + mAccountModelConnection->makeConnectToModel( + &AccountModel::presenceChanged, [this](LinphoneEnums::Presence presence, bool userInitiated) { + mAccountModelConnection->invokeToCore([this, presence, userInitiated]() { + if (userInitiated) mExplicitPresence = presence; + else mExplicitPresence = LinphoneEnums::Presence::Undefined; + mPresence = presence; + emit presenceChanged(); + }); + }); + // From GUI mAccountModelConnection->makeConnectToCore(&AccountCore::lSetPictureUri, [this](QString uri) { mAccountModelConnection->invokeToModel([this, uri]() { mAccountModel->setPictureUri(uri); }); @@ -258,6 +276,13 @@ void AccountCore::setSelf(QSharedPointer me) { mAccountModelConnection->makeConnectToCore(&AccountCore::lSetNotificationsAllowed, [this](bool value) { mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setNotificationsAllowed(value); }); }); + mAccountModelConnection->makeConnectToCore( + &AccountCore::lSetPresence, [this](LinphoneEnums::Presence presence, bool userInitiated, bool resetToAuto) { + mAccountModelConnection->invokeToModel( + [this, presence, userInitiated, resetToAuto, presenceNote = mPresenceNote]() { + mAccountModel->setPresence(presence, userInitiated, resetToAuto, presenceNote); + }); + }); DEFINE_CORE_GET_CONNECT(mAccountModelConnection, AccountCore, AccountModel, mAccountModel, int, voicemailCount, VoicemailCount) @@ -425,6 +450,30 @@ QString AccountCore::getHumanReadableRegistrationState() const { } } +QColor AccountCore::getRegistrationColor() const { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + switch (mRegistrationState) { + case LinphoneEnums::RegistrationState::Ok: + return Utils::getDefaultStyleColor("success_500main"); + case LinphoneEnums::RegistrationState::Refreshing: + return Utils::getDefaultStyleColor("main2_500main"); + case LinphoneEnums::RegistrationState::Progress: + return Utils::getDefaultStyleColor("main2_500main"); + case LinphoneEnums::RegistrationState::Failed: + return Utils::getDefaultStyleColor("danger_500main"); + case LinphoneEnums::RegistrationState::None: + case LinphoneEnums::RegistrationState::Cleared: + return Utils::getDefaultStyleColor("warning_600"); + default: + return " "; + } +} + +QUrl AccountCore::getRegistrationIcon() const { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + return Utils::getRegistrationStateIcon(mRegistrationState); +} + QString AccountCore::getHumanReadableRegistrationStateExplained() const { switch (mRegistrationState) { case LinphoneEnums::RegistrationState::Ok: @@ -804,3 +853,38 @@ void AccountCore::undo() { }); } } + +LinphoneEnums::Presence AccountCore::getPresence() { + return mPresence; +} + +QColor AccountCore::getPresenceColor() { + return Utils::getPresenceColor(mPresence); +} + +QUrl AccountCore::getPresenceIcon() { + return Utils::getPresenceIcon(mPresence); +} + +QString AccountCore::getPresenceStatus() { + return Utils::getPresenceStatus(mPresence); +} + +void AccountCore::resetToAutomaticPresence() { + emit lSetPresence(LinphoneEnums::Presence::Online, false, true); +} + +LinphoneEnums::Presence AccountCore::getExplicitPresence() { + return mExplicitPresence; +} + +void AccountCore::setPresenceNote(QString presenceNote) { + if (presenceNote != mPresenceNote) { + mPresenceNote = presenceNote; + emit lSetPresence(mPresence, mExplicitPresence != LinphoneEnums::Presence::Undefined, false); + } +} + +QString AccountCore::getPresenceNote() { + return mPresenceNote; +} diff --git a/Linphone/core/account/AccountCore.hpp b/Linphone/core/account/AccountCore.hpp index 95e7a3739..70bf45107 100644 --- a/Linphone/core/account/AccountCore.hpp +++ b/Linphone/core/account/AccountCore.hpp @@ -75,6 +75,15 @@ public: Q_PROPERTY(bool isSaved READ isSaved WRITE setIsSaved NOTIFY isSavedChanged) Q_PROPERTY( QString voicemailAddress READ getVoicemailAddress WRITE setVoicemailAddress NOTIFY voicemailAddressChanged) + Q_PROPERTY(LinphoneEnums::Presence presence READ getPresence WRITE lSetPresence NOTIFY presenceChanged) + Q_PROPERTY(QColor presenceColor READ getPresenceColor NOTIFY presenceChanged) + Q_PROPERTY(QUrl presenceIcon READ getPresenceIcon NOTIFY presenceChanged) + Q_PROPERTY(QString presenceStatus READ getPresenceStatus NOTIFY presenceChanged) + Q_PROPERTY(QColor registrationColor READ getRegistrationColor NOTIFY registrationStateChanged) + Q_PROPERTY(QUrl registrationIcon READ getRegistrationIcon NOTIFY registrationStateChanged) + Q_PROPERTY(LinphoneEnums::Presence explicitPresence MEMBER mExplicitPresence NOTIFY presenceChanged) + Q_PROPERTY(QString presenceNote READ getPresenceNote WRITE setPresenceNote NOTIFY presenceChanged) + Q_PROPERTY(int maxPresenceNoteSize MEMBER mMaxPresenceNoteSize CONSTANT) DECLARE_CORE_GET(int, voicemailCount, VoicemailCount) static QSharedPointer create(const std::shared_ptr &account); @@ -114,6 +123,8 @@ public: void onDialPlanChanged(QVariantMap internationalPrefix); QString getHumanReadableRegistrationState() const; QString getHumanReadableRegistrationStateExplained() const; + QColor getRegistrationColor() const; + QUrl getRegistrationIcon() const; bool getRegisterEnabled() const; void onRegisterEnabledChanged(bool enabled); @@ -170,6 +181,15 @@ public: Q_INVOKABLE void save(); Q_INVOKABLE void undo(); + QColor getPresenceColor(); + QUrl getPresenceIcon(); + QString getPresenceStatus(); + LinphoneEnums::Presence getPresence(); + Q_INVOKABLE void resetToAutomaticPresence(); + LinphoneEnums::Presence getExplicitPresence(); + void setPresenceNote(QString presenceNote); + QString getPresenceNote(); + signals: void pictureUriChanged(); void registrationStateChanged(const QString &message); @@ -198,6 +218,7 @@ signals: void removed(); void isSavedChanged(); void voicemailAddressChanged(); + void presenceChanged(); // Account requests void lSetPictureUri(QString pictureUri); @@ -209,6 +230,7 @@ signals: void lSetDialPlan(QVariantMap internationalPrefix); void lSetRegisterEnabled(bool enabled); void lSetNotificationsAllowed(bool value); + void lSetPresence(LinphoneEnums::Presence presence, bool userInitiated = true, bool resetToAuto = false); protected: void writeIntoModel(std::shared_ptr model) const; @@ -243,6 +265,10 @@ private: QString mAudioVideoConferenceFactoryAddress; QString mLimeServerUrl; QString mVoicemailAddress; + LinphoneEnums::Presence mPresence = LinphoneEnums::Presence::Undefined; + LinphoneEnums::Presence mExplicitPresence; + QString mPresenceNote; + int mMaxPresenceNoteSize; bool mIsSaved = true; std::shared_ptr mAccountModel; diff --git a/Linphone/core/account/AccountList.cpp b/Linphone/core/account/AccountList.cpp index e6f026f4c..4b930e2b6 100644 --- a/Linphone/core/account/AccountList.cpp +++ b/Linphone/core/account/AccountList.cpp @@ -68,6 +68,10 @@ void AccountList::setSelf(QSharedPointer me) { setHaveAccount(accounts->size() > 0); setDefaultAccount(defaultAccountCore); if (isInitialization) setInitialized(true); + for (const QSharedPointer &accountCore : *accounts) { + if (accountCore->getExplicitPresence() != LinphoneEnums::Presence::Undefined) + emit accountCore->lSetPresence(accountCore->getExplicitPresence(), true, false); + } delete accounts; }); }); @@ -88,7 +92,8 @@ void AccountList::setSelf(QSharedPointer me) { // with the open id account mModelConnection->makeConnectToModel(&CoreModel::bearerAccountAdded, [this] { setInitialized(false); - emit lUpdate(true); }); + emit lUpdate(true); + }); mModelConnection->makeConnectToModel( &CoreModel::globalStateChanged, diff --git a/Linphone/core/friend/FriendCore.cpp b/Linphone/core/friend/FriendCore.cpp index 1b3e2e13d..622a09287 100644 --- a/Linphone/core/friend/FriendCore.cpp +++ b/Linphone/core/friend/FriendCore.cpp @@ -48,8 +48,9 @@ FriendCore::FriendCore(const std::shared_ptr &contact, bool is mustBeInLinphoneThread(getClassName()); mFriendModel = Utils::makeQObject_ptr(contact); mFriendModel->setSelf(mFriendModel); - mConsolidatedPresence = LinphoneEnums::fromLinphone(contact->getConsolidatedPresence()); - mPresenceTimestamp = mFriendModel->getPresenceTimestamp(); + auto presence = mFriendModel->getPresence(contact); + auto note = mFriendModel->getPresenceNote(contact); + App::postCoreAsync([this, presence, note]() { setPresence(presence, note); }); mPictureUri = Utils::coreStringToAppString(contact->getPhoto()); mFullName = mFriendModel->getFullName(); auto defaultAddress = contact->getAddress(); @@ -65,7 +66,7 @@ FriendCore::FriendCore(const std::shared_ptr &contact, bool is auto addresses = contact->getAddresses(); for (auto &address : addresses) { mAddressList.append(Utils::createFriendAddressVariant( - tr("sip_address"), Utils::coreStringToAppString(address->asStringUriOnly()))); + tr("sip_address"), Utils::coreStringToAppString(address->asStringUriOnly()))); } mDefaultAddress = defaultAddress ? Utils::coreStringToAppString(defaultAddress->asStringUriOnly()) : QString(); mDefaultFullAddress = defaultAddress ? Utils::coreStringToAppString(defaultAddress->asString()) : QString(); @@ -142,8 +143,7 @@ void FriendCore::setSelf(QSharedPointer me) { mFriendModelConnection->makeConnectToModel( &FriendModel::removed, [this]() { mFriendModelConnection->invokeToCore([this]() { removed(this); }); }); mFriendModelConnection->makeConnectToModel( - &FriendModel::presenceReceived, - [this](LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp) { + &FriendModel::presenceReceived, [this](LinphoneEnums::Presence presence, QString presenceNote) { auto devices = mFriendModel->getDevices(); QVariantList devicesList; for (auto &device : devices) { @@ -153,14 +153,11 @@ void FriendCore::setSelf(QSharedPointer me) { Utils::coreStringToAppString(device->getAddress()->asString()), LinphoneEnums::fromLinphone(device->getSecurityLevel()))); } - mFriendModelConnection->invokeToCore( - [this, consolidatedPresence, presenceTimestamp, devicesList]() { - setConsolidatedPresence(consolidatedPresence); - setPresenceTimestamp(presenceTimestamp); - - setDevices(devicesList); - updateVerifiedDevicesCount(); - }); + mFriendModelConnection->invokeToCore([this, presence, devicesList, presenceNote]() { + setPresence(presence, presenceNote); + setDevices(devicesList); + updateVerifiedDevicesCount(); + }); }); mFriendModelConnection->makeConnectToModel(&FriendModel::pictureUriChanged, [this](const QString &uri) { mFriendModelConnection->invokeToCore([this, uri]() { this->onPictureUriChanged(uri); }); @@ -188,7 +185,7 @@ void FriendCore::setSelf(QSharedPointer me) { QList addr; for (auto &num : numbers) { addr.append(Utils::createFriendAddressVariant( - tr("sip_address"), Utils::coreStringToAppString(num->asStringUriOnly()))); + tr("sip_address"), Utils::coreStringToAppString(num->asStringUriOnly()))); } mFriendModelConnection->invokeToCore([this, addr]() { resetPhoneNumbers(addr); }); }); @@ -419,9 +416,10 @@ void FriendCore::appendAddress(const QString &addr) { QString interpretedFullAddress = linphoneAddr ? Utils::coreStringToAppString(linphoneAddr->asString()) : ""; QString interpretedAddress = linphoneAddr ? Utils::coreStringToAppString(linphoneAddr->asStringUriOnly()) : ""; mCoreModelConnection->invokeToCore([this, interpretedAddress, interpretedFullAddress]() { - if (interpretedAddress.isEmpty()) Utils::showInformationPopup(tr("information_popup_error_title"), - //: "Adresse invalide" - tr("information_popup_invalid_address_message"), false); + if (interpretedAddress.isEmpty()) + Utils::showInformationPopup(tr("information_popup_error_title"), + //: "Adresse invalide" + tr("information_popup_invalid_address_message"), false); else { mAddressList.append(Utils::createFriendAddressVariant(tr("sip_address"), interpretedAddress)); if (mDefaultFullAddress.isEmpty()) mDefaultFullAddress = interpretedFullAddress; @@ -507,30 +505,6 @@ void FriendCore::setDefaultAddress(const QString &address) { } } -LinphoneEnums::ConsolidatedPresence FriendCore::getConsolidatedPresence() const { - return mConsolidatedPresence; -} - -void FriendCore::setConsolidatedPresence(LinphoneEnums::ConsolidatedPresence presence) { - mustBeInMainThread(log().arg(Q_FUNC_INFO)); - if (mConsolidatedPresence != presence) { - mConsolidatedPresence = presence; - emit consolidatedPresenceChanged(mConsolidatedPresence); - } -} - -QDateTime FriendCore::getPresenceTimestamp() const { - return mPresenceTimestamp; -} - -void FriendCore::setPresenceTimestamp(QDateTime presenceTimestamp) { - mustBeInMainThread(log().arg(Q_FUNC_INFO)); - if (mPresenceTimestamp != presenceTimestamp) { - mPresenceTimestamp = presenceTimestamp; - emit presenceTimestampChanged(mPresenceTimestamp); - } -} - QString FriendCore::getPictureUri() const { return mPictureUri; } @@ -612,8 +586,8 @@ void FriendCore::writeFromModel(const std::shared_ptr &model) { QList addresses; for (auto &addr : model->getAddresses()) { - addresses.append( - Utils::createFriendAddressVariant(tr("sip_address"), Utils::coreStringToAppString(addr->asStringUriOnly()))); + addresses.append(Utils::createFriendAddressVariant(tr("sip_address"), + Utils::coreStringToAppString(addr->asStringUriOnly()))); } mAddressList = addresses; mDefaultAddress = model->getDefaultAddress(); @@ -759,3 +733,29 @@ bool FriendCore::getReadOnly() const { std::shared_ptr FriendCore::getFriendModel() { return mFriendModel; } + +void FriendCore::setPresence(LinphoneEnums::Presence presence, QString presenceNote) { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + bool notify = false; + if (presence != mPresence) { + mPresence = presence; + notify = true; + } + if (presenceNote != mPresenceNote) { + mPresenceNote = presenceNote; + notify = true; + } + if (notify) emit presenceChanged(); +} + +QUrl FriendCore::getPresenceIcon() { + return Utils::getPresenceIcon(mPresence); +} + +QColor FriendCore::getPresenceColor() { + return Utils::getPresenceColor(mPresence); +} + +QString FriendCore::getPresenceStatus() { + return Utils::getPresenceStatus(mPresence); +} diff --git a/Linphone/core/friend/FriendCore.hpp b/Linphone/core/friend/FriendCore.hpp index 9d2edc631..1d03dbbbb 100644 --- a/Linphone/core/friend/FriendCore.hpp +++ b/Linphone/core/friend/FriendCore.hpp @@ -29,6 +29,7 @@ #include "tool/thread/SafeSharedPointer.hpp" #include +#include #include #include #include @@ -63,9 +64,11 @@ class FriendCore : public QObject, public AbstractObject { Q_PROPERTY(QString defaultAddress READ getDefaultAddress WRITE setDefaultAddress NOTIFY defaultAddressChanged) Q_PROPERTY(QString defaultFullAddress READ getDefaultFullAddress WRITE setDefaultFullAddress NOTIFY defaultFullAddressChanged) - Q_PROPERTY(QDateTime presenceTimestamp READ getPresenceTimestamp NOTIFY presenceTimestampChanged) - Q_PROPERTY(LinphoneEnums::ConsolidatedPresence consolidatedPresence READ getConsolidatedPresence NOTIFY - consolidatedPresenceChanged) + Q_PROPERTY(LinphoneEnums::Presence presence MEMBER mPresence NOTIFY presenceChanged) + Q_PROPERTY(QUrl presenceIcon READ getPresenceIcon NOTIFY presenceChanged) + Q_PROPERTY(QColor presenceColor READ getPresenceColor NOTIFY presenceChanged) + Q_PROPERTY(QString presenceStatus READ getPresenceStatus NOTIFY presenceChanged) + Q_PROPERTY(QString presenceNote MEMBER mPresenceNote NOTIFY presenceChanged) Q_PROPERTY(bool isSaved READ getIsSaved NOTIFY isSavedChanged) Q_PROPERTY(bool isStored READ getIsStored NOTIFY isStoredChanged) Q_PROPERTY(QString pictureUri READ getPictureUri WRITE setPictureUri NOTIFY pictureUriChanged) @@ -130,12 +133,6 @@ public: void setDevices(QVariantList devices); Q_INVOKABLE LinphoneEnums::SecurityLevel getSecurityLevelForAddress(const QString &address) const; - LinphoneEnums::ConsolidatedPresence getConsolidatedPresence() const; - void setConsolidatedPresence(LinphoneEnums::ConsolidatedPresence presence); - - QDateTime getPresenceTimestamp() const; - void setPresenceTimestamp(QDateTime presenceTimestamp); - bool getIsSaved() const; void setIsSaved(bool isSaved); @@ -146,8 +143,6 @@ public: void setPictureUri(const QString &uri); void onPictureUriChanged(QString uri); - void onPresenceReceived(LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp); - bool isLdap() const; bool isAppFriend() const; bool isCardDAV() const; @@ -159,6 +154,10 @@ public: Q_INVOKABLE void save(); Q_INVOKABLE void undo(); + QColor getPresenceColor(); + QString getPresenceStatus(); + QUrl getPresenceIcon(); + protected: void resetPhoneNumbers(QList newList); void resetAddresses(QList newList); @@ -173,8 +172,6 @@ signals: void addressChanged(); void organizationChanged(); void jobChanged(); - void consolidatedPresenceChanged(LinphoneEnums::ConsolidatedPresence level); - void presenceTimestampChanged(QDateTime presenceTimestamp); void pictureUriChanged(); void saved(); void isSavedChanged(bool isSaved); @@ -186,13 +183,16 @@ signals: void devicesChanged(); void verifiedDevicesChanged(); void lSetStarred(bool starred); + void presenceChanged(); protected: void writeIntoModel(std::shared_ptr model) const; void writeFromModel(const std::shared_ptr &model); - LinphoneEnums::ConsolidatedPresence mConsolidatedPresence = LinphoneEnums::ConsolidatedPresence::Offline; - QDateTime mPresenceTimestamp; + LinphoneEnums::Presence mPresence = LinphoneEnums::Presence::Undefined; + QColor mPresenceColor; + QString mPresenceStatus; + QString mPresenceNote = ""; QString mGivenName; QString mFamilyName; QString mFullName; @@ -214,6 +214,9 @@ protected: QSharedPointer> mFriendModelConnection; QSharedPointer> mCoreModelConnection; +private: + void setPresence(LinphoneEnums::Presence presence, QString presenceNote); + DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/core/notifier/Notifier.cpp b/Linphone/core/notifier/Notifier.cpp index 54a04029f..28eec67bb 100644 --- a/Linphone/core/notifier/Notifier.cpp +++ b/Linphone/core/notifier/Notifier.cpp @@ -290,6 +290,15 @@ void Notifier::notifyReceivedCall(const shared_ptr &call) { if (!accountModel->getNotificationsAllowed()) { qInfo() << "Notifications have been disabled for this account - not creating a notification for incoming call"; + if (accountModel->forwardToVoiceMailInDndPresence()) { + lInfo() << log().arg("Transferring call to voicemail"); + auto voicemailAddress = linphone::Factory::get()->createAddress( + Utils::appStringToCoreString(accountModel->getVoicemailAddress())); + if (voicemailAddress) call->transferTo(voicemailAddress); + } else { + lInfo() << log().arg("Declining call."); + call->decline(linphone::Reason::Busy); + } return; } } @@ -337,7 +346,7 @@ void Notifier::notifyReceivedMessages(const std::shared_ptr remoteAddress = Utils::coreStringToAppString(remoteAddr->asStringUriOnly()); auto fileContent = message->getFileTransferInformation(); if (!fileContent) { - foreach (auto content, message->getContents()) { + for (auto content : message->getContents()) { if (content->isText()) txt += content->getUtf8Text().c_str(); } } else if (fileContent->isVoiceRecording()) diff --git a/Linphone/data/image/presence-note.svg b/Linphone/data/image/presence-note.svg new file mode 100644 index 000000000..a68031473 --- /dev/null +++ b/Linphone/data/image/presence-note.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Linphone/data/image/presence_away.svg b/Linphone/data/image/presence_away.svg new file mode 100644 index 000000000..60badad86 --- /dev/null +++ b/Linphone/data/image/presence_away.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/data/image/presence_busy.svg b/Linphone/data/image/presence_busy.svg new file mode 100644 index 000000000..253401328 --- /dev/null +++ b/Linphone/data/image/presence_busy.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/data/image/presence_do_not_disturb.svg b/Linphone/data/image/presence_do_not_disturb.svg new file mode 100644 index 000000000..be6d89b8f --- /dev/null +++ b/Linphone/data/image/presence_do_not_disturb.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Linphone/data/image/presence_offline.svg b/Linphone/data/image/presence_offline.svg new file mode 100644 index 000000000..5d5961a43 --- /dev/null +++ b/Linphone/data/image/presence_offline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Linphone/data/image/presence_online.svg b/Linphone/data/image/presence_online.svg new file mode 100644 index 000000000..178809cee --- /dev/null +++ b/Linphone/data/image/presence_online.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/data/image/regitration_deactivated.svg b/Linphone/data/image/regitration_deactivated.svg new file mode 100644 index 000000000..da60bfa55 --- /dev/null +++ b/Linphone/data/image/regitration_deactivated.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/data/image/regitration_error.svg b/Linphone/data/image/regitration_error.svg new file mode 100644 index 000000000..18273a00f --- /dev/null +++ b/Linphone/data/image/regitration_error.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/data/image/regitration_progress.svg b/Linphone/data/image/regitration_progress.svg new file mode 100644 index 000000000..48c4dc8e3 --- /dev/null +++ b/Linphone/data/image/regitration_progress.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/data/languages/en.ts b/Linphone/data/languages/en.ts index d31233c0b..ef9bf247a 100644 --- a/Linphone/data/languages/en.ts +++ b/Linphone/data/languages/en.ts @@ -704,30 +704,6 @@ CallHistoryLayout - - - contact_presence_status_online - "En ligne" - Online - - - - contact_presence_status_busy - "Occupé" - Busy - - - - contact_presence_status_do_not_disturb - "Ne pas déranger" - Do not disturb - - - - contact_presence_status_offline - "Hors ligne" - Offline - meeting_info_join_title @@ -4049,6 +4025,36 @@ To enable them in a commercial project, please contact us. Utils + + + contact_presence_status_available + "En ligne" + Available + + + + contact_presence_status_busy + "Occupé" + Busy + + + + contact_presence_status_do_not_disturb + "Ne pas déranger" + Do not disturb + + + + contact_presence_status_offline + "Hors ligne" + Offline + + + + contact_presence_status_away + "Absent" + Idle/Away + information_popup_call_not_created_message @@ -5605,4 +5611,131 @@ To enable them in a commercial project, please contact us. Call forward deactivated + + Presence + + + contact_presence_status_available + "En ligne" + Available + + + + contact_presence_status_busy + "Occupé" + Busy + + + + contact_presence_status_do_not_disturb + "Ne pas déranger" + Do not disturb + + + + contact_presence_status_offline + "Hors ligne" + Offline + + + + contact_presence_status_away + "Absent" + Idle/Away + + + + contact_presence_reset_status + "Reset status" + Reset status + + + + contact_presence_save_status + "Save" + Save + + + + contact_presence_edit_status + "Edit" + Edit + + + + contact_presence_delete_status + "Delete" + Delete + + + + contact_presence_delete_status + "Delete" + Delete + + + + contact_presence_button_set_custom_status + "Set" + Set + + + + contact_presence_button_edit_custom_status + "Edit" + Edit + + + + contact_presence_button_delete_custom_status + "Delete" + Delete + + + + contact_presence_button_set_custom_status_title + "Set a custom status message" + Set a custom status message + + + + contact_presence_custom_status + "Custom status" + Custom status + + + + contact_presence_custom_status + "Custom status" + Custom status + + + + PresenceSetCustomStatus + + + contact_presence_button_set_custom_status_title + "Set a custom status message" + Set a custom status message + + + + contact_presence_button_save_custom_status + "Save" + Save + + + + PresenceNoteLayout + + + contact_presence_note_title + "Message personnalisé" + Custom message + + + + + + diff --git a/Linphone/data/languages/fr_FR.ts b/Linphone/data/languages/fr_FR.ts index ab8a3aae7..e7eff3770 100644 --- a/Linphone/data/languages/fr_FR.ts +++ b/Linphone/data/languages/fr_FR.ts @@ -704,30 +704,6 @@ CallHistoryLayout - - - contact_presence_status_online - "En ligne" - En ligne - - - - contact_presence_status_busy - "Occupé" - Occupé - - - - contact_presence_status_do_not_disturb - "Ne pas déranger" - Ne pas déranger - - - - contact_presence_status_offline - "Hors ligne" - Hors ligne - meeting_info_join_title @@ -4041,6 +4017,36 @@ Pour les activer dans un projet commercial, merci de nous contacter. Utils + + + contact_presence_status_available + "En ligne" + Disponible + + + + contact_presence_status_busy + "Occupé" + Occupé + + + + contact_presence_status_do_not_disturb + "Ne pas déranger" + Ne pas déranger + + + + contact_presence_status_offline + "Hors ligne" + Hors ligne + + + + contact_presence_status_away + "Absent" + Inactif/Absent + information_popup_call_not_created_message @@ -5520,4 +5526,46 @@ Pour les activer dans un projet commercial, merci de nous contacter.Ok + + Presence + + + contact_presence_status_available + "En ligne" + Disponible + + + + contact_presence_status_busy + "Occupé" + Occupé + + + + contact_presence_status_do_not_disturb + "Ne pas déranger" + Ne pas déranger + + + + contact_presence_status_offline + "Hors ligne" + Hors ligne + + + + contact_presence_status_away + "Absent" + Inactif/Absent + + + + PresenceNoteLayout + + + contact_presence_note_title + "Message personnalisé" + Message personnalisé + + diff --git a/Linphone/model/account/AccountModel.cpp b/Linphone/model/account/AccountModel.cpp index e11e7b63e..abb8b1679 100644 --- a/Linphone/model/account/AccountModel.cpp +++ b/Linphone/model/account/AccountModel.cpp @@ -22,6 +22,8 @@ #include "core/path/Paths.hpp" #include "model/core/CoreModel.hpp" +#include "model/setting/SettingsModel.hpp" +#include "model/tool/ToolModel.hpp" #include "tool/Utils.hpp" #include "tool/providers/AvatarProvider.hpp" #include @@ -467,3 +469,94 @@ void AccountModel::removeUserData(const std::shared_ptr &acco mustBeInLinphoneThread(sLog().arg(Q_FUNC_INFO)); userDataMap.remove(account); } + +// Up to date there are api to get/set presence from/to a linphone account object +// therefore retrieved/set from core + +LinphoneEnums::Presence AccountModel::getPresence() { + auto presenceModel = CoreModel::getInstance()->getCore()->getPresenceModel(); // No api (yet) at the account level + return ToolModel::corePresenceModelToAppPresence(presenceModel); +} + +void AccountModel::setPresence(LinphoneEnums::Presence presence, + bool userInitiated, + bool resetToAuto, + QString presenceNote) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + + lDebug() << log().arg("presence set request to: " + LinphoneEnums::toString(presence) + " user initiated? " + + (userInitiated ? "true" : "false") + " reset to auto? " + (resetToAuto ? "true" : "false")); + + auto core = CoreModel::getInstance()->getCore(); + + if (core->getGlobalState() != linphone::GlobalState::On) { + lWarning() << log().arg("Unable to set presence this time as core state is not ON"); + return; + } + + if (presence == LinphoneEnums::Presence::Undefined) { + lWarning() << log().arg("Unable to set Undefined presence"); + return; + } + + auto accountSection = ToolModel::configAccountSection(mMonitor); + + if (!resetToAuto && !userInitiated && + !core->getConfig()->getString(accountSection, "explicit_presence", "").empty()) { + lDebug() << log().arg("Ignoring automatic presence update, as user already set a presence explicitely : ") + << Utils::coreStringToAppString(core->getConfig()->getString(accountSection, "explicit_presence", "")); + return; + } + + if (userInitiated) { + core->getConfig()->setString(accountSection, "explicit_presence", + Utils::appStringToCoreString(LinphoneEnums::toString(presence))); + core->getConfig()->sync(); + } + + if (resetToAuto) { + core->getConfig()->cleanEntry(accountSection, "explicit_presence"); + core->getConfig()->sync(); + } + + if (!presenceNote.isEmpty()) { + core->getConfig()->setString(accountSection, "presence_note", Utils::appStringToCoreString(presenceNote)); + core->getConfig()->sync(); + } + + if (!mMonitor->getParams()->publishEnabled()) { + auto params = mMonitor->getParams()->clone(); + params->enablePublish(true); + mMonitor->setParams(params); + } + + auto presenceModel = ToolModel::appPresenceToCorePresenceModel(presence, presenceNote); + core->setPresenceModel(presenceModel); // No api (yet) at the account level + + if (presence == LinphoneEnums::Presence::Offline) { + for (auto friendList : core->getFriendsLists()) + friendList->enableSubscriptions(false); + } else { + for (auto friendList : core->getFriendsLists()) + friendList->enableSubscriptions(true); + } + + setNotificationsAllowed( + presence != LinphoneEnums::Presence::DoNotDisturb && + (presence != LinphoneEnums::Presence::Away || + core->getConfig()->getBool(accountSection, "allow_notifications_in_presence_away", true)) && + (presence != LinphoneEnums::Presence::Busy || + core->getConfig()->getBool(accountSection, "allow_notifications_in_presence_busy", true))); + + if (!SettingsModel::dndEnabled(core->getConfig())) { + SettingsModel::getInstance()->enableRinging(presence != LinphoneEnums::Presence::DoNotDisturb); + } + + emit presenceChanged(presence, userInitiated); +} + +bool AccountModel::forwardToVoiceMailInDndPresence() { + auto accountSection = ToolModel::configAccountSection(mMonitor); + auto core = CoreModel::getInstance()->getCore(); + return core->getConfig()->getBool(accountSection, "forward_to_voicemail_in_dnd_presence", false); +} diff --git a/Linphone/model/account/AccountModel.hpp b/Linphone/model/account/AccountModel.hpp index 50a739fcc..f21d8602b 100644 --- a/Linphone/model/account/AccountModel.hpp +++ b/Linphone/model/account/AccountModel.hpp @@ -24,6 +24,7 @@ #include "model/listener/Listener.hpp" #include "tool/AbstractObject.hpp" +#include "tool/LinphoneEnums.hpp" #include #include @@ -83,6 +84,10 @@ public: bool getShowMwi(); void setVoicemailAddress(QString value); QString getVoicemailAddress() const; + LinphoneEnums::Presence getPresence(); + void setPresence(LinphoneEnums::Presence presence, bool userInitiated, bool resetToAuto, QString presenceNote); + std::string configAccountSection(); + bool forwardToVoiceMailInDndPresence(); signals: void registrationStateChanged(const std::shared_ptr &account, @@ -112,6 +117,7 @@ signals: void voicemailCountChanged(int count); void showMwiChanged(bool show); void voicemailAddressChanged(QString value); + void presenceChanged(LinphoneEnums::Presence presence, bool userInitiated); private: /**Linphone **/ diff --git a/Linphone/model/auth/OIDCModel.cpp b/Linphone/model/auth/OIDCModel.cpp index de7a18a09..e641f64b4 100644 --- a/Linphone/model/auth/OIDCModel.cpp +++ b/Linphone/model/auth/OIDCModel.cpp @@ -310,7 +310,7 @@ void OIDCModel::setBearers() { qWarning() << "No refresh token found"; } CoreModel::getInstance()->getCore()->addAuthInfo(mAuthInfo); - emit CoreModel::getInstance() -> bearerAccountAdded(); + emit CoreModel::getInstance()->bearerAccountAdded(); emit finished(); } QString OIDCModel::idToken() const { @@ -319,4 +319,4 @@ QString OIDCModel::idToken() const { #else return mIdToken; #endif -} \ No newline at end of file +} diff --git a/Linphone/model/core/CoreModel.cpp b/Linphone/model/core/CoreModel.cpp index d9c73348a..90db5e672 100644 --- a/Linphone/model/core/CoreModel.cpp +++ b/Linphone/model/core/CoreModel.cpp @@ -425,6 +425,14 @@ void CoreModel::onCallStateChanged(const std::shared_ptr &core, core->getCallsNb() == 0) { // Disable tones in DND mode if no more calls are running. SettingsModel::getInstance()->setCallToneIndicationsEnabled(false); } + App::postModelAsync([core]() { + for (int i = 0; i < App::getInstance()->getAccountList()->rowCount(); ++i) { + auto accountCore = App::getInstance()->getAccountList()->getAt(i); + emit accountCore->lSetPresence(core->getCallsNb() == 0 ? LinphoneEnums::Presence::Online + : LinphoneEnums::Presence::Busy, + false, false); + } + }); emit callStateChanged(core, call, state, message); } void CoreModel::onCallStatsUpdated(const std::shared_ptr &core, diff --git a/Linphone/model/friend/FriendModel.cpp b/Linphone/model/friend/FriendModel.cpp index 813939a8d..9f5091835 100644 --- a/Linphone/model/friend/FriendModel.cpp +++ b/Linphone/model/friend/FriendModel.cpp @@ -71,14 +71,6 @@ std::shared_ptr FriendModel::getFriend() const { return mMonitor; } -QDateTime FriendModel::getPresenceTimestamp() const { - if (mMonitor && mMonitor->getPresenceModel()) { - time_t timestamp = mMonitor->getPresenceModel()->getLatestActivityTimestamp(); - if (timestamp == -1) return QDateTime(); - else return QDateTime::fromMSecsSinceEpoch(timestamp * 1000); - } else return QDateTime(); -} - void FriendModel::setAddress(const std::shared_ptr &address) { if (!mMonitor) return; if (address) { @@ -316,8 +308,22 @@ void FriendModel::setStarred(bool starred) { mMonitor->setStarred(starred); emit starredChanged(starred); } + void FriendModel::onPresenceReceived(const std::shared_ptr &contact) { - emit presenceReceived(LinphoneEnums::fromLinphone(contact->getConsolidatedPresence()), getPresenceTimestamp()); + emit presenceReceived(getPresence(contact), getPresenceNote(contact)); +} + +LinphoneEnums::Presence FriendModel::getPresence(const std::shared_ptr &contact) { + auto presenceModel = contact->getPresenceModel(); + return ToolModel::corePresenceModelToAppPresence(presenceModel); +} + +QString FriendModel::getPresenceNote(const std::shared_ptr &contact) { + auto presenceModel = contact->getPresenceModel(); + auto note = presenceModel && presenceModel->getNote(Utils::appStringToCoreString(QLocale().name().left(2))) + ? presenceModel->getNote(Utils::appStringToCoreString(QLocale().name().left(2)))->getContent() + : ""; + return Utils::coreStringToAppString(note); } QString FriendModel::getPictureUri() const { diff --git a/Linphone/model/friend/FriendModel.hpp b/Linphone/model/friend/FriendModel.hpp index 8e1410900..124456d28 100644 --- a/Linphone/model/friend/FriendModel.hpp +++ b/Linphone/model/friend/FriendModel.hpp @@ -91,6 +91,8 @@ public: void onUpdated(const std::shared_ptr &data); void onRemoved(const std::shared_ptr &data); + LinphoneEnums::Presence getPresence(const std::shared_ptr &contact); + QString mFullName; signals: @@ -105,12 +107,13 @@ signals: void familyNameChanged(const QString &name); void organizationChanged(const QString &orga); void jobChanged(const QString &job); - void presenceReceived(LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp); + void presenceReceived(LinphoneEnums::Presence presence, QString presenceNote); void updated(); void removed(); private: DECLARE_ABSTRACT_OBJECT + QString getPresenceNote(const std::shared_ptr &contact); //-------------------------------------------------------------------------------- // LINPHONE diff --git a/Linphone/model/tool/ToolModel.cpp b/Linphone/model/tool/ToolModel.cpp index d5c063d54..4db65fb28 100644 --- a/Linphone/model/tool/ToolModel.cpp +++ b/Linphone/model/tool/ToolModel.cpp @@ -579,4 +579,85 @@ std::shared_ptr ToolModel::createChatForAddress(std::shared_ } auto chatRoom = core->createChatRoom(params, participants); return chatRoom; -} \ No newline at end of file +} +// Presence mapping from SDK PresenceModel/RFC 3863 <-> Linphone UI (5 statuses Online, Offline, Away, Busy, DND). +// Online = Basic Status open with no activity +// Busy = Basic Status open with activity Busy and description busy +// Away = Basic Status open with activity Away and description away +// Offline = Basic Status open with activity PermanentAbsence and description offline +// DND = Basic Status open with activity Other and description dnd +// Note : close status on the last 2 items would be preferrable, but they currently trigger multiple tuple NOTIFY from +// flexisip presence server Note 2 : close status with no activity triggers an unsubscribe. + +LinphoneEnums::Presence +ToolModel::corePresenceModelToAppPresence(std::shared_ptr presenceModel) { + if (!presenceModel) { + lWarning() << sLog().arg("presence model is null."); + return LinphoneEnums::Presence::Undefined; + } + + auto presenceActivity = presenceModel->getActivity(); + if (presenceModel->getBasicStatus() == linphone::PresenceBasicStatus::Open) { + if (!presenceActivity) return LinphoneEnums::Presence::Online; + else if (presenceActivity->getType() == linphone::PresenceActivity::Type::Busy) + return LinphoneEnums::Presence::Busy; + else if (presenceActivity->getType() == linphone::PresenceActivity::Type::Away) + return LinphoneEnums::Presence::Away; + else if (presenceActivity->getType() == linphone::PresenceActivity::Type::PermanentAbsence) + return LinphoneEnums::Presence::Offline; + else if (presenceActivity->getType() == linphone::PresenceActivity::Type::Other) + return LinphoneEnums::Presence::DoNotDisturb; + else { + lWarning() << sLog().arg("unhandled core activity type : ") << (int)presenceActivity->getType(); + return LinphoneEnums::Presence::Undefined; + } + } + return LinphoneEnums::Presence::Undefined; +} + +std::shared_ptr ToolModel::appPresenceToCorePresenceModel(LinphoneEnums::Presence presence, + QString presenceNote) { + auto presenceModel = CoreModel::getInstance()->getCore()->createPresenceModel(); + + switch (presence) { + case LinphoneEnums::Presence::Online: + presenceModel->setBasicStatus(linphone::PresenceBasicStatus::Open); + break; + case LinphoneEnums::Presence::Busy: + presenceModel->setBasicStatus(linphone::PresenceBasicStatus::Open); + presenceModel->setActivity(linphone::PresenceActivity::Type::Busy, "busy"); + break; + case LinphoneEnums::Presence::Away: + presenceModel->setBasicStatus(linphone::PresenceBasicStatus::Open); + presenceModel->setActivity(linphone::PresenceActivity::Type::Away, "away"); + break; + case LinphoneEnums::Presence::Offline: + presenceModel->setBasicStatus(linphone::PresenceBasicStatus::Open); + presenceModel->setActivity(linphone::PresenceActivity::Type::PermanentAbsence, "offline"); + break; + case LinphoneEnums::Presence::DoNotDisturb: + presenceModel->setBasicStatus(linphone::PresenceBasicStatus::Open); + presenceModel->setActivity(linphone::PresenceActivity::Type::Other, "dnd"); + break; + case LinphoneEnums::Presence::Undefined: + lWarning() << sLog().arg("Trying to build PresenceModel from Undefined presence "); + return nullptr; + } + + if (!presenceNote.isEmpty()) { + auto note = CoreModel::getInstance()->getCore()->createPresenceNote( + Utils::appStringToCoreString(presenceNote), Utils::appStringToCoreString(QLocale().name().left(2))); + auto service = presenceModel->getNthService(0); + service->addNote(note); + } + return presenceModel; +} + +std::string ToolModel::configAccountSection(const std::shared_ptr &account) { + int count = 0; + for (auto item : CoreModel::getInstance()->getCore()->getAccountList()) { + if (account == item) return "proxy_" + std::to_string(count); + count++; + } + return "account"; +} diff --git a/Linphone/model/tool/ToolModel.hpp b/Linphone/model/tool/ToolModel.hpp index d98aad65f..02ddc64c2 100644 --- a/Linphone/model/tool/ToolModel.hpp +++ b/Linphone/model/tool/ToolModel.hpp @@ -88,6 +88,12 @@ public: static std::shared_ptr lookupChatForAddress(std::shared_ptr remoteAddress); static std::shared_ptr createChatForAddress(std::shared_ptr remoteAddress); + static LinphoneEnums::Presence + corePresenceModelToAppPresence(std::shared_ptr presenceModel); + static std::shared_ptr appPresenceToCorePresenceModel(LinphoneEnums::Presence presence, + QString presenceNote); + static std::string configAccountSection(const std::shared_ptr &account); + private: DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/tool/LinphoneEnums.cpp b/Linphone/tool/LinphoneEnums.cpp index f40030bc7..1fc17e454 100644 --- a/Linphone/tool/LinphoneEnums.cpp +++ b/Linphone/tool/LinphoneEnums.cpp @@ -44,6 +44,7 @@ void LinphoneEnums::registerMetaTypes() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); qmlRegisterUncreatableMetaObject(LinphoneEnums::staticMetaObject, Constants::MainQmlUri, 1, 0, "LinphoneEnums", "Only enums"); } @@ -207,7 +208,8 @@ LinphoneEnums::ConferenceLayout LinphoneEnums::fromLinphone(const linphone::Conf QString LinphoneEnums::toString(LinphoneEnums::ConferenceLayout layout) { //: "Participant actif" - if (layout == LinphoneEnums::ConferenceLayout::ActiveSpeaker) return QObject::tr("conference_layout_active_speaker"); + if (layout == LinphoneEnums::ConferenceLayout::ActiveSpeaker) + return QObject::tr("conference_layout_active_speaker"); //: "Mosaïque" else if (layout == LinphoneEnums::ConferenceLayout::Grid) return QObject::tr("conference_layout_grid"); //: "Audio uniquement" @@ -249,11 +251,22 @@ LinphoneEnums::ConferenceSchedulerState LinphoneEnums::fromLinphone(const linpho return static_cast(state); } -linphone::ConsolidatedPresence LinphoneEnums::toLinphone(const LinphoneEnums::ConsolidatedPresence &data) { - return static_cast(data); +QString LinphoneEnums::toString(Presence presence) { + const QMetaObject &metaObj = LinphoneEnums::staticMetaObject; + int index = metaObj.indexOfEnumerator("Presence"); + QMetaEnum metaEnum = metaObj.enumerator(index); + return metaEnum.valueToKey(static_cast(presence)); } -LinphoneEnums::ConsolidatedPresence LinphoneEnums::fromLinphone(const linphone::ConsolidatedPresence &data) { - return static_cast(data); + +LinphoneEnums::Presence LinphoneEnums::fromString(const QString &key) { + const QMetaObject &metaObj = LinphoneEnums::staticMetaObject; + int index = metaObj.indexOfEnumerator("Presence"); + QMetaEnum metaEnum = metaObj.enumerator(index); + int value = metaEnum.keyToValue(key.toUtf8().constData()); + if (value == -1) { + return LinphoneEnums::Presence::Undefined; + } + return static_cast(value); } linphone::MagicSearch::Aggregation LinphoneEnums::toLinphone(const LinphoneEnums::MagicSearchAggregation &data) { diff --git a/Linphone/tool/LinphoneEnums.hpp b/Linphone/tool/LinphoneEnums.hpp index fa6b7be31..b192ce998 100644 --- a/Linphone/tool/LinphoneEnums.hpp +++ b/Linphone/tool/LinphoneEnums.hpp @@ -250,16 +250,12 @@ Q_ENUM_NS(ConferenceSchedulerState) linphone::ConferenceScheduler::State toLinphone(const LinphoneEnums::ConferenceSchedulerState &state); LinphoneEnums::ConferenceSchedulerState fromLinphone(const linphone::ConferenceScheduler::State &state); -enum class ConsolidatedPresence { - Online = int(linphone::ConsolidatedPresence::Online), - Busy = int(linphone::ConsolidatedPresence::Busy), - DoNotDisturb = int(linphone::ConsolidatedPresence::DoNotDisturb), - Offline = int(linphone::ConsolidatedPresence::Offline) -}; -Q_ENUM_NS(ConsolidatedPresence); +// App Presence +enum class Presence { Undefined, Online, Busy, DoNotDisturb, Offline, Away }; +Q_ENUM_NS(Presence); -linphone::ConsolidatedPresence toLinphone(const LinphoneEnums::ConsolidatedPresence &state); -LinphoneEnums::ConsolidatedPresence fromLinphone(const linphone::ConsolidatedPresence &state); +QString toString(Presence presence); +Presence fromString(const QString &key); enum class MagicSearchAggregation { Friend = int(linphone::MagicSearch::Aggregation::Friend), diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index d5bafb700..ea797bdc8 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -43,6 +43,8 @@ #include #include #include +#include +#include #include #include #include @@ -1620,3 +1622,126 @@ void Utils::runCommandLine(const QString command) { lWarning() << "Unsupported OS!"; #endif } + +// Presence + +QColor Utils::getDefaultStyleColor(const QString &colorName) { + static QObject *defaultStyleSingleton = nullptr; + if (!defaultStyleSingleton) { + QQmlComponent component(App::getInstance()->mEngine, QUrl("qrc:/qt/qml/Linphone/view/Style/DefaultStyle.qml")); + defaultStyleSingleton = component.create(); + } + return QQmlProperty::read(defaultStyleSingleton, colorName).value(); +} + +QUrl Utils::getAppIcon(const QString &iconName) { + static QObject *appIconsSingleton = nullptr; + if (!appIconsSingleton) { + QQmlComponent component(App::getInstance()->mEngine, QUrl("qrc:/qt/qml/Linphone/view/Style/AppIcons.qml")); + appIconsSingleton = component.create(); + } + return QQmlProperty::read(appIconsSingleton, iconName).value(); +} + +QColor Utils::getPresenceColor(LinphoneEnums::Presence presence) { + mustBeInMainThread(sLog().arg(Q_FUNC_INFO)); + QColor presenceColor = QColorConstants::Transparent; + switch (presence) { + case LinphoneEnums::Presence::Online: + presenceColor = Utils::getDefaultStyleColor("success_500main"); + break; + case LinphoneEnums::Presence::Away: + presenceColor = Utils::getDefaultStyleColor("warning_500_main"); + break; + case LinphoneEnums::Presence::Busy: + presenceColor = Utils::getDefaultStyleColor("danger_500main"); + break; + case LinphoneEnums::Presence::DoNotDisturb: + presenceColor = Utils::getDefaultStyleColor("danger_500main"); + break; + case LinphoneEnums::Presence::Offline: + presenceColor = Utils::getDefaultStyleColor("main2_600"); + break; + case LinphoneEnums::Presence::Undefined: + presenceColor = Utils::getDefaultStyleColor("transparent"); + break; + } + return presenceColor; +} + +QUrl Utils::getPresenceIcon(LinphoneEnums::Presence presence) { + mustBeInMainThread(sLog().arg(Q_FUNC_INFO)); + QUrl presenceIcon; + switch (presence) { + case LinphoneEnums::Presence::Online: + presenceIcon = Utils::getAppIcon("presenceOnline"); + break; + case LinphoneEnums::Presence::Away: + presenceIcon = Utils::getAppIcon("presenceAway"); + break; + case LinphoneEnums::Presence::Busy: + presenceIcon = Utils::getAppIcon("presenceBusy"); + break; + case LinphoneEnums::Presence::DoNotDisturb: + presenceIcon = Utils::getAppIcon("presenceDoNotDisturb"); + break; + case LinphoneEnums::Presence::Offline: + presenceIcon = Utils::getAppIcon("presenceOffline"); + break; + case LinphoneEnums::Presence::Undefined: + presenceIcon = QUrl(""); + break; + } + return presenceIcon; +} + +QUrl Utils::getRegistrationStateIcon(LinphoneEnums::RegistrationState state) { + mustBeInMainThread(sLog().arg(Q_FUNC_INFO)); + QUrl registrationStateIcon; + switch (state) { + case LinphoneEnums::RegistrationState::Refreshing: + registrationStateIcon = Utils::getAppIcon("regitrationProgress"); + break; + case LinphoneEnums::RegistrationState::Progress: + registrationStateIcon = Utils::getAppIcon("regitrationProgress"); + break; + case LinphoneEnums::RegistrationState::Failed: + registrationStateIcon = Utils::getAppIcon("regitrationError"); + break; + case LinphoneEnums::RegistrationState::Cleared: + registrationStateIcon = Utils::getAppIcon("regitrationDeactivated"); + break; + case LinphoneEnums::RegistrationState::None: + registrationStateIcon = Utils::getAppIcon("regitrationDeactivated"); + break; + default: + registrationStateIcon = QUrl(); + } + return registrationStateIcon; +} + +QString Utils::getPresenceStatus(LinphoneEnums::Presence presence) { + mustBeInMainThread(sLog().arg(Q_FUNC_INFO)); + QString presenceStatus = ""; + switch (presence) { + case LinphoneEnums::Presence::Online: + presenceStatus = tr("contact_presence_status_available"); + break; + case LinphoneEnums::Presence::Away: + presenceStatus = tr("contact_presence_status_away"); + break; + case LinphoneEnums::Presence::Busy: + presenceStatus = tr("contact_presence_status_busy"); + break; + case LinphoneEnums::Presence::DoNotDisturb: + presenceStatus = tr("contact_presence_status_do_not_disturb"); + break; + case LinphoneEnums::Presence::Offline: + presenceStatus = tr("contact_presence_status_offline"); + break; + case LinphoneEnums::Presence::Undefined: + presenceStatus = ""; + break; + } + return presenceStatus; +} diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index 6b20893e9..7e7748680 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -142,6 +142,9 @@ public: Q_INVOKABLE static QString getFileChecksum(const QString &filePath); Q_INVOKABLE QList append(const QList a, const QList b); Q_INVOKABLE QString getAddressToDisplay(QVariantList addressList, QString filter, QString defaultAddress); + Q_INVOKABLE static QColor getPresenceColor(LinphoneEnums::Presence presence); + Q_INVOKABLE static QUrl getPresenceIcon(LinphoneEnums::Presence presence); + Q_INVOKABLE static QString getPresenceStatus(LinphoneEnums::Presence presence); Q_INVOKABLE static VariantObject *getCurrentCallChat(CallGui *call); Q_INVOKABLE static VariantObject *getChatForAddress(QString address); @@ -195,6 +198,12 @@ public: static void runCommandLine(QString command); + // Presence + + static QColor getDefaultStyleColor(const QString &colorName); + static QUrl getAppIcon(const QString &iconName); + static QUrl getRegistrationStateIcon(LinphoneEnums::RegistrationState state); + private: DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index a5e5a69ec..1ad677ed0 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -34,6 +34,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Control/Container/Call/CallGridLayout.qml view/Control/Container/Call/Mosaic.qml view/Control/Container/Contact/ContactLayout.qml + view/Control/Container/Contact/PresenceNoteLayout.qml view/Control/Container/Main/MainRightPanel.qml view/Control/Display/BusyIndicator.qml @@ -54,6 +55,9 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Control/Display/Chat/ChatMessagesListView.qml view/Control/Display/Contact/Avatar.qml view/Control/Display/Contact/Contact.qml + view/Control/Display/Contact/Presence.qml + view/Control/Display/Contact/PresenceStatusItem.qml + view/Control/Display/Contact/PresenceSetCustomStatus.qml view/Control/Display/Contact/ContactListItem.qml view/Control/Display/Contact/ContactListView.qml view/Control/Display/Contact/AllContactListView.qml diff --git a/Linphone/view/Control/Container/Call/CallHistoryLayout.qml b/Linphone/view/Control/Container/Call/CallHistoryLayout.qml index cd321c99f..20f426218 100644 --- a/Linphone/view/Control/Container/Call/CallHistoryLayout.qml +++ b/Linphone/view/Control/Container/Call/CallHistoryLayout.qml @@ -122,29 +122,12 @@ ColumnLayout { } } Text { - property var mode : contact ? contact.core.consolidatedPresence : -1 Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true visible: mainItem.contact - text: mode === LinphoneEnums.ConsolidatedPresence.Online - //: "En ligne" - ? qsTr("contact_presence_status_online") - : mode === LinphoneEnums.ConsolidatedPresence.Busy - //: "Occupé" - ? qsTr("contact_presence_status_busy") - : mode === LinphoneEnums.ConsolidatedPresence.DoNotDisturb - //: "Ne pas déranger" - ? qsTr("contact_presence_status_do_not_disturb") - //: "Hors ligne" - : qsTr("contact_presence_status_offline") - color: mode === LinphoneEnums.ConsolidatedPresence.Online - ? DefaultStyle.success_500main - : mode === LinphoneEnums.ConsolidatedPresence.Busy - ? DefaultStyle.warning_600 - : mode === LinphoneEnums.ConsolidatedPresence.DoNotDisturb - ? DefaultStyle.danger_500main - : DefaultStyle.main2_500main + text: contact ? contact.core.presenceStatus : "" + color: contact ? contact.core.presenceColor : 'transparent' font { pixelSize: Math.round(12 * DefaultStyle.dp) weight: Math.round(300 * DefaultStyle.dp) diff --git a/Linphone/view/Control/Container/Contact/ContactLayout.qml b/Linphone/view/Control/Container/Contact/ContactLayout.qml index 9f6a5281f..1507a7f49 100644 --- a/Linphone/view/Control/Container/Contact/ContactLayout.qml +++ b/Linphone/view/Control/Container/Contact/ContactLayout.qml @@ -30,40 +30,50 @@ ColumnLayout { columnSpacing: Math.round(49 * DefaultStyle.dp) rowSpacing: Math.round(27 * DefaultStyle.dp) - RowLayout { - Layout.preferredWidth: Math.round(341 * DefaultStyle.dp) - Control.Control { - // Layout.preferredWidth: Math.round(734 * DefaultStyle.dp) - Layout.fillWidth: true - width: Math.round(734 * DefaultStyle.dp) - height: Math.round(100 * DefaultStyle.dp) - rightPadding: Math.round(21 * DefaultStyle.dp) - background: GradientRectangle { - anchors.fill: parent - anchors.leftMargin: avatar.width / 2 - radius: Math.round(15 * DefaultStyle.dp) - borderGradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: DefaultStyle.grey_100 } - GradientStop { position: 1.0; color: DefaultStyle.main2_200 } + ColumnLayout { + spacing: Math.round(16 * DefaultStyle.dp) + Layout.preferredWidth: Math.round(341 * DefaultStyle.dp) + RowLayout { + Layout.preferredWidth: Math.round(341 * DefaultStyle.dp) + Control.Control { + // Layout.preferredWidth: Math.round(734 * DefaultStyle.dp) + Layout.fillWidth: true + width: Math.round(734 * DefaultStyle.dp) + height: Math.round(100 * DefaultStyle.dp) + rightPadding: Math.round(21 * DefaultStyle.dp) + background: GradientRectangle { + anchors.fill: parent + anchors.leftMargin: avatar.width / 2 + radius: Math.round(15 * DefaultStyle.dp) + borderGradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: DefaultStyle.grey_100 } + GradientStop { position: 1.0; color: DefaultStyle.main2_200 } + } + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: DefaultStyle.grey_0 } + GradientStop { position: 1.0; color: DefaultStyle.grey_100 } + } } - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: DefaultStyle.grey_0 } - GradientStop { position: 1.0; color: DefaultStyle.grey_100 } - } - } - contentItem: RowLayout { - id: bannerLayout - spacing: Math.round(32 * DefaultStyle.dp) - Avatar { - id: avatar - contact: mainItem.contact - Layout.preferredWidth: Math.round(100 * DefaultStyle.dp) - Layout.preferredHeight: Math.round(100 * DefaultStyle.dp) + contentItem: RowLayout { + id: bannerLayout + spacing: Math.round(32 * DefaultStyle.dp) + Avatar { + id: avatar + contact: mainItem.contact + Layout.preferredWidth: Math.round(100 * DefaultStyle.dp) + Layout.preferredHeight: Math.round(100 * DefaultStyle.dp) + } } } } + PresenceNoteLayout { + visible: contact.core.presenceNote.length > 0 && mainItem.useVerticalLayout + friendCore: contact.core + Layout.preferredWidth: 412 * DefaultStyle.dp + Layout.preferredHeight: 85 * DefaultStyle.dp + } } Item { id: verticalLayoutSecondLine @@ -78,6 +88,18 @@ ColumnLayout { style: ButtonStyle.main } } + Rectangle { + Layout.fillWidth:true + Layout.preferredHeight: 79 * DefaultStyle.dp + color: 'transparent' + visible: contact.core.presenceNote.length > 0 && !mainItem.useVerticalLayout + PresenceNoteLayout { + anchors.centerIn: parent + friendCore: contact.core + width: 412 * DefaultStyle.dp + height: 85 * DefaultStyle.dp + } + } StackLayout { id: detailLayout Layout.alignment: Qt.AlignCenter diff --git a/Linphone/view/Control/Container/Contact/PresenceNoteLayout.qml b/Linphone/view/Control/Container/Contact/PresenceNoteLayout.qml new file mode 100644 index 000000000..2218c8677 --- /dev/null +++ b/Linphone/view/Control/Container/Contact/PresenceNoteLayout.qml @@ -0,0 +1,52 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls.Basic as Control +import Linphone +import UtilsCpp +import SettingsCpp +import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle + +Rectangle { + id: mainItem + property var friendCore + color: DefaultStyle.grey_0 + radius: 20 * DefaultStyle.dp + border.color: DefaultStyle.main2_200 + border.width: 2 * DefaultStyle.dp + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 16 * DefaultStyle.dp + anchors.rightMargin: 16 * DefaultStyle.dp + spacing: 8 * DefaultStyle.dp + + RowLayout { + spacing: 6 * DefaultStyle.dp + + EffectImage { + fillMode: Image.PreserveAspectFit + imageSource: AppIcons.presenceNote + colorizationColor: DefaultStyle.main2_600 + Layout.preferredHeight: Math.round(17 * DefaultStyle.dp) + Layout.preferredWidth: Math.round(17 * DefaultStyle.dp) + } + + Text { + font: Typography.p2 + color: DefaultStyle.main2_600 + text: qsTr("contact_presence_note_title") + } + } + + Text { + font: Typography.p3 + color: DefaultStyle.main2_500main + text: mainItem.friendCore.presenceNote + wrapMode: Text.Wrap + Layout.fillWidth: true + } + } +} diff --git a/Linphone/view/Control/Display/Contact/Avatar.qml b/Linphone/view/Control/Display/Contact/Avatar.qml index 288f3b910..5755854ae 100644 --- a/Linphone/view/Control/Display/Contact/Avatar.qml +++ b/Linphone/view/Control/Display/Contact/Avatar.qml @@ -52,14 +52,8 @@ Loader{ : false property bool securityBreach: securityLevel === LinphoneEnums.SecurityLevel.Unsafe - - property bool displayPresence: account - ? account.core?.registrationState != LinphoneEnums.RegistrationState.Progress && account.core?.registrationState != LinphoneEnums.RegistrationState.Refreshing || false - : contact - ? contact.core?.consolidatedPresence != LinphoneEnums.ConsolidatedPresence.Offline || false - : false - - + property bool displayPresence: true + asynchronous: true sourceComponent: Component{ Item { @@ -108,39 +102,22 @@ Loader{ } } - Rectangle { + + Image { visible: mainItem.displayPresence width: stackView.width/4.5 height: width - radius: width / 2 + sourceSize.width: width + sourceSize.height: width + smooth: false anchors.bottom: parent.bottom anchors.right: parent.right anchors.rightMargin: stackView.width / 15 z: 1 - color: account - ? account.core?.registrationState == LinphoneEnums.RegistrationState.Ok - ? DefaultStyle.success_500main - : account.core?.registrationState == LinphoneEnums.RegistrationState.Cleared || account.core?.registrationState == LinphoneEnums.RegistrationState.None - ? DefaultStyle.warning_600 - : account.core?.registrationState == LinphoneEnums.RegistrationState.Progress || account.core?.registrationState == LinphoneEnums.RegistrationState.Refreshing - ? DefaultStyle.main2_500main - : DefaultStyle.danger_500main - : contact - ? contact.core.consolidatedPresence === LinphoneEnums.ConsolidatedPresence.Online - ? DefaultStyle.success_500main - : contact.core.consolidatedPresence === LinphoneEnums.ConsolidatedPresence.Busy - ? DefaultStyle.warning_600 - : contact.core.consolidatedPresence === LinphoneEnums.ConsolidatedPresence.DoNotDisturb - ? DefaultStyle.danger_500main - : DefaultStyle.main2_500main - : "transparent" - border { - width: Math.round(2 * DefaultStyle.dp) - color: DefaultStyle.grey_0 - } + source: account ? (account.core?.registrationState != LinphoneEnums.RegistrationState.Ok ? account.core?.registrationStateIcon : account.core?.presenceIcon) + : (contact ? contact.core?.presenceIcon : "") } - } Component{ diff --git a/Linphone/view/Control/Display/Contact/Contact.qml b/Linphone/view/Control/Display/Contact/Contact.qml index 084357db1..3abd8f189 100644 --- a/Linphone/view/Control/Display/Contact/Contact.qml +++ b/Linphone/view/Control/Display/Contact/Contact.qml @@ -8,6 +8,8 @@ import QtQuick.Controls.Basic as Control import Linphone import UtilsCpp import SettingsCpp +import 'qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js' as Utils +import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle Control.Control{ id: mainItem @@ -55,66 +57,87 @@ Control.Control{ } } } - Control.Control { - id: registrationStatusItem - Layout.minimumWidth: Math.round(49 * DefaultStyle.dp) - Layout.maximumWidth: 150 - Layout.preferredHeight: Math.round(24 * DefaultStyle.dp) - topPadding: Math.round(4 * DefaultStyle.dp) - bottomPadding: Math.round(4 * DefaultStyle.dp) - leftPadding: Math.round(8 * DefaultStyle.dp) - rightPadding: Math.round(8 * DefaultStyle.dp) - Layout.preferredWidth: text.implicitWidth + (2 * Math.round(8 * DefaultStyle.dp)) - background: Rectangle{ - id: registrationStatus - anchors.fill: parent - color: DefaultStyle.main2_200 - radius: Math.round(90 * DefaultStyle.dp) - } - contentItem: Text { - id: text - anchors.fill: parent - anchors.leftMargin: registrationStatusItem.leftPadding - anchors.rightMargin: registrationStatusItem.rightPadding - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - visible: mainItem.account - property int mode : !mainItem.account || mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.Ok - ? 0 - : mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.Cleared || mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.None - ? 1 - : mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.Progress || mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.Refreshing - ? 2 - : 3 - // Test texts - // Timer{ - // running: true - // interval: 1000 - // repeat: true - // onTriggered: text.mode = (++text.mode) % 4 - // } - font.weight: Math.round(300 * DefaultStyle.dp) - font.pixelSize: Math.round(12 * DefaultStyle.dp) - color: mode == 0 - ? DefaultStyle.success_500main - : mode == 1 - ? DefaultStyle.warning_600 - : mode == 2 - ? DefaultStyle.main2_500main - : DefaultStyle.danger_500main - text: mode == 0 - //: "Connecté" - ? qsTr("drawer_menu_account_connection_status_connected") - : mode == 1 - //: "Désactivé" - ? qsTr("drawer_menu_account_connection_status_cleared") - : mode == 2 - //: "Connexion…" - ? qsTr("drawer_menu_account_connection_status_refreshing") - //: "Erreur" - : qsTr("drawer_menu_account_connection_status_failed") + PopupButton { + id: presenceAndRegistrationItem + Layout.minimumWidth: Math.round(86 * DefaultStyle.dp) + Layout.maximumWidth: Math.round(150 * DefaultStyle.dp) + Layout.preferredHeight: Math.round(24 * DefaultStyle.dp) + Layout.preferredWidth: presenceOrRegistrationText.implicitWidth + Math.round(50 * DefaultStyle.dp) + contentItem: + Rectangle{ + id: presenceBar + property bool isRegistered: mainItem.account?.core.registrationState == LinphoneEnums.RegistrationState.Ok + color: DefaultStyle.main2_200 + radius: Math.round(15 * DefaultStyle.dp) + RowLayout { + anchors.fill: parent + Image { + sourceSize.width: 11 * DefaultStyle.dp + sourceSize.height: 11 * DefaultStyle.dp + smooth: false + Layout.preferredWidth: 11 * DefaultStyle.dp + Layout.preferredHeight: 11 * DefaultStyle.dp + source: presenceBar.isRegistered ? mainItem.account.core.presenceIcon : mainItem.account?.core.registrationStateIcon + Layout.leftMargin: 8 * DefaultStyle.dp + } + Text { + id: presenceOrRegistrationText + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + visible: mainItem.account + // Test texts + // Timer{ + // running: true + // interval: 1000 + // repeat: true + // onTriggered: text.mode = (++text.mode) % 4 + // } + font.weight: Math.round(300 * DefaultStyle.dp) + font.pixelSize: Math.round(12 * DefaultStyle.dp) + color: presenceBar.isRegistered ? mainItem.account.core.presenceColor : mainItem.account?.core.registrationColor + text: presenceBar.isRegistered ? mainItem.account.core.presenceStatus : mainItem.account?.core.humaneReadableRegistrationState + } + EffectImage { + fillMode: Image.PreserveAspectFit + imageSource: AppIcons.downArrow + colorizationColor: DefaultStyle.main2_600 + Layout.preferredHeight: Math.round(14 * DefaultStyle.dp) + Layout.preferredWidth: Math.round(14 * DefaultStyle.dp) + Layout.rightMargin: 8 * DefaultStyle.dp + } + } + } + popup.contentItem: Rectangle { + implicitWidth: 280 * DefaultStyle.dp + implicitHeight: 20 * DefaultStyle.dp + (setCustomStatus.visible ? 240 * DefaultStyle.dp : setPresence.implicitHeight) + Presence { + id: setPresence + anchors.fill: parent + anchors.margins: 20 * DefaultStyle.dp + accountCore: mainItem.account.core + onSetCustomStatusClicked: { + setPresence.visible = false + setCustomStatus.visible = true + } + onIsSet: presenceAndRegistrationItem.popup.close() + } + PresenceSetCustomStatus { + id: setCustomStatus + visible: false + anchors.fill: parent + anchors.margins: 20 * DefaultStyle.dp + accountCore: mainItem.account.core + onVisibleChanged: { + if (!visible) { + setPresence.visible = true + setCustomStatus.visible = false + } + } + onIsSet: presenceAndRegistrationItem.popup.close() + } } } + Item{ Layout.preferredWidth: Math.round(26 * DefaultStyle.dp) Layout.preferredHeight: Math.round(26 * DefaultStyle.dp) diff --git a/Linphone/view/Control/Display/Contact/Presence.qml b/Linphone/view/Control/Display/Contact/Presence.qml new file mode 100644 index 000000000..a685f9248 --- /dev/null +++ b/Linphone/view/Control/Display/Contact/Presence.qml @@ -0,0 +1,114 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Linphone +import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle + +ColumnLayout { + id: mainItem + + property var accountCore + signal setCustomStatusClicked + signal isSet + + spacing: 8 * DefaultStyle.dp + PresenceStatusItem { presence: LinphoneEnums.Presence.Online; accountCore: mainItem.accountCore; onClick: mainItem.isSet()} + PresenceStatusItem { presence: LinphoneEnums.Presence.Away; accountCore: mainItem.accountCore; onClick: mainItem.isSet()} + PresenceStatusItem { presence: LinphoneEnums.Presence.Busy; accountCore: mainItem.accountCore; onClick: mainItem.isSet()} + PresenceStatusItem { presence: LinphoneEnums.Presence.DoNotDisturb; accountCore: mainItem.accountCore; onClick: mainItem.isSet()} + PresenceStatusItem { presence: LinphoneEnums.Presence.Offline; accountCore: mainItem.accountCore; onClick: mainItem.isSet()} + + RowLayout { + spacing: 0 + visible: accountCore.explicitPresence != LinphoneEnums.Presence.Undefined + Layout.alignment: Qt.AlignLeft + Layout.topMargin: Math.round(3 * DefaultStyle.dp) + Layout.bottomMargin: Math.round(3 * DefaultStyle.dp) + Label { + font: Typography.p1 + text: qsTr("contact_presence_reset_status") + color: DefaultStyle.main2_600 + } + Item { + Layout.fillWidth: true + } + Item { + width: Math.round(17 * DefaultStyle.dp) + height: Math.round(17 * DefaultStyle.dp) + + MouseArea { + id: hoverArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + accountCore.resetToAutomaticPresence() + } + } + + EffectImage { + fillMode: Image.PreserveAspectFit + imageSource: AppIcons.reloadArrow + colorizationColor: hoverArea.containsMouse ? DefaultStyle.main2_800 : DefaultStyle.main2_600 + anchors.fill: parent + } + } + } + Rectangle { + height: 1 + width: parent.width + color: DefaultStyle.main2_500main + Layout.topMargin: 8 * DefaultStyle.dp + } + ColumnLayout { + spacing: 19 * DefaultStyle.dp + RowLayout { + spacing: 10 * DefaultStyle.dp + Layout.topMargin: Math.round(3 * DefaultStyle.dp) + Layout.alignment: Qt.AlignLeft + Text { + font: Typography.p1 + text: accountCore.presenceNote.length > 0 ? accountCore.presenceNote : qsTr("contact_presence_custom_status") + color: DefaultStyle.main2_600 + wrapMode: Text.WordWrap + Layout.preferredWidth: (accountCore.presenceNote.length == 0 ? 175 : 230) * DefaultStyle.dp + } + Item { + Layout.fillWidth: true + } + SmallButton { + visible: accountCore.presenceNote.length == 0 + style: ButtonStyle.secondary + text: qsTr("contact_presence_button_set_custom_status") + onClicked: { + mainItem.setCustomStatusClicked() + } + } + } + RowLayout { + visible: accountCore.presenceNote.length > 0 + spacing: 10 * DefaultStyle.dp + Item { + Layout.fillWidth: true + } + SmallButton { + style: ButtonStyle.secondary + text: qsTr("contact_presence_button_edit_custom_status") + onClicked: { + mainItem.setCustomStatusClicked() + } + } + SmallButton { + style: ButtonStyle.secondary + visible: accountCore.presenceNote.length > 0 + text: qsTr("contact_presence_button_delete_custom_status") + onClicked: { + mainItem.accountCore.presenceNote = "" + } + } + } + } + Item { + Layout.fillHeight: true + } +} diff --git a/Linphone/view/Control/Display/Contact/PresenceSetCustomStatus.qml b/Linphone/view/Control/Display/Contact/PresenceSetCustomStatus.qml new file mode 100644 index 000000000..0d822ce19 --- /dev/null +++ b/Linphone/view/Control/Display/Contact/PresenceSetCustomStatus.qml @@ -0,0 +1,78 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Linphone +import SettingsCpp +import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle + +Column { + id: mainItem + spacing: 20 * DefaultStyle.dp + anchors.centerIn: parent + property var accountCore + signal isSet + + Text { + text: qsTr("contact_presence_button_set_custom_status_title") + horizontalAlignment: Text.AlignHCenter + color: DefaultStyle.main2_600 + font: Typography.p2 + } + + Rectangle { + width: parent.width + height: 150 * DefaultStyle.dp + color: "transparent" + border.color: DefaultStyle.main1_500_main + border.width: 1 * DefaultStyle.dp + radius: 8 * DefaultStyle.dp + + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 * DefaultStyle.dp + TextEdit { + id: statusMessage + wrapMode: TextEdit.Wrap + font: Typography.p1 + color: DefaultStyle.main2_500main + Layout.fillHeight: true + Layout.fillWidth: true + property string previoustext: "" + text: mainItem.accountCore.presenceNote + onTextChanged: { + if (statusMessage.text.length > accountCore.maxPresenceNoteSize) { + statusMessage.text = previoustext + statusMessage.cursorPosition = statusMessage.text.length + } else { + previoustext = statusMessage.text + } + } + } + Item { + Layout.fillHeight: true + } + Text { + Layout.fillWidth: true + text: statusMessage.text.length + " / " + accountCore.maxPresenceNoteSize + font: Typography.p1 + color: DefaultStyle.main2_400 + horizontalAlignment: Text.AlignRight + } + } + } + + Row { + spacing: 10 * DefaultStyle.dp + anchors.right: parent.right + + SmallButton { + style: ButtonStyle.secondary + text: qsTr("contact_presence_button_save_custom_status") + enabled: statusMessage.text.length > 0 + onClicked: { + mainItem.accountCore.presenceNote = statusMessage.text + mainItem.isSet() + } + } + } +} diff --git a/Linphone/view/Control/Display/Contact/PresenceStatusItem.qml b/Linphone/view/Control/Display/Contact/PresenceStatusItem.qml new file mode 100644 index 000000000..ca325f397 --- /dev/null +++ b/Linphone/view/Control/Display/Contact/PresenceStatusItem.qml @@ -0,0 +1,53 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Linphone +import UtilsCpp + + +Rectangle { + id: mainItem + + property var accountCore + property var presence + signal click() + + color: mouseArea.containsMouse ? DefaultStyle.main2_100 : "transparent" + width: 236 * DefaultStyle.dp + height: 22 * DefaultStyle.dp + radius: 5 * DefaultStyle.dp + + RowLayout { + anchors.fill: parent + spacing: 10 * DefaultStyle.dp + Layout.alignment: Qt.AlignLeft + + Image { + sourceSize.width: 11 * DefaultStyle.dp + sourceSize.height: 11 * DefaultStyle.dp + smooth: false + Layout.preferredWidth: 11 * DefaultStyle.dp + Layout.preferredHeight: 11 * DefaultStyle.dp + source: UtilsCpp.getPresenceIcon(mainItem.presence) + } + + Text { + text: UtilsCpp.getPresenceStatus(mainItem.presence) + font: Typography.p1 + horizontalAlignment: Text.AlignLeft + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + color: UtilsCpp.getPresenceColor(mainItem.presence) + } + } + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + mainItem.accountCore.presence = mainItem.presence + mainItem.click() + } + } +} diff --git a/Linphone/view/Control/Display/Sticker.qml b/Linphone/view/Control/Display/Sticker.qml index 6973506f6..6b637c731 100644 --- a/Linphone/view/Control/Display/Sticker.qml +++ b/Linphone/view/Control/Display/Sticker.qml @@ -53,6 +53,9 @@ Item { : call.core.remoteName : "" + property var contactObj: UtilsCpp.findFriendByAddress(call.core.remoteAddress) + property var contact: contactObj && contactObj.value || null + property var identityAddress: account ? UtilsCpp.getDisplayName(account.core.identityAddress) : null property bool videoEnabled: (previewEnabled && call && call.core.localVideoEnabled) || (!previewEnabled && call && call.core.remoteVideoEnabled) @@ -168,6 +171,7 @@ Item { anchors.topMargin: Math.round(21 * DefaultStyle.dp) anchors.left: parent.left anchors.right: parent.right + Text { Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter diff --git a/Linphone/view/Control/Tool/Prototype/FriendPrototype.qml b/Linphone/view/Control/Tool/Prototype/FriendPrototype.qml index 0984e46ad..01a284580 100644 --- a/Linphone/view/Control/Tool/Prototype/FriendPrototype.qml +++ b/Linphone/view/Control/Tool/Prototype/FriendPrototype.qml @@ -52,7 +52,8 @@ Window{ RowLayout{ anchors.fill: parent Text{ - text: modelData.core.presenceTimestamp + " == " +modelData.core.consolidatedPresence + " / " + text: modelData.core.presenceStatus + color: modelData.core.presenceColor } Button { text: 'X' diff --git a/Linphone/view/Page/Main/Contact/ContactPage.qml b/Linphone/view/Page/Main/Contact/ContactPage.qml index 31af1feed..0878a9591 100644 --- a/Linphone/view/Page/Main/Contact/ContactPage.qml +++ b/Linphone/view/Page/Main/Contact/ContactPage.qml @@ -454,27 +454,10 @@ FriendGui{ } Text { visible: contactDetail.contact - property var mode: contactDetail.contact ? contactDetail.contact.core.consolidatedPresence : -1 horizontalAlignment: Text.AlignLeft Layout.fillWidth: true - text: mode === LinphoneEnums.ConsolidatedPresence.Online - //: "En ligne" - ? qsTr("contact_presence_status_online") - : mode === LinphoneEnums.ConsolidatedPresence.Busy - //: "Occupé" - ? qsTr("contact_presence_status_busy") - : mode === LinphoneEnums.ConsolidatedPresence.DoNotDisturb - //: "Ne pas déranger" - ? qsTr("contact_presence_status_do_not_disturb") - //: "Hors ligne" - : qsTr("contact_presence_status_offline") - color: mode === LinphoneEnums.ConsolidatedPresence.Online - ? DefaultStyle.success_500main - : mode === LinphoneEnums.ConsolidatedPresence.Busy - ? DefaultStyle.warning_600 - : mode === LinphoneEnums.ConsolidatedPresence.DoNotDisturb - ? DefaultStyle.danger_500main - : DefaultStyle.main2_500main + text: contactDetail.contact ? contactDetail.contact.core.presenceStatus : "" + color: contactDetail.contact ? contactDetail.contact.core.presenceColor : 'transparent' font.pixelSize: Math.round(14 * DefaultStyle.dp) } }, diff --git a/Linphone/view/Style/AppIcons.qml b/Linphone/view/Style/AppIcons.qml index 0067bd66b..d3bbde49a 100644 --- a/Linphone/view/Style/AppIcons.qml +++ b/Linphone/view/Style/AppIcons.qml @@ -128,4 +128,13 @@ QtObject { property string appWindow: "image://internal/app-window.svg" property string bellMwi: "image://internal/bell-simple.svg" property string callForward: "image://internal/call-forward.svg" + property string regitrationDeactivated: "image://internal/regitration_deactivated.svg" + property string regitrationProgress: "image://internal/regitration_progress.svg" + property string regitrationError: "image://internal/regitration_error.svg" + property string presenceOnline: "image://internal/presence_online.svg" + property string presenceAway: "image://internal/presence_away.svg" + property string presenceBusy: "image://internal/presence_busy.svg" + property string presenceDoNotDisturb: "image://internal/presence_do_not_disturb.svg" + property string presenceOffline: "image://internal/presence_offline.svg" + property string presenceNote: "image://internal/presence-note.svg" } diff --git a/Linphone/view/Style/DefaultStyle.qml b/Linphone/view/Style/DefaultStyle.qml index 0acbc4b1a..be8448bf4 100644 --- a/Linphone/view/Style/DefaultStyle.qml +++ b/Linphone/view/Style/DefaultStyle.qml @@ -33,6 +33,7 @@ QtObject { property color warning_600: "#DBB820" property color danger_500main: "#DD5F5F" + property color warning_500_main: "#FFDC2E" property color danger_700: "#9E3548" property color danger_900: "#723333" property color success_500main: "#4FAE80" @@ -62,5 +63,4 @@ QtObject { property color placeholders: '#CACACA' // No name in design - property color warning_500_main: "#FFDC2E" } diff --git a/Linphone/view/Style/Typography.qml b/Linphone/view/Style/Typography.qml index 67a4cb96e..ab887cb86 100644 --- a/Linphone/view/Style/Typography.qml +++ b/Linphone/view/Style/Typography.qml @@ -51,7 +51,7 @@ QtObject { pixelSize: Math.round(13 * DefaultStyle.dp), weight: Math.min(Math.round(700 * DefaultStyle.dp), 1000) }) - + // Text/P2 - Large Bold, reduced paragraph text property font p2l: Qt.font( { family: DefaultStyle.defaultFont,