From a1d72e6382c12c73cd4061ad541b71655d7cf64d Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Fri, 1 Dec 2023 11:21:50 +0100 Subject: [PATCH] Feature : display accounts. - Implement shaders to make round images and use qsb --qt6. - Add picture to Friend. - Display username if displayname is not found. - Compute initials from C++ with emojis. - Add Accounts list in a popup from main window. - Add a hack on account to update avatar on all AcountModel. - Add Avatar item for initials/picture. - Add Contact description item. - Make sizes proportionals to match designs. - Add image colorization. --- Linphone/core/friend/FriendCore.cpp | 18 +++ Linphone/core/friend/FriendCore.hpp | 8 + Linphone/data/CMakeLists.txt | 4 + Linphone/data/shaders/roundEffect.frag | 18 +++ Linphone/data/shaders/roundEffect.frag.qsb | Bin 0 -> 1627 bytes Linphone/data/shaders/roundEffect.vert | 13 ++ Linphone/data/shaders/roundEffect.vert.qsb | Bin 0 -> 1455 bytes Linphone/model/account/AccountModel.cpp | 9 +- Linphone/model/friend/FriendModel.cpp | 22 +++ Linphone/model/friend/FriendModel.hpp | 6 +- Linphone/model/tool/ToolModel.cpp | 1 + Linphone/tool/Utils.cpp | 20 +++ Linphone/tool/Utils.hpp | 1 + Linphone/view/App/Layout/MainLayout.qml | 48 +++--- Linphone/view/App/Main.qml | 4 +- Linphone/view/CMakeLists.txt | 8 + Linphone/view/Item/Account/Accounts.qml | 73 ++++++++++ Linphone/view/Item/Contact/Avatar.qml | 77 ++++++++++ Linphone/view/Item/Contact/Contact.qml | 137 ++++++++++++++++++ .../view/Item/Contact/ContactDescription.qml | 37 +++++ Linphone/view/Item/EffectImage.qml | 23 ++- Linphone/view/Item/Popup.qml | 27 ++++ Linphone/view/Style/AppIcons.qml | 1 + Linphone/view/Style/DefaultStyle.qml | 12 ++ 24 files changed, 537 insertions(+), 30 deletions(-) create mode 100644 Linphone/data/shaders/roundEffect.frag create mode 100644 Linphone/data/shaders/roundEffect.frag.qsb create mode 100644 Linphone/data/shaders/roundEffect.vert create mode 100644 Linphone/data/shaders/roundEffect.vert.qsb create mode 100644 Linphone/view/Item/Account/Accounts.qml create mode 100644 Linphone/view/Item/Contact/Avatar.qml create mode 100644 Linphone/view/Item/Contact/Contact.qml create mode 100644 Linphone/view/Item/Contact/ContactDescription.qml create mode 100644 Linphone/view/Item/Popup.qml diff --git a/Linphone/core/friend/FriendCore.cpp b/Linphone/core/friend/FriendCore.cpp index 019400ce7..b9b465929 100644 --- a/Linphone/core/friend/FriendCore.cpp +++ b/Linphone/core/friend/FriendCore.cpp @@ -40,6 +40,7 @@ FriendCore::FriendCore(const std::shared_ptr &contact) : QObje mFriendModel->setSelf(mFriendModel); mConsolidatedPresence = LinphoneEnums::fromLinphone(contact->getConsolidatedPresence()); mPresenceTimestamp = mFriendModel->getPresenceTimestamp(); + mPictureUri = Utils::coreStringToAppString(contact->getPhoto()); auto address = contact->getAddress(); mAddress = address ? Utils::coreStringToAppString(contact->getAddress()->asString()) : "NoAddress"; mIsSaved = true; @@ -71,6 +72,14 @@ void FriendCore::setSelf(SafeSharedPointer me) { setPresenceTimestamp(presenceTimestamp); }); }); + mFriendModelConnection->makeConnect(mFriendModel.get(), &FriendModel::pictureUriChanged, [this](QString uri) { + mFriendModelConnection->invokeToCore([this, uri]() { this->onPictureUriChanged(uri); }); + }); + + // From GUI + mFriendModelConnection->makeConnect(this, &FriendCore::lSetPictureUri, [this](QString uri) { + mFriendModelConnection->invokeToModel([this, uri]() { mFriendModel->setPictureUri(uri); }); + }); } else { // Create mFriendModelConnection = QSharedPointer( @@ -133,6 +142,15 @@ void FriendCore::setPresenceTimestamp(QDateTime presenceTimestamp) { } } +QString FriendCore::getPictureUri() const { + return mPictureUri; +} + +void FriendCore::onPictureUriChanged(QString uri) { + mPictureUri = uri; + emit pictureUriChanged(); +} + bool FriendCore::getIsSaved() const { return mIsSaved; } diff --git a/Linphone/core/friend/FriendCore.hpp b/Linphone/core/friend/FriendCore.hpp index c025bc569..f9d0e6884 100644 --- a/Linphone/core/friend/FriendCore.hpp +++ b/Linphone/core/friend/FriendCore.hpp @@ -44,6 +44,7 @@ class FriendCore : public QObject, public AbstractObject { Q_PROPERTY(LinphoneEnums::ConsolidatedPresence consolidatedPresence READ getConsolidatedPresence NOTIFY consolidatedPresenceChanged) Q_PROPERTY(bool isSaved READ getIsSaved NOTIFY isSavedChanged) + Q_PROPERTY(QString pictureUri READ getPictureUri WRITE lSetPictureUri NOTIFY pictureUriChanged) public: // Should be call from model Thread. Will be automatically in App thread after initialization @@ -70,6 +71,9 @@ public: bool getIsSaved() const; void setIsSaved(bool isSaved); + QString getPictureUri() const; + void onPictureUriChanged(QString uri); + void onPresenceReceived(LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp); Q_INVOKABLE void remove(); @@ -84,10 +88,13 @@ signals: 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 lSetPictureUri(QString pictureUri); + protected: void writeInto(std::shared_ptr contact) const; void writeFrom(const std::shared_ptr &contact); @@ -96,6 +103,7 @@ protected: QDateTime mPresenceTimestamp; QString mName; QString mAddress; + QString mPictureUri; bool mIsSaved; std::shared_ptr mFriendModel; QSharedPointer mFriendModelConnection; diff --git a/Linphone/data/CMakeLists.txt b/Linphone/data/CMakeLists.txt index e35f06f02..8850549be 100644 --- a/Linphone/data/CMakeLists.txt +++ b/Linphone/data/CMakeLists.txt @@ -4,6 +4,7 @@ list(APPEND _LINPHONEAPP_RC_FILES data/assistant/use-app-sip-account.rc "data/image/info.svg" "data/image/belledonne.svg" "data/image/user-circle.svg" + "data/image/user-circle-gear.svg" "data/image/logo.svg" "data/image/login_image.svg" "data/image/eye-slash.svg" @@ -45,6 +46,9 @@ list(APPEND _LINPHONEAPP_RC_FILES data/assistant/use-app-sip-account.rc "data/image/outgoing_call_missed.svg" "data/image/outgoing_call_rejected.svg" + data/shaders/roundEffect.vert.qsb + data/shaders/roundEffect.frag.qsb + ) set(_LINPHONEAPP_RC_FILES ${_LINPHONEAPP_RC_FILES} PARENT_SCOPE) diff --git a/Linphone/data/shaders/roundEffect.frag b/Linphone/data/shaders/roundEffect.frag new file mode 100644 index 000000000..e26263047 --- /dev/null +++ b/Linphone/data/shaders/roundEffect.frag @@ -0,0 +1,18 @@ +#version 440 +layout(location = 0) in vec2 coord; +layout(location = 0) out vec4 fragColor; +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float edge; +}; +layout(binding = 1) uniform sampler2D src; + +void main() { + float dist = distance(coord, vec2( 0.5 )); + float delta = fwidth(dist); + float alpha = smoothstep( mix(clamp(edge, 0.0, 1.0), 0.0, 0.5) - delta, 0.5, dist ); + + vec4 tex = texture(src, coord); + fragColor = mix( tex, vec4(0.0), alpha) * qt_Opacity; +} diff --git a/Linphone/data/shaders/roundEffect.frag.qsb b/Linphone/data/shaders/roundEffect.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..e074217ab529431a0875d6252a9b69c2dc350a12 GIT binary patch literal 1627 zcmV-h2Bi4_02GLLob6a!Z`4K0Xe(8n$o8Bw=klG)%&fu~ zJIojxgJ%SuGV@pre+lz}D#62kr@@8{R5`%{5O-OVt$|`oDl3D3sye~8SQpfB=CB1; zgV_U3W>lJg!33;61{VTMhpY$OBP4NGlb+5dGo-S?I-ul%ieS6s@@|H$I1VumF*o6@ z1lJD|91I`@NFK~gigV<3DsMU_=-i{XLmNh3XialmX$>cEgAh3Q3RW^jt2}=S?zI zii|fxa(6XeiFoVM-)PQ5-)G@FOSUrlLyf*xSieT=BShO8_LE?>B-ck}CUAZlW|&|@ zK4TK^BqjX}(1&SWqV+P_M*cFQjJp7p&&vba8_*_)-jG{-xQu9iF6N92xVrU7q7T!6QlDf8=c3t2?Q1Bi~JY$LC47<;ht2Jd5wv1gEbn875irZCNV=Xdwz@(2RoQ^ic16v2A)LpqNW$K-9mirj6})4%!=CB20HF!cEext^*p^*2<}JHIQ}kn8SJov}H8j%zrDh1;1H14R z-Gnc!n-FPL#YR%imh(m8HrHL@HtbE1MaLj7L#aV1=uJ)~$V$byipjL}aAC<*-x zcbTW$=MAPa2fK{gzy9Rm)921w5~r#2FzR%L-g0CHd_BAO)!N3!({)>cAELa`6$Ne0 zA6TeAcb>UPlM6B-yBr0aH*a<5x4`Q4-nP*peVu-%J9(!Eveko1MxeXFZVv|9ouX6? z54EvWwM@NE2RFY&A-f;FL0CPQO0;rL&F>VNe}Br=QL(R%2BfaZoe<|)a#b}~^d*ZL zB|}HmP>dnYlxi#qC8TpKJwUD7!Hmyk2IfZW@eV)_b6y)b=};=wu^A{$fz%4C&9Wp z93`G=ei!@d08-~qqmdolN+>&RV66@_jEPuMppsvm87zEOj8}Whui4FEWYLCJk-xLs zD{xb>wDfj&*uG7z;x)4jRE-QiDa3`g&o4D)iLNyw4!+p5EfTApfm>r-W|nTc!fmJi Z3y5RLzqkr}>9w+q=%S7se*lsAR51QeNVot1 literal 0 HcmV?d00001 diff --git a/Linphone/data/shaders/roundEffect.vert b/Linphone/data/shaders/roundEffect.vert new file mode 100644 index 000000000..f76842d39 --- /dev/null +++ b/Linphone/data/shaders/roundEffect.vert @@ -0,0 +1,13 @@ +#version 440 +layout(location = 0) in vec4 qt_Vertex; +layout(location = 1) in vec2 qt_MultiTextCoord0; +layout(location = 0) out vec2 coord; +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; +}; + +void main() { + coord = qt_MultiTextCoord0; + gl_Position = qt_Matrix * qt_Vertex; +} diff --git a/Linphone/data/shaders/roundEffect.vert.qsb b/Linphone/data/shaders/roundEffect.vert.qsb new file mode 100644 index 0000000000000000000000000000000000000000..0951efeecf99fb407f08d2cf2ef1a0ce684026dd GIT binary patch literal 1455 zcmV;g1yK3`03ND%ob6fNPuo@$ze)HgrW6Vk+N~d^VJs;u3kcS!X;3RVt(A(X3PY13 zWQD|G-V&SI*AP}A?PYI!*-QV*{Q=vsMS~tZEt0K&-XdMdyZ`iV{C}Y zm4R!Py+hFd(4xKR8hoY0s z3^J6l&=?}YHF%O4Drl@0rYI9UF%p^#uwAILA$A5^Vk@wFr0ArTLe^Oo?k*sLg$$F% z14Vl**i4ei2HOXt8klf)L?IspD4fxJbNJYjZ8G=vN=N=<@6T2;%&{ScEdOu%&$?Lj(3dq(sy(dgqb>xY~=2EWr}-%t7homKn>JSbcoO~6Z$-UISGO@1-oYjD4< zLf=&Cn*gh{kY_}WxSxXE0Ni!Bra>QL50Fd;`U2I1{1VYa#6!78`2})K!3F(|kUuNDa|4-mxUz7>| zi-h4SNQa1i2pSmoQ_AHE=}!sVB)?2}-=g@xgUtwGah>?+XNG!ag8X2d+obn5Wc{@q z(Y|1ASTvg;=-X0f^9vHZ*=%ZJf6{EuvJY=Qk)9=bkbRt2T<>~bklTi zOy(4fKd1eGBA=%m&ieeL56t^Bs_`LESWiyTpHuYmB>NToB7KK!^NQ_)VmnXq7DzYY zd4oiqh2IIO;p~CwdX{7Bi^Za*dEE7DT;KH@+DlD`)vEGk{RKDHtK7BPCBbVnohnD> z#(uSC@uQM^M)jhK8J^jF}E$KDKaqA_m%E8aB&rQii&wi6z zTV|WDp++&LB7$$5E;rlg(Xr|}R+7t8e!{I@PIQFwy{7TZ@hpxNmNBnFSd4y0^?j^q zJz#<>_1$@bDU!$(43L41>Hfb6#*_;`qk0e;R8`qw65wqXF10V0&OG36w=V;jVtsk` zVLGpU`6}v5_E!Y>%4q}c_`Cyb%kGV`l)b|)e5G{Z{j&=Su+C4j;kV0D)N7fY8sN|X zJA~t&?wWg+$4xi4TMY7cokOEl{n2q{mCNmAOwNdsyQc%ZKRJztXYyRFm{-9kO*(*> z>T+J52%Kh>TQ%~p=3nX%g$LBhOaipiquRmLIh)U(Jl{6fT*vc_C-%P2soJ;&q`dXb z_iGy)&mR?)!jhsYs*AF)gjgFsj|HQudIai#19UdG6>yG&MuXYl#((Ixmfy^UB`L@Dyf`8CmtOw z*rgj_OWi1j#N@tf$ATR98a#NQW&ZfU22T}qJSpGSKq}aT56ej5?)x>dZ~l%r(*qu* z-l?Ya(Rq0e^s(rF%<-|XkhSbu)32NQ{g%nAO~d2$rnS4OMSRa{)xX3ag&$Vo`^CdA zX1m%lJ(y`z7gnW&PonUt@milnWR6)DG4rH`uT*>w9#KuFW*752J3X7eQrV|1Ds_3c z>|V27se~W!xmbk60ax`G^Fgo!=`1Wo?s7qJ-NFomHrm-)fa+`@Z2{gK&JxJw(^+JZ z55F-3hj^9sm&YG`r{_SHUon|8? zy)IO(yYFMW#cafD1GLmPM_lFbwHh_{Ye)DuL`HusjeQ+;$zxB=6Xi+vjAF$71S@VY J*1sC~^7geT>GS{q literal 0 HcmV?d00001 diff --git a/Linphone/model/account/AccountModel.cpp b/Linphone/model/account/AccountModel.cpp index ee7d94c0f..5b4376055 100644 --- a/Linphone/model/account/AccountModel.cpp +++ b/Linphone/model/account/AccountModel.cpp @@ -34,6 +34,10 @@ AccountModel::AccountModel(const std::shared_ptr &account, QO mustBeInLinphoneThread(getClassName()); connect(CoreModel::getInstance().get(), &CoreModel::defaultAccountChanged, this, &AccountModel::onDefaultAccountChanged); + + // Hack because Account doesn't provide callbacks on updated data + connect(this, &AccountModel::defaultAccountChanged, this, + [this]() { emit pictureUriChanged(Utils::coreStringToAppString(mMonitor->getParams()->getPictureUri())); }); } AccountModel::~AccountModel() { @@ -61,7 +65,10 @@ void AccountModel::setPictureUri(QString uri) { } params->setPictureUri(Utils::appStringToCoreString(uri)); account->setParams(params); - emit pictureUriChanged(uri); + // Hack because Account doesn't provide callbacks on updated data + // emit pictureUriChanged(uri); + emit CoreModel::getInstance()->defaultAccountChanged(CoreModel::getInstance()->getCore(), + CoreModel::getInstance()->getCore()->getDefaultAccount()); } void AccountModel::onDefaultAccountChanged() { diff --git a/Linphone/model/friend/FriendModel.cpp b/Linphone/model/friend/FriendModel.cpp index 9f1db1a38..7ea18b8d5 100644 --- a/Linphone/model/friend/FriendModel.cpp +++ b/Linphone/model/friend/FriendModel.cpp @@ -20,7 +20,11 @@ #include "FriendModel.hpp" +#include "core/path/Paths.hpp" +#include "tool/Utils.hpp" +#include "tool/providers/AvatarProvider.hpp" #include +#include DEFINE_ABSTRACT_OBJECT(FriendModel) @@ -48,3 +52,21 @@ QDateTime FriendModel::getPresenceTimestamp() const { void FriendModel::onPresenceReceived(const std::shared_ptr &contact) { emit presenceReceived(LinphoneEnums::fromLinphone(contact->getConsolidatedPresence()), getPresenceTimestamp()); } + +void FriendModel::setPictureUri(QString uri) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto account = std::dynamic_pointer_cast(mMonitor); + auto params = account->getParams()->clone(); + auto oldPictureUri = Utils::coreStringToAppString(params->getPictureUri()); + if (!oldPictureUri.isEmpty()) { + QString appPrefix = QStringLiteral("image://%1/").arg(AvatarProvider::ProviderId); + if (oldPictureUri.startsWith(appPrefix)) { + oldPictureUri = Paths::getAvatarsDirPath() + oldPictureUri.mid(appPrefix.length()); + } + QFile oldPicture(oldPictureUri); + if (!oldPicture.remove()) qWarning() << log().arg("Cannot delete old avatar file at " + oldPictureUri); + } + params->setPictureUri(Utils::appStringToCoreString(uri)); + account->setParams(params); + emit pictureUriChanged(uri); +} diff --git a/Linphone/model/friend/FriendModel.hpp b/Linphone/model/friend/FriendModel.hpp index cd993d8f1..3aefe48c6 100644 --- a/Linphone/model/friend/FriendModel.hpp +++ b/Linphone/model/friend/FriendModel.hpp @@ -39,9 +39,13 @@ public: ~FriendModel(); QDateTime getPresenceTimestamp() const; - std::shared_ptr getFriend() const; + void setPictureUri(QString uri); + +signals: + void pictureUriChanged(QString uri); + private: DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/model/tool/ToolModel.cpp b/Linphone/model/tool/ToolModel.cpp index bc80afe58..ffaaae180 100644 --- a/Linphone/model/tool/ToolModel.cpp +++ b/Linphone/model/tool/ToolModel.cpp @@ -51,6 +51,7 @@ QString ToolModel::getDisplayName(const std::shared_ptr QString displayName; if (address) { displayName = Utils::coreStringToAppString(address->getDisplayName()); + if (displayName.isEmpty()) displayName = Utils::coreStringToAppString(address->getUsername()); // TODO // std::shared_ptr cleanAddress = address->clone(); // cleanAddress->clean(); diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index 093cf4c44..9a850d10c 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -53,6 +53,26 @@ VariantObject *Utils::getDisplayName(const QString &address) { return data; } +QString Utils::getInitials(const QString &username) { + if (username.isEmpty()) return ""; + + QRegularExpression regex("[\\s\\.]+"); + QStringList words = username.split(regex); // Qt 5.14: Qt::SkipEmptyParts + QStringList initials; + auto str32 = words[0].toStdU32String(); + std::u32string char32; + char32 += str32[0]; + initials << QString::fromStdU32String(char32); + for (int i = 1; i < words.size() && initials.size() <= 1; ++i) { + if (words[i].size() > 0) { + str32 = words[i].toStdU32String(); + char32[0] = str32[0]; + initials << QString::fromStdU32String(char32); + } + } + return QLocale().toUpper(initials.join("")); +} + VariantObject *Utils::createCall(const QString &sipAddress, const QString &prepareTransfertAddress, const QHash &headers) { diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index 811972703..29bc6f843 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -49,6 +49,7 @@ public: } Q_INVOKABLE static VariantObject *getDisplayName(const QString &address); + Q_INVOKABLE static QString getInitials(const QString &username); // Support UTF32 Q_INVOKABLE static VariantObject *createCall(const QString &sipAddress, const QString &prepareTransfertAddress = "", const QHash &headers = {}); diff --git a/Linphone/view/App/Layout/MainLayout.qml b/Linphone/view/App/Layout/MainLayout.qml index 0e6ac27b8..4f8a9a0d9 100644 --- a/Linphone/view/App/Layout/MainLayout.qml +++ b/Linphone/view/App/Layout/MainLayout.qml @@ -6,7 +6,7 @@ import QtCore import QtQuick 2.15 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.2 as Control -import QtQuick.Dialogs +import QtQuick.Effects import Linphone import UtilsCpp @@ -17,7 +17,7 @@ Item { RowLayout { anchors.fill: parent // spacing: 30 - anchors.topMargin: 18 + anchors.topMargin: 18 * DefaultStyle.dp VerticalTabBar { id: tabbar Layout.fillHeight: true @@ -32,7 +32,9 @@ Item { Layout.fillWidth: true Layout.fillHeight: true RowLayout { - Layout.leftMargin: 25 + id: topRow + Layout.leftMargin: 25 * DefaultStyle.dp + Layout.rightMargin: 41 * DefaultStyle.dp TextInput { fillWidth: true placeholderText: qsTr("Rechercher un contact, appeler ou envoyer un message...") @@ -41,42 +43,44 @@ Item { id: avatarButton AccountProxy{ id: accountProxy - property bool haveAvatar: defaultAccount && defaultAccount.core.pictureUri || false + //property bool haveAvatar: defaultAccount && defaultAccount.core.pictureUri || false } - Layout.preferredWidth: 30 - Layout.preferredHeight: 30 + Layout.preferredWidth: 54 * DefaultStyle.dp + Layout.preferredHeight: width background: Item { visible: false } - contentItem: Image { + contentItem: Avatar { id: avatar - source: accountProxy.haveAvatar ? accountProxy.defaultAccount.core.pictureUri : AppIcons.welcomeLinphoneLogo - fillMode: Image.PreserveAspectFit + height: avatarButton.height + width: avatarButton.width + account: accountProxy.defaultAccount } onClicked: { - fileDialog.open() + accountList.open() } - FileDialog { - id: fileDialog - currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] - onAccepted: { - var avatarPath = UtilsCpp.createAvatar( selectedFile ) - if(avatarPath){ - accountProxy.defaultAccount.core.pictureUri = avatarPath - } - } - } } Control.Button { + id: settingsButton enabled: false - Layout.preferredWidth: 30 - Layout.preferredHeight: 30 + Layout.preferredWidth: 30 * DefaultStyle.dp + Layout.preferredHeight: 30 * DefaultStyle.dp background: Item { } contentItem: Image { source: AppIcons.verticalDots } + Popup{ + id: accountList + x: -width + parent.width + y: settingsButton.height + (10 * DefaultStyle.dp) + contentWidth: accounts.width + contentHeight: accounts.height + Accounts{ + id: accounts + } + } } } StackLayout { diff --git a/Linphone/view/App/Main.qml b/Linphone/view/App/Main.qml index 8ba48bf6b..931f7c513 100644 --- a/Linphone/view/App/Main.qml +++ b/Linphone/view/App/Main.qml @@ -6,8 +6,8 @@ import Linphone Window { id: mainWindow - width: 1025 - height: 641 + width: 1512 * DefaultStyle.dp + height: 930 * DefaultStyle.dp visible: true title: qsTr("Linphone") property bool firstConnection: true diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index 252db4179..239c6bfd8 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -4,10 +4,17 @@ list(APPEND _LINPHONEAPP_QML_FILES view/App/Layout/LoginLayout.qml view/App/Layout/MainLayout.qml + view/Item/Account/Accounts.qml + view/Item/Button.qml view/Item/Carousel.qml view/Item/CheckBox.qml view/Item/ComboBox.qml + + view/Item/Contact/Avatar.qml + view/Item/Contact/Contact.qml + view/Item/Contact/ContactDescription.qml + view/Item/DesktopPopup.qml view/Item/DigitInput.qml @@ -18,6 +25,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/NumericPad.qml view/Item/PhoneNumberComboBox.qml view/Item/PhoneNumberInput.qml + view/Item/Popup.qml view/Item/RadioButton.qml view/Item/RectangleTest.qml view/Item/SearchBar.qml diff --git a/Linphone/view/Item/Account/Accounts.qml b/Linphone/view/Item/Account/Accounts.qml new file mode 100644 index 000000000..c37ff2491 --- /dev/null +++ b/Linphone/view/Item/Account/Accounts.qml @@ -0,0 +1,73 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.2 as Control + +import Linphone +import UtilsCpp + +Item { + id: mainItem + width: 517 * DefaultStyle.dp + readonly property int topPadding: 23 * DefaultStyle.dp + readonly property int bottomPadding: 18 * DefaultStyle.dp + readonly property int leftPadding: 32 * DefaultStyle.dp + readonly property int rightPadding: 32 * DefaultStyle.dp + readonly property int spacing: 16 * DefaultStyle.dp + implicitHeight: list.contentHeight + topPadding + bottomPadding + 32 * DefaultStyle.dp + 1 + newAccountArea.height + ColumnLayout{ + anchors.top: parent.top + anchors.topMargin: mainItem.topPadding + anchors.left: parent.left + anchors.leftMargin: mainItem.leftPadding + anchors.right: parent.right + anchors.rightMargin: mainItem.rightPadding + ListView{ + id: list + Layout.preferredHeight: contentHeight + Layout.fillWidth: true + spacing: mainItem.spacing + model: AccountProxy{} + delegate: Contact{ + width: list.width + account: modelData + } + } + Rectangle{ + id: separator + Layout.fillWidth: true + Layout.topMargin: mainItem.spacing + Layout.bottomMargin: mainItem.spacing + height: 1 + color: DefaultStyle.main2_300 + } + MouseArea{ // TODO + Layout.fillWidth: true + Layout.preferredHeight: 32 * DefaultStyle.dp + onClicked: console.log('New!') + RowLayout{ + id: newAccountArea + anchors.fill: parent + spacing: 5 * DefaultStyle.dp + EffectImage { + id: newAccount + image.source: AppIcons.plusCircle + Layout.fillHeight: true + Layout.preferredWidth: height + Layout.alignment: Qt.AlignHCenter + image.fillMode: Image.PreserveAspectFit + colorizationColor: DefaultStyle.main2_500main + } + Text{ + Layout.fillHeight: true + Layout.fillWidth: true + verticalAlignment: Text.AlignVCenter + font.weight: 400 * DefaultStyle.dp + font.pixelSize: 14 * DefaultStyle.dp + color: DefaultStyle.main2_500main + text: 'Ajouter un compte' + } + } + } + } +} + diff --git a/Linphone/view/Item/Contact/Avatar.qml b/Linphone/view/Item/Contact/Avatar.qml new file mode 100644 index 000000000..a83e37c9f --- /dev/null +++ b/Linphone/view/Item/Contact/Avatar.qml @@ -0,0 +1,77 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.2 +import QtQuick.Effects + +import Linphone +import UtilsCpp + +// Avatar using initial of the username in case +// they don't have any profile picture + +StackView{ + id: mainItem + property FriendGui contact + property AccountGui account + onAccountChanged: if(account) replace(avatar, StackView.Immediate) + property string address: account ? account.core.identityAddress : '' + property var displayNameObj: UtilsCpp.getDisplayName(address) + property bool haveAvatar: (account && account.core.pictureUri ) + || (contact && contact.core.pictureUri) + + initialItem: haveAvatar ? avatar : initials + Component{ + id: initials + Rectangle { + id: initialItem + property string initials: UtilsCpp.getInitials(mainItem.displayNameObj.value) + onInitialsChanged: console.log("newInit:"+initials) + radius: width / 2 + color: DefaultStyle.main2_200 + height: mainItem.height + width: height + Component.onCompleted: console.log("init:"+initials) + Text { + anchors.fill: parent + anchors.centerIn: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: initialItem.initials + font { + pixelSize: initialItem.height * 36 / 120 + weight: 800 * DefaultStyle.dp + capitalization: Font.AllUppercase + } + } + } + } + Component{ + id: avatar + Item { + id: avatarItem + height: mainItem.height + width: height + + Image { + id: image + visible: false + width: parent.width + height: parent.height + sourceSize.width: avatarItem.width + sourceSize.height: avatarItem.height + fillMode: Image.PreserveAspectCrop + anchors.centerIn: parent + source: mainItem.account ? mainItem.account.core.pictureUri : mainItem.contact.core.pictureUri + mipmap: true + } + ShaderEffect { + id: roundEffect + property variant src: image + property double edge: 0.9 + anchors.fill: parent + vertexShader: 'qrc:/data/shaders/roundEffect.vert.qsb' + fragmentShader: 'qrc:/data/shaders/roundEffect.frag.qsb' + } + } + } +} diff --git a/Linphone/view/Item/Contact/Contact.qml b/Linphone/view/Item/Contact/Contact.qml new file mode 100644 index 000000000..f7993c4fc --- /dev/null +++ b/Linphone/view/Item/Contact/Contact.qml @@ -0,0 +1,137 @@ +import QtCore +import QtQuick +import QtQuick.Effects +import QtQuick.Dialogs +import QtQuick.Layouts + + +import Linphone +import UtilsCpp + +Rectangle{ + id: mainItem + property AccountGui account + height: 45 * DefaultStyle.dp + RowLayout{ + anchors.fill: parent + spacing: 0 + Avatar{ + id: avatar + Layout.preferredWidth: 45 * DefaultStyle.dp + Layout.preferredHeight: 45 * DefaultStyle.dp + account: mainItem.account + MouseArea{ + anchors.fill: parent + onClicked: fileDialog.open() + } + FileDialog { + id: fileDialog + currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + onAccepted: { + var avatarPath = UtilsCpp.createAvatar( selectedFile ) + if(avatarPath){ + mainItem.account.core.pictureUri = avatarPath + } + } + } + } + ContactDescription{ + id: description + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: 10 * DefaultStyle.dp + account: mainItem.account + } + Item{ + id: registrationStatusItem + Layout.preferredWidth: 97 * DefaultStyle.dp + Layout.fillHeight: true + Rectangle{ + id: registrationStatus + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: Math.min(text.implicitWidth + (2 * 8 * DefaultStyle.dp), registrationStatusItem.width) + height: 24 * DefaultStyle.dp + color: DefaultStyle.main2_200 + radius: 90 * DefaultStyle.dp + Text{ + id: text + anchors.fill: parent + anchors.leftMargin: 8 * DefaultStyle.dp + anchors.rightMargin: 8 * DefaultStyle.dp + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + visible: mainItem.account + readonly 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: 300 * DefaultStyle.dp + font.pixelSize: 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é' + : mode == 1 + ? 'Désactivé' + : mode == 2 + ? 'Connexion...' + : 'Erreur' + } + } + } + Item{ // TODO + Layout.preferredWidth: 100 * DefaultStyle.dp + Layout.fillHeight: true + Rectangle{ + id: unreadNotifications + anchors.left: parent.left + anchors.leftMargin: 10 * DefaultStyle.dp + anchors.verticalCenter: parent.verticalCenter + width: 22 * DefaultStyle.dp + height: 22 * DefaultStyle.dp + radius: width/2 + color: DefaultStyle.danger_500main + border.color: DefaultStyle.grey_0 + border.width: 2 * DefaultStyle.dp + Text{ + id: unreadCount + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + color: DefaultStyle.grey_0 + text: '2' + } + } + } + EffectImage { + id: manageAccount + image.source: AppIcons.manageProfile + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + Layout.alignment: Qt.AlignHCenter + image.sourceSize.width: 24 * DefaultStyle.dp + image.fillMode: Image.PreserveAspectFit + colorizationColor: DefaultStyle.main2_500main + MouseArea{ // TODO + anchors.fill: parent + onClicked: console.log('Manage!') + } + } + } +} diff --git a/Linphone/view/Item/Contact/ContactDescription.qml b/Linphone/view/Item/Contact/ContactDescription.qml new file mode 100644 index 000000000..72a22fdb8 --- /dev/null +++ b/Linphone/view/Item/Contact/ContactDescription.qml @@ -0,0 +1,37 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +import Linphone +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 : '' + spacing: 0 + Text{ + id: topTextItem + Layout.fillWidth: true + Layout.fillHeight: true + verticalAlignment: (bottomTextItem.visible?Text.AlignBottom:Text.AlignVCenter) + visible: text != '' + font.weight: 400 * DefaultStyle.dp + font.pixelSize: 14 * DefaultStyle.dp + color: DefaultStyle.main2_700 + text: mainItem.topText + } + Text{ + id: bottomTextItem + Layout.fillWidth: true + Layout.fillHeight: true + verticalAlignment: (topTextItem.visible?Text.AlignTop:Text.AlignVCenter) + visible: text != '' + font.weight: 300 * DefaultStyle.dp + font.pixelSize: 12 * DefaultStyle.dp + color: DefaultStyle.main2_400 + text: mainItem.bottomText + } +} diff --git a/Linphone/view/Item/EffectImage.qml b/Linphone/view/Item/EffectImage.qml index dd3ddac6c..4948e5b00 100644 --- a/Linphone/view/Item/EffectImage.qml +++ b/Linphone/view/Item/EffectImage.qml @@ -9,21 +9,36 @@ Item { id: mainItem property alias image: image property alias effect: effect + property alias effect2: effect2 + property var colorizationColor + readonly property bool useColor: colorizationColor != undefined width: image.width height: image.height Image { id: image - width: 20 - height: 20 - sourceSize.width: 20 + width: parent.width + height: parent.height + //sourceSize.width: parent.width fillMode: Image.PreserveAspectFit anchors.centerIn: parent + visible: !effect2.enabled } MultiEffect { id: effect anchors.fill: image source: image maskSource: image + visible: !effect2.enabled + brightness: effect2.enabled ? 1.0 : 0.0 } -} \ No newline at end of file + MultiEffect { + id: effect2 + enabled: mainItem.useColor + anchors.fill: effect + source: effect + maskSource: effect + colorizationColor: effect2.enabled ? mainItem.colorizationColor : 'black' + colorization: effect2.enabled ? 1.0 : 0.0 + } +} diff --git a/Linphone/view/Item/Popup.qml b/Linphone/view/Item/Popup.qml new file mode 100644 index 000000000..46b6f510b --- /dev/null +++ b/Linphone/view/Item/Popup.qml @@ -0,0 +1,27 @@ +import QtQuick +import QtQuick.Controls as Control +import QtQuick.Effects +import Linphone + +Control.Popup{ + id: mainItem + padding: 0 + background: Item{ + Rectangle{ + id: backgroundItem + width: mainItem.width + height: mainItem.height + radius: 16 * DefaultStyle.dp + border.color: DefaultStyle.grey_0 + border.width: 1 + } + MultiEffect { + anchors.fill: backgroundItem + source: backgroundItem + maskSource: backgroundItem + shadowEnabled: true + shadowBlur: 1.0 + shadowOpacity: 0.1 + } + } +} diff --git a/Linphone/view/Style/AppIcons.qml b/Linphone/view/Style/AppIcons.qml index 8fb67ea23..3801363ed 100644 --- a/Linphone/view/Style/AppIcons.qml +++ b/Linphone/view/Style/AppIcons.qml @@ -14,6 +14,7 @@ QtObject { property string loginImage: "image://internal/login_image.svg" property string belledonne: "image://internal/belledonne.svg" property string profile: "image://internal/user-circle.svg" + property string manageProfile: "image://internal/user-circle-gear.svg" property string verif_page_image: "image://internal/verif_page_image.svg" property string check: "image://internal/check.svg" property string dialer: "image://internal/dialer.svg" diff --git a/Linphone/view/Style/DefaultStyle.qml b/Linphone/view/Style/DefaultStyle.qml index f0ac7d59f..bc86c1a1d 100644 --- a/Linphone/view/Style/DefaultStyle.qml +++ b/Linphone/view/Style/DefaultStyle.qml @@ -78,4 +78,16 @@ QtObject { property color splitViewHandleColor: "#F9F9F9" property color splitViewHoveredHandleColor: "#EDEDED" + + property color danger_500main: "#DD5F5F" + property color grey_0: "#FFFFFF" + property color success_500main: "#4FAE80" + property color warning_600: "#DBB820" + property color main2_200: "#DFECF2" + property color main2_300: "#C0D1D9" + property color main2_400: "#9AABB5" + property color main2_700: "#364860" + property color main2_500main: "#6C7A87" + + property double dp: 1.0//0.66 }