diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index fe4f1a12c..9967ab0c9 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -34,6 +34,7 @@ #include "core/account/AccountCore.hpp" #include "core/account/AccountProxy.hpp" +#include "core/call-history/CallHistoryProxy.hpp" #include "core/call/CallCore.hpp" #include "core/call/CallGui.hpp" #include "core/call/CallList.hpp" @@ -157,6 +158,7 @@ void App::initCppInterfaces() { qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "AccountCore", QLatin1String("Uncreatable")); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "CallCore", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallProxy"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallHistoryProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "FriendGui"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "FriendCore", QLatin1String("Uncreatable")); diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index 524bde53e..3918041e5 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -8,6 +8,10 @@ list(APPEND _LINPHONEAPP_SOURCES core/call/CallGui.cpp core/call/CallList.cpp core/call/CallProxy.cpp + core/call-history/CallHistoryCore.cpp + core/call-history/CallHistoryGui.cpp + core/call-history/CallHistoryList.cpp + core/call-history/CallHistoryProxy.cpp core/camera/CameraGui.cpp core/camera/CameraDummy.cpp core/friend/FriendCore.cpp diff --git a/Linphone/core/account/AccountProxy.cpp b/Linphone/core/account/AccountProxy.cpp index d6e36aa76..0cf377737 100644 --- a/Linphone/core/account/AccountProxy.cpp +++ b/Linphone/core/account/AccountProxy.cpp @@ -23,11 +23,11 @@ #include "AccountList.hpp" AccountProxy::AccountProxy(QObject *parent) : SortFilterProxy(parent) { - mList = AccountList::create(); - connect(mList.get(), &AccountList::countChanged, this, &AccountProxy::resetDefaultAccount); - connect(mList.get(), &AccountList::defaultAccountChanged, this, &AccountProxy::resetDefaultAccount); - connect(mList.get(), &AccountList::haveAccountChanged, this, &AccountProxy::haveAccountChanged); - setSourceModel(mList.get()); + mAccountList = AccountList::create(); + connect(mAccountList.get(), &AccountList::countChanged, this, &AccountProxy::resetDefaultAccount); + connect(mAccountList.get(), &AccountList::defaultAccountChanged, this, &AccountProxy::resetDefaultAccount); + connect(mAccountList.get(), &AccountList::haveAccountChanged, this, &AccountProxy::haveAccountChanged); + setSourceModel(mAccountList.get()); sort(0); } diff --git a/Linphone/core/account/AccountProxy.hpp b/Linphone/core/account/AccountProxy.hpp index 5b8fc2dc6..65021940b 100644 --- a/Linphone/core/account/AccountProxy.hpp +++ b/Linphone/core/account/AccountProxy.hpp @@ -58,7 +58,7 @@ protected: QString mFilterText; AccountGui *mDefaultAccount = nullptr; // When null, a new UI object is build from List - QSharedPointer mList; + QSharedPointer mAccountList; }; #endif diff --git a/Linphone/core/call-history/CallHistoryCore.cpp b/Linphone/core/call-history/CallHistoryCore.cpp new file mode 100644 index 000000000..987d99508 --- /dev/null +++ b/Linphone/core/call-history/CallHistoryCore.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "CallHistoryCore.hpp" +#include "core/App.hpp" +#include "model/call-history/CallHistoryModel.hpp" +#include "model/object/VariantObject.hpp" +#include "model/tool/ToolModel.hpp" +#include "tool/Utils.hpp" +#include "tool/thread/SafeConnection.hpp" + +#include + +DEFINE_ABSTRACT_OBJECT(CallHistoryCore) + +QSharedPointer CallHistoryCore::create(const std::shared_ptr &callLog) { + auto sharedPointer = QSharedPointer(new CallHistoryCore(callLog), &QObject::deleteLater); + sharedPointer->setSelf(sharedPointer); + sharedPointer->moveToThread(App::getInstance()->thread()); + return sharedPointer; +} + +CallHistoryCore::CallHistoryCore(const std::shared_ptr &callLog) : QObject(nullptr) { + // qDebug() << "[CallHistoryCore] new" << this; + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); + // Should be call from model Thread + mustBeInLinphoneThread(getClassName()); + + auto addr = callLog->getRemoteAddress()->clone(); + addr->clean(); + mRemoteAddress = Utils::coreStringToAppString(addr->asStringUriOnly()); + // mRemoteAddress->clean(); + mStatus = LinphoneEnums::fromLinphone(callLog->getStatus()); + mDate = QDateTime::fromMSecsSinceEpoch(callLog->getStartDate() * 1000); + mCallHistoryModel = std::make_shared(callLog); + mIsOutgoing = callLog->getDir() == linphone::Call::Dir::Outgoing; + mDuration = QString::number(callLog->getDuration()); +} + +CallHistoryCore::~CallHistoryCore() { + qDebug() << "[CallHistoryCore] delete" << this; + mustBeInMainThread("~" + getClassName()); +} + +void CallHistoryCore::setSelf(QSharedPointer me) { + mHistoryModelConnection = QSharedPointer>( + new SafeConnection(me, mCallHistoryModel), &QObject::deleteLater); +} + +QString CallHistoryCore::getDuration() const { + return mDuration; +} + +void CallHistoryCore::setDuration(const QString &duration) { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + if (mDuration != duration) { + mDuration = duration; + emit durationChanged(mDuration); + } +} + +void CallHistoryCore::remove() { + mHistoryModelConnection->invokeToModel([this]() { mCallHistoryModel->removeCallHistory(); }); +} \ No newline at end of file diff --git a/Linphone/core/call-history/CallHistoryCore.hpp b/Linphone/core/call-history/CallHistoryCore.hpp new file mode 100644 index 000000000..4ee2debf6 --- /dev/null +++ b/Linphone/core/call-history/CallHistoryCore.hpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CALL_HISTORY_CORE_H_ +#define CALL_HISTORY_CORE_H_ + +#include "tool/LinphoneEnums.hpp" +#include "tool/thread/SafeConnection.hpp" +#include +#include +#include +#include + +class CallHistoryModel; + +class CallHistoryCore : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(QString remoteAddress MEMBER mRemoteAddress CONSTANT) + Q_PROPERTY(bool isOutgoing MEMBER mIsOutgoing CONSTANT) + Q_PROPERTY(QDateTime date MEMBER mDate CONSTANT) + Q_PROPERTY(LinphoneEnums::CallStatus status MEMBER mStatus CONSTANT) + Q_PROPERTY(QString duration READ getDuration WRITE setDuration NOTIFY durationChanged) + +public: + static QSharedPointer create(const std::shared_ptr &callLogs); + CallHistoryCore(const std::shared_ptr &callLog); + ~CallHistoryCore(); + + void setSelf(QSharedPointer me); + + QString getDuration() const; + void setDuration(const QString &duration); + + Q_INVOKABLE void remove(); + + QString mRemoteAddress; + QDateTime mDate; + bool mIsOutgoing; + LinphoneEnums::CallStatus mStatus; + +signals: + void durationChanged(QString duration); + +private: + QString mDuration; + + std::shared_ptr mCallHistoryModel; + QSharedPointer> mHistoryModelConnection; + + DECLARE_ABSTRACT_OBJECT +}; +Q_DECLARE_METATYPE(CallHistoryCore *) +#endif diff --git a/Linphone/core/call-history/CallHistoryGui.cpp b/Linphone/core/call-history/CallHistoryGui.cpp new file mode 100644 index 000000000..ffd5585e2 --- /dev/null +++ b/Linphone/core/call-history/CallHistoryGui.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "CallHistoryGui.hpp" +#include "core/App.hpp" + +DEFINE_ABSTRACT_OBJECT(CallHistoryGui) + +CallHistoryGui::CallHistoryGui(QSharedPointer core) { + // qDebug() << "[CallHistoryGui] new" << this; + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); + mCore = core; + if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); +} + +CallHistoryGui::~CallHistoryGui() { + mustBeInMainThread("~" + getClassName()); + // qDebug() << "[CallHistoryGui] delete" << this; +} + +CallHistoryCore *CallHistoryGui::getCore() const { + return mCore.get(); +} diff --git a/Linphone/core/call-history/CallHistoryGui.hpp b/Linphone/core/call-history/CallHistoryGui.hpp new file mode 100644 index 000000000..ab956d56c --- /dev/null +++ b/Linphone/core/call-history/CallHistoryGui.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CALL_HISTORY_GUI_H_ +#define CALL_HISTORY_GUI_H_ + +#include "CallHistoryCore.hpp" +#include +#include + +class CallHistoryGui : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(CallHistoryCore *core READ getCore CONSTANT) + +public: + CallHistoryGui(QSharedPointer core); + ~CallHistoryGui(); + CallHistoryCore *getCore() const; + QSharedPointer mCore; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/call-history/CallHistoryList.cpp b/Linphone/core/call-history/CallHistoryList.cpp new file mode 100644 index 000000000..dbf0b294e --- /dev/null +++ b/Linphone/core/call-history/CallHistoryList.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "CallHistoryList.hpp" +#include "CallHistoryGui.hpp" +#include "core/App.hpp" +#include "model/object/VariantObject.hpp" +#include "tool/Utils.hpp" +#include +#include + +// ============================================================================= + +DEFINE_ABSTRACT_OBJECT(CallHistoryList) + +QSharedPointer CallHistoryList::create() { + auto model = QSharedPointer(new CallHistoryList(), &QObject::deleteLater); + model->moveToThread(App::getInstance()->thread()); + model->setSelf(model); + return model; +} + +QSharedPointer +CallHistoryList::createCallHistoryCore(const std::shared_ptr &callLog) { + auto callHistoryCore = CallHistoryCore::create(callLog); + return callHistoryCore; +} + +CallHistoryList::CallHistoryList(QObject *parent) : ListProxy(parent) { + mustBeInMainThread(getClassName()); +} + +CallHistoryList::~CallHistoryList() { + mustBeInMainThread("~" + getClassName()); + mModelConnection = nullptr; +} + +void CallHistoryList::setSelf(QSharedPointer me) { + mModelConnection = QSharedPointer>( + new SafeConnection(me, CoreModel::getInstance()), &QObject::deleteLater); + + mModelConnection->makeConnectToCore(&CallHistoryList::lUpdate, [this]() { + mModelConnection->invokeToModel([this]() { + // Avoid copy to lambdas + QList> *callLogs = new QList>(); + mustBeInLinphoneThread(getClassName()); + auto linphoneCallLogs = CoreModel::getInstance()->getCore()->getCallLogs(); + for (auto it : linphoneCallLogs) { + auto model = createCallHistoryCore(it); + callLogs->push_back(model); + } + mModelConnection->invokeToCore([this, callLogs]() { + mustBeInMainThread(getClassName()); + resetData(); + add(*callLogs); + delete callLogs; + }); + }); + }); + + mModelConnection->makeConnectToModel(&CoreModel::callLogUpdated, + [this]() { mModelConnection->invokeToCore([this]() { lUpdate(); }); }); + lUpdate(); +} + +void CallHistoryList::removeAllEntries() { + beginResetModel(); + for (auto it = mList.rbegin(); it != mList.rend(); ++it) { + auto callHistory = it->objectCast(); + callHistory->remove(); + } + mList.clear(); + endResetModel(); +} + +void CallHistoryList::remove(const int &row) { + beginRemoveRows(QModelIndex(), row, row); + auto item = mList[row].objectCast(); + item->remove(); + endRemoveRows(); +} + +QVariant CallHistoryList::data(const QModelIndex &index, int role) const { + int row = index.row(); + if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant(); + if (role == Qt::DisplayRole) { + return QVariant::fromValue(new CallHistoryGui(mList[row].objectCast())); + } + return QVariant(); +} diff --git a/Linphone/core/call-history/CallHistoryList.hpp b/Linphone/core/call-history/CallHistoryList.hpp new file mode 100644 index 000000000..dcb2ae9a4 --- /dev/null +++ b/Linphone/core/call-history/CallHistoryList.hpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CALL_HISTORY_LIST_H_ +#define CALL_HISTORY_LIST_H_ + +#include "../proxy/ListProxy.hpp" +#include "tool/AbstractObject.hpp" +#include "tool/thread/SafeConnection.hpp" +#include + +class CallGui; +class CallHistoryCore; +class CoreModel; +// ============================================================================= + +class CallHistoryList : public ListProxy, public AbstractObject { + Q_OBJECT +public: + static QSharedPointer create(); + // Create a CallHistoryCore and make connections to List. + QSharedPointer createCallHistoryCore(const std::shared_ptr &callLog); + CallHistoryList(QObject *parent = Q_NULLPTR); + ~CallHistoryList(); + + void setSelf(QSharedPointer me); + + void removeAllEntries(); + void remove(const int &row); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // virtual QHash roleNames() const override { + // QHash roles; + // roles[Qt::DisplayRole] = "gui"; + // roles[Qt::DisplayRole + 1] = "name"; + // roles[Qt::DisplayRole + 2] = "date"; + // return roles; + // } + +signals: + void lUpdate(); + +private: + // Check the state from CallHistoryCore: sender() must be a CallHistoryCore. + void onStatusChanged(); + + bool mHaveCallHistory = false; + QSharedPointer> mModelConnection; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/call-history/CallHistoryProxy.cpp b/Linphone/core/call-history/CallHistoryProxy.cpp new file mode 100644 index 000000000..cb55de7ea --- /dev/null +++ b/Linphone/core/call-history/CallHistoryProxy.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "CallHistoryProxy.hpp" +#include "CallHistoryGui.hpp" +#include "CallHistoryList.hpp" +#include "tool/Utils.hpp" + +DEFINE_ABSTRACT_OBJECT(CallHistoryProxy) + +CallHistoryProxy::CallHistoryProxy(QObject *parent) : SortFilterProxy(parent) { + mHistoryList = CallHistoryList::create(); + setSourceModel(mHistoryList.get()); + // sort(0); +} + +CallHistoryProxy::~CallHistoryProxy() { + setSourceModel(nullptr); +} + +QString CallHistoryProxy::getFilterText() const { + return mFilterText; +} + +void CallHistoryProxy::setFilterText(const QString &filter) { + if (mFilterText != filter) { + mFilterText = filter; + invalidate(); + emit filterTextChanged(); + } +} + +void CallHistoryProxy::removeAllEntries() { + static_cast(sourceModel())->removeAllEntries(); +} + +void CallHistoryProxy::removeEntriesWithFilter() { + std::list> itemList(rowCount()); + for (auto i = rowCount() - 1; i >= 0; --i) { + auto item = getItemAt(i); + itemList.emplace_back(item); + } + for (auto item : itemList) { + mHistoryList->ListProxy::remove(item.get()); + if (item) item->remove(); + } +} + +void CallHistoryProxy::updateView() { + mHistoryList->lUpdate(); +} + +bool CallHistoryProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { + bool show = (mFilterText.isEmpty() || mFilterText == "*"); + if (!show) { + QRegularExpression search(QRegularExpression::escape(mFilterText), + QRegularExpression::CaseInsensitiveOption | + QRegularExpression::UseUnicodePropertiesOption); + auto callLog = qobject_cast(sourceModel())->getAt(sourceRow); + show = callLog->mRemoteAddress.contains(search); + } + + return show; +} + +bool CallHistoryProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { + auto l = getItemAt(left.row()); + auto r = getItemAt(right.row()); + + return l->mDate < r->mDate; +} diff --git a/Linphone/core/call-history/CallHistoryProxy.hpp b/Linphone/core/call-history/CallHistoryProxy.hpp new file mode 100644 index 000000000..628fcc68c --- /dev/null +++ b/Linphone/core/call-history/CallHistoryProxy.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CALL_HISTORY_PROXY_H_ +#define CALL_HISTORY_PROXY_H_ + +#include "../proxy/SortFilterProxy.hpp" +#include "CallHistoryGui.hpp" +#include "CallHistoryList.hpp" +#include "tool/AbstractObject.hpp" + +// ============================================================================= + +class CallHistoryProxy : public SortFilterProxy, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(QString filterText READ getFilterText WRITE setFilterText NOTIFY filterTextChanged) + +public: + CallHistoryProxy(QObject *parent = Q_NULLPTR); + ~CallHistoryProxy(); + + QString getFilterText() const; + void setFilterText(const QString &filter); + + Q_INVOKABLE void removeAllEntries(); + Q_INVOKABLE void removeEntriesWithFilter(); + Q_INVOKABLE void updateView(); + +signals: + void filterTextChanged(); + +protected: + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + + QString mFilterText; + QSharedPointer mHistoryList; + + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/proxy/SortFilterProxy.hpp b/Linphone/core/proxy/SortFilterProxy.hpp index 8a5dec126..7fb12904e 100644 --- a/Linphone/core/proxy/SortFilterProxy.hpp +++ b/Linphone/core/proxy/SortFilterProxy.hpp @@ -36,6 +36,11 @@ public: virtual int getCount() const; virtual int getFilterType() const; Q_INVOKABLE QVariant getAt(const int &index) const; + template + QSharedPointer getItemAt(const int &atIndex) const { + auto modelIndex = index(atIndex, 0); + return qobject_cast(sourceModel())->template getAt(mapToSource(modelIndex).row()); + } Q_INVOKABLE void setSortOrder(const Qt::SortOrder &order); virtual void setFilterType(int filterType); diff --git a/Linphone/data/CMakeLists.txt b/Linphone/data/CMakeLists.txt index 3223891a8..7fc2d466a 100644 --- a/Linphone/data/CMakeLists.txt +++ b/Linphone/data/CMakeLists.txt @@ -58,6 +58,9 @@ list(APPEND _LINPHONEAPP_RC_FILES data/assistant/use-app-sip-account.rc "data/image/randomAvatar.png" "data/image/pause.svg" "data/image/smiley.svg" + "data/image/trash-simple.svg" + "data/image/copy.svg" + "data/image/empty.svg" data/shaders/roundEffect.vert.qsb data/shaders/roundEffect.frag.qsb diff --git a/Linphone/data/image/empty.svg b/Linphone/data/image/empty.svg new file mode 100644 index 000000000..08951ccad --- /dev/null +++ b/Linphone/data/image/empty.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/model/CMakeLists.txt b/Linphone/model/CMakeLists.txt index 0516b0453..c4f27bdfe 100644 --- a/Linphone/model/CMakeLists.txt +++ b/Linphone/model/CMakeLists.txt @@ -4,6 +4,8 @@ list(APPEND _LINPHONEAPP_SOURCES model/call/CallModel.cpp + model/call-history/CallHistoryModel.cpp + model/core/CoreModel.cpp model/friend/FriendModel.cpp diff --git a/Linphone/model/call-history/CallHistoryModel.cpp b/Linphone/model/call-history/CallHistoryModel.cpp new file mode 100644 index 000000000..4861dde28 --- /dev/null +++ b/Linphone/model/call-history/CallHistoryModel.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "CallHistoryModel.hpp" + +#include + +#include "model/core/CoreModel.hpp" +#include "tool/Utils.hpp" + +DEFINE_ABSTRACT_OBJECT(CallHistoryModel) + +CallHistoryModel::CallHistoryModel(const std::shared_ptr &callLog, QObject *parent) + : callLog(callLog) { + mustBeInLinphoneThread(getClassName()); +} + +CallHistoryModel::~CallHistoryModel() { + qDebug() << "[CallHistoryModel] delete" << this; + mustBeInLinphoneThread("~" + getClassName()); +} + +void CallHistoryModel::removeCallHistory() { + CoreModel::getInstance()->getCore()->removeCallLog(callLog); +} \ No newline at end of file diff --git a/Linphone/model/call-history/CallHistoryModel.hpp b/Linphone/model/call-history/CallHistoryModel.hpp new file mode 100644 index 000000000..c9507c456 --- /dev/null +++ b/Linphone/model/call-history/CallHistoryModel.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CALL_HISTORY_MODEL_H_ +#define CALL_HISTORY_MODEL_H_ + +#include "model/listener/Listener.hpp" +#include "tool/AbstractObject.hpp" + +#include +#include +#include + +class CallHistoryModel : public QObject, public AbstractObject { + Q_OBJECT +public: + CallHistoryModel(const std::shared_ptr &callLog, QObject *parent = nullptr); + ~CallHistoryModel(); + + void removeCallHistory(); + +private: + std::shared_ptr callLog; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index 421cb1aa5..700b10e6f 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -89,6 +89,7 @@ VariantObject *Utils::createCall(const QString &sipAddress, auto app = App::getInstance(); auto window = app->getCallsWindow(callGui); smartShowWindow(window); + // callGui.value()->getCore()->lSetCameraEnabled(true); }); return callGui; } else return QVariant(); @@ -174,7 +175,7 @@ QString Utils::createAvatar(const QUrl &fileUrl) { return fileUri; } -QString Utils::formatElapsedTime(int seconds) { +QString Utils::formatElapsedTime(int seconds, bool dotsSeparator) { // s, m, h, d, W, M, Y // 1, 60, 3600, 86400, 604800, 2592000, 31104000 auto y = floor(seconds / 31104000); @@ -192,14 +193,50 @@ QString Utils::formatElapsedTime(int seconds) { QString hours, min, sec; - if (h < 10 && h > 0) hours = "0"; + if (dotsSeparator && h < 10 && h > 0) hours = "0"; hours.append(QString::number(h)); - if (m < 10) min = "0"; + if (dotsSeparator && m < 10) min = "0"; min.append(QString::number(m)); - if (s < 10) sec = "0"; + if (dotsSeparator && s < 10) sec = "0"; sec.append(QString::number(s)); - return (h == 0 ? "" : hours + ":") + min + ":" + sec; + if (dotsSeparator) return (h == 0 ? "" : hours + ":") + min + ":" + sec; + else return (h == 0 ? "" : hours + "h ") + (m == 0 ? "" : min + "min ") + sec + "s"; } + +QString Utils::formatDate(const QDateTime &date, bool includeTime) { + QString format = date.date().year() == QDateTime::currentDateTime().date().year() ? "dd MMMM" : "dd MMMM yyyy"; + auto dateDay = tr(date.date().toString(format).toLocal8Bit().data()); + if (!includeTime) return dateDay; + + auto time = date.time().toString("hh:mm"); + return dateDay + " | " + time; +} + +QString Utils::formatDateElapsedTime(const QDateTime &date) { + // auto y = floor(seconds / 31104000); + // if (y > 0) return QString::number(y) + " years"; + // auto M = floor(seconds / 2592000); + // if (M > 0) return QString::number(M) + " months"; + // auto w = floor(seconds / 604800); + // if (w > 0) return QString::number(w) + " week"; + auto dateSec = date.secsTo(QDateTime::currentDateTime()); + + auto d = floor(dateSec / 86400); + if (d > 7) { + return formatDate(date, false); + } else if (d > 0) { + return tr(date.date().toString("dddd").toLocal8Bit().data()); + } + + auto h = floor(dateSec / 3600); + if (h > 0) return QString::number(h) + " h"; + + auto m = floor((dateSec - h * 3600) / 60); + if (m > 0) return QString::number(m) + " m"; + + auto s = dateSec - h * 3600 - m * 60; + return QString::number(s) + " s"; +} \ No newline at end of file diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index 376ab83c3..f38746ab6 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -64,7 +64,10 @@ public: Q_INVOKABLE static VariantObject *haveAccount(); Q_INVOKABLE static void smartShowWindow(QQuickWindow *window); Q_INVOKABLE static QString createAvatar(const QUrl &fileUrl); // Return the avatar path - Q_INVOKABLE static QString formatElapsedTime(int seconds); // Return the elapsed time formated + Q_INVOKABLE static QString formatElapsedTime(int seconds, + bool dotsSeparator = true); // Return the elapsed time formated + Q_INVOKABLE static QString formatDate(const QDateTime &date, bool includeTime = true); // Return the date formated + Q_INVOKABLE static QString formatDateElapsedTime(const QDateTime &date); // Return the date formated static inline QString coreStringToAppString(const std::string &str) { if (Constants::LinphoneLocaleEncoding == QString("UTF-8")) return QString::fromStdString(str); diff --git a/Linphone/view/App/CallsWindow.qml b/Linphone/view/App/CallsWindow.qml index 3b755733d..e83bf3418 100644 --- a/Linphone/view/App/CallsWindow.qml +++ b/Linphone/view/App/CallsWindow.qml @@ -121,9 +121,9 @@ Window { component BottomButton : Button { + id: bottomButton required property string enabledIcon property string disabledIcon - id: bottomButton enabled: call != undefined padding: 18 * DefaultStyle.dp checkable: true @@ -367,7 +367,8 @@ Window { Avatar { Layout.alignment: Qt.AlignCenter // TODO : remove username when friend list ready - address: mainWindow.peerNameText + call: mainWindow.call + // address: mainWindow.peerNameText Layout.preferredWidth: 120 * DefaultStyle.dp Layout.preferredHeight: 120 * DefaultStyle.dp } @@ -414,6 +415,7 @@ Window { } Text { anchors.left: parent.left + anchors.right: parent.right anchors.bottom: parent.bottom anchors.leftMargin: 10 * DefaultStyle.dp anchors.bottomMargin: 10 * DefaultStyle.dp @@ -528,15 +530,13 @@ Window { id: callList model: callsModel height: contentHeight - onHeightChanged: console.log("height changzed lustviexw", height, contentHeight) spacing: 15 * DefaultStyle.dp onCountChanged: forceLayout() delegate: Item { - anchors.left: parent.left - anchors.right: parent.right id: callDelegate + width: callList.width height: 45 * DefaultStyle.dp RowLayout { @@ -567,118 +567,62 @@ Window { || modelData.core.state === LinphoneEnums.CallState.PausedByRemote ? qsTr("Appel en pause") : qsTr("Appel en cours") } - Button { + PopupButton { id: listCallOptionsButton - checked: listCallOptionsMenu.visible Layout.preferredWidth: 24 * DefaultStyle.dp Layout.preferredHeight: 24 * DefaultStyle.dp Layout.alignment: Qt.AlignRight - leftPadding: 0 - rightPadding: 0 - topPadding: 0 - bottomPadding: 0 - background: Rectangle { - anchors.fill: listCallOptionsButton - opacity: listCallOptionsButton.checked ? 1 : 0 - color: DefaultStyle.main2_300 - radius: 40 * DefaultStyle.dp - } - contentItem: Image { - source: AppIcons.verticalDots - sourceSize.width: 24 * DefaultStyle.dp - sourceSize.height: 24 * DefaultStyle.dp - width: 24 * DefaultStyle.dp - height: 24 * DefaultStyle.dp - } - onPressed: { - console.log("listCallOptionsMenu visible", listCallOptionsMenu.visible, "opened", listCallOptionsMenu.opened) - if (listCallOptionsMenu.visible){ - console.log("close popup") - listCallOptionsMenu.close() - } - else { - console.log("open popup") - listCallOptionsMenu.open() - } - } - Control.Popup { - id: listCallOptionsMenu - x: - width - y: listCallOptionsButton.height - onVisibleChanged: console.log("popup visible", visible) - closePolicy: Popup.CloseOnPressOutsideParent | Popup.CloseOnEscape - padding: 20 * DefaultStyle.dp - - background: Item { - anchors.fill: parent - Rectangle { - id: callOptionsMenuPopup - anchors.fill: parent - color: DefaultStyle.grey_0 - radius: 16 * DefaultStyle.dp - } - MultiEffect { - source: callOptionsMenuPopup - anchors.fill: callOptionsMenuPopup - shadowEnabled: true - shadowBlur: 1 - shadowColor: DefaultStyle.grey_900 - shadowOpacity: 0.4 - } - } - - contentItem: ColumnLayout { - spacing: 0 - Control.Button { - background: Item {} - contentItem: RowLayout { - Image { - source: modelData.core.state === LinphoneEnums.CallState.Paused - || modelData.core.state === LinphoneEnums.CallState.PausedByRemote - ? AppIcons.phone : AppIcons.pause - sourceSize.width: 32 * DefaultStyle.dp - sourceSize.height: 32 * DefaultStyle.dp - Layout.preferredWidth: 32 * DefaultStyle.dp - Layout.preferredHeight: 32 * DefaultStyle.dp - fillMode: Image.PreserveAspectFit - } - Text { - text: modelData.core.state === LinphoneEnums.CallState.Paused - || modelData.core.state === LinphoneEnums.CallState.PausedByRemote - ? qsTr("Reprendre l'appel") : qsTr("Mettre en pause") - color: DefaultStyle.main2_500main - Layout.preferredWidth: metrics.width - } - TextMetrics { - id: metrics - text: qsTr("Reprendre l'appel") - } - Item { - Layout.fillWidth: true - } + popup.contentItem: ColumnLayout { + spacing: 0 + Control.Button { + background: Item {} + contentItem: RowLayout { + Image { + source: modelData.core.state === LinphoneEnums.CallState.Paused + || modelData.core.state === LinphoneEnums.CallState.PausedByRemote + ? AppIcons.phone : AppIcons.pause + sourceSize.width: 32 * DefaultStyle.dp + sourceSize.height: 32 * DefaultStyle.dp + Layout.preferredWidth: 32 * DefaultStyle.dp + Layout.preferredHeight: 32 * DefaultStyle.dp + fillMode: Image.PreserveAspectFit } - onClicked: modelData.core.lSetPaused(!modelData.core.paused) - } - Control.Button { - background: Item {} - contentItem: RowLayout { - EffectImage { - image.source: AppIcons.endCall - colorizationColor: DefaultStyle.danger_500main - width: 32 * DefaultStyle.dp - height: 32 * DefaultStyle.dp - } - Text { - color: DefaultStyle.danger_500main - text: qsTr("Terminer l'appel") - } - Item { - Layout.fillWidth: true - } + Text { + text: modelData.core.state === LinphoneEnums.CallState.Paused + || modelData.core.state === LinphoneEnums.CallState.PausedByRemote + ? qsTr("Reprendre l'appel") : qsTr("Mettre en pause") + color: DefaultStyle.main2_500main + Layout.preferredWidth: metrics.width + } + TextMetrics { + id: metrics + text: qsTr("Reprendre l'appel") + } + Item { + Layout.fillWidth: true } - onClicked: mainWindow.endCall(modelData) } + onClicked: modelData.core.lSetPaused(!modelData.core.paused) + } + Control.Button { + background: Item {} + contentItem: RowLayout { + EffectImage { + image.source: AppIcons.endCall + colorizationColor: DefaultStyle.danger_500main + width: 32 * DefaultStyle.dp + height: 32 * DefaultStyle.dp + } + Text { + color: DefaultStyle.danger_500main + text: qsTr("Terminer l'appel") + } + Item { + Layout.fillWidth: true + } + } + onClicked: mainWindow.endCall(modelData) } } } diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index 95711d9f9..ebafceb4b 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -15,18 +15,16 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/Prototype/CanvasCircle.qml - view/Item/BusyIndicator.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/Contact/Sticker.qml + view/Item/BusyIndicator.qml + view/Item/Button.qml + view/Item/Carousel.qml + view/Item/CheckBox.qml + view/Item/ComboBox.qml view/Item/DesktopPopup.qml view/Item/DigitInput.qml view/Item/EffectImage.qml @@ -35,6 +33,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/PhoneNumberComboBox.qml view/Item/PhoneNumberInput.qml view/Item/Popup.qml + view/Item/PopupButton.qml view/Item/RadioButton.qml view/Item/RectangleTest.qml view/Item/SearchBar.qml diff --git a/Linphone/view/Item/Contact/Contact.qml b/Linphone/view/Item/Contact/Contact.qml index d113ac4ae..6407c2414 100644 --- a/Linphone/view/Item/Contact/Contact.qml +++ b/Linphone/view/Item/Contact/Contact.qml @@ -104,7 +104,7 @@ Rectangle{ width: 22 * DefaultStyle.dp height: 22 * DefaultStyle.dp radius: width/2 - color: DefaultStyle.danger_500 + color: DefaultStyle.danger_500main border.color: DefaultStyle.grey_0 border.width: 2 * DefaultStyle.dp Text{ diff --git a/Linphone/view/Item/Notification/NotificationReceivedCall.qml b/Linphone/view/Item/Notification/NotificationReceivedCall.qml index efbc7b2cf..a50166f77 100644 --- a/Linphone/view/Item/Notification/NotificationReceivedCall.qml +++ b/Linphone/view/Item/Notification/NotificationReceivedCall.qml @@ -12,6 +12,7 @@ Notification { readonly property var call: notificationData && notificationData.call property var state: call.core.state onStateChanged:{ + console.log("state notif", state) if(state != LinphoneEnums.CallState.IncomingReceived){ close() } diff --git a/Linphone/view/Item/PopupButton.qml b/Linphone/view/Item/PopupButton.qml new file mode 100644 index 000000000..819260318 --- /dev/null +++ b/Linphone/view/Item/PopupButton.qml @@ -0,0 +1,62 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 as Control +import QtQuick.Effects +import Linphone + +Button { + id: mainItem + property alias popup: popup + checked: popup.visible + implicitWidth: 24 * DefaultStyle.dp + implicitHeight: 24 * DefaultStyle.dp + leftPadding: 0 + rightPadding: 0 + topPadding: 0 + bottomPadding: 0 + function closePopup() { + popup.close() + } + background: Rectangle { + anchors.fill: mainItem + visible: mainItem.checked + color: DefaultStyle.main2_300 + radius: 40 * DefaultStyle.dp + } + contentItem: Image { + source: AppIcons.verticalDots + sourceSize.width: 24 * DefaultStyle.dp + sourceSize.height: 24 * DefaultStyle.dp + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + } + onPressed: { + if (popup.visible) popup.close() + else popup.open() + } + Control.Popup { + id: popup + x: - width + y: mainItem.height + closePolicy: Popup.CloseOnPressOutsideParent | Popup.CloseOnEscape + + padding: 20 * DefaultStyle.dp + + background: Item { + anchors.fill: parent + Rectangle { + id: callOptionsMenuPopup + anchors.fill: parent + color: DefaultStyle.grey_0 + radius: 16 * DefaultStyle.dp + } + MultiEffect { + source: callOptionsMenuPopup + anchors.fill: callOptionsMenuPopup + shadowEnabled: true + shadowBlur: 1 + shadowColor: DefaultStyle.grey_900 + shadowOpacity: 0.4 + } + } + } +} \ No newline at end of file diff --git a/Linphone/view/Page/Main/AbstractMainPage.qml b/Linphone/view/Page/Main/AbstractMainPage.qml index f68f394b3..258a3c7e3 100644 --- a/Linphone/view/Page/Main/AbstractMainPage.qml +++ b/Linphone/view/Page/Main/AbstractMainPage.qml @@ -15,9 +15,8 @@ Item { property string newItemIconSource property string emptyListText property alias leftPanelContent: leftPanel.children - property var rightPanelContent: rightPanelItem.children + property alias rightPanelContent: rightPanelItem.children property bool showDefaultItem: true - // onShowDefaultItemChanged: stackView.replace(showDefaultItem ? defaultItem : rightPanelItem) signal noItemButtonPressed() Control.SplitView { @@ -31,7 +30,7 @@ Item { ColumnLayout { id: leftPanel - Control.SplitView.preferredWidth: 280 * DefaultStyle.dp + Control.SplitView.preferredWidth: 350 * DefaultStyle.dp } Rectangle { id: rightPanel @@ -44,6 +43,7 @@ Item { id: defaultItem Layout.fillWidth: true Layout.fillHeight: true + RowLayout { Layout.fillHeight: true Layout.fillWidth: true @@ -105,7 +105,7 @@ Item { } } - Item { + ColumnLayout { id: rightPanelItem Layout.fillWidth: true Layout.fillHeight: true @@ -114,4 +114,3 @@ Item { } } } - diff --git a/Linphone/view/Page/Main/CallPage.qml b/Linphone/view/Page/Main/CallPage.qml index 4e72657fd..c49a73001 100644 --- a/Linphone/view/Page/Main/CallPage.qml +++ b/Linphone/view/Page/Main/CallPage.qml @@ -1,4 +1,5 @@ import QtQuick 2.15 +import QtQuick.Effects import QtQuick.Layouts import QtQuick.Controls as Control import Linphone @@ -10,26 +11,33 @@ AbstractMainPage { emptyListText: qsTr("Historique d'appel vide") newItemIconSource: AppIcons.newCall + property var selectedRowHistoryGui + + signal listViewUpdated() + onNoItemButtonPressed: goToNewCall() - + + showDefaultItem: listStackView.currentItem.listView ? listStackView.currentItem.listView.count === 0 : true + function goToNewCall() { listStackView.push(newCallItem) } leftPanelContent: Item { + id: leftPanel Layout.fillWidth: true Layout.fillHeight: true Control.StackView { id: listStackView clip: true - initialItem: listItem + initialItem: historyListItem anchors.fill: parent property int sideMargin: 25 * DefaultStyle.dp // anchors.leftMargin: 25 // anchors.rightMargin: 25 } Component { - id: listItem + id: historyListItem ColumnLayout { RowLayout { @@ -45,12 +53,35 @@ AbstractMainPage { Item { Layout.fillWidth: true } - Control.Button { - enabled: false - background: Item { - } - contentItem: Image { - source: AppIcons.verticalDots + PopupButton { + id: removeHistory + popup.x: 0 + popup.padding: 10 * DefaultStyle.dp + popup.contentItem: Button { + background: Item{} + contentItem: RowLayout { + EffectImage { + image.source: AppIcons.trashCan + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + image.fillMode: Image.PreserveAspectFit + colorizationColor: DefaultStyle.danger_500main + } + Text { + text: qsTr("Supprimmer l’historique") + color: DefaultStyle.danger_500main + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + onClicked: { + historyListView.model.removeAllEntries() + removeHistory.closePopup() + } } } Control.Button { @@ -65,112 +96,200 @@ AbstractMainPage { fillMode: Image.PreserveAspectFit } onClicked: { - console.log("[CallPage]User: create new call") + console.debug("[CallPage]User: create new call") listStackView.push(newCallItem) } } } - Control.Control { - id: listLayout - Layout.fillWidth: true - Layout.fillHeight: true - Layout.leftMargin: listStackView.sideMargin - Layout.rightMargin: listStackView.sideMargin + RowLayout { + Control.Control { + id: listLayout + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: listStackView.sideMargin + Layout.rightMargin: listStackView.sideMargin - background: Rectangle { - anchors.fill: parent - } - ColumnLayout { - anchors.fill: parent - SearchBar { - id: searchBar - Layout.alignment: Qt.AlignTop - Layout.fillWidth: true - placeholderText: qsTr("Rechercher un appel") + background: Rectangle { + anchors.fill: parent } ColumnLayout { - Text { - text: qsTr("Aucun appel") - font { - pixelSize: 16 * DefaultStyle.dp - weight: 800 * DefaultStyle.dp - } - visible: listView.count === 0 - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 30 * DefaultStyle.dp - } - ListView { - id: listView - clip: true + anchors.fill: parent + SearchBar { + id: searchBar + Layout.alignment: Qt.AlignTop Layout.fillWidth: true - Layout.fillHeight: true - model: 0 - currentIndex: 0 - - delegate: Item { - required property int index - width:listView.width - height: 30 * DefaultStyle.dp - // RectangleTest{} - RowLayout { - anchors.fill: parent - Image { - source: AppIcons.info - } - ColumnLayout { - Text { - text: "John Doe" - } - // RowLayout { - // Image { - // source: AppIcons.incomingCall - // } - // Text { - // text: "info sur l'appel" - // } - // } - } - Item { - Layout.fillWidth: true - } - Control.Button { - implicitWidth: 30 * DefaultStyle.dp - implicitHeight: 30 * DefaultStyle.dp - background: Item { - visible: false - } - contentItem: Image { - source: AppIcons.phone - width: 20 * DefaultStyle.dp - sourceSize.width: 20 * DefaultStyle.dp - fillMode: Image.PreserveAspectFit - } - } + placeholderText: qsTr("Rechercher un appel") + } + ColumnLayout { + Text { + text: qsTr("Aucun appel") + font { + pixelSize: 16 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp } - MouseArea { - hoverEnabled: true - Rectangle { + visible: historyListView.count === 0 + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 30 * DefaultStyle.dp + } + ListView { + id: historyListView + clip: true + Layout.fillWidth: true + Layout.fillHeight: true + Layout.topMargin: 30 * DefaultStyle.dp + model: CallHistoryProxy{ + filterText: searchBar.text + } + currentIndex: -1 + onCurrentIndexChanged: { + mainItem.selectedRowHistoryGui = model.getAt(currentIndex) + } + spacing: 10 * DefaultStyle.dp + highlightMoveDuration: 10 + highlightMoveVelocity: -1 + // highlightFollowsCurrentItem: true + highlight: Rectangle { + x: historyListView.x + width: historyListView.width + height: historyListView.height + color: DefaultStyle.main2_100 + y: historyListView.currentItem? historyListView.currentItem.y : 0 + } + + delegate: Item { + width:historyListView.width + height: 56 * DefaultStyle.dp + anchors.topMargin: 5 * DefaultStyle.dp + anchors.bottomMargin: 5 * DefaultStyle.dp + RowLayout { + z: 1 anchors.fill: parent - opacity: 0.1 - radius: 15 * DefaultStyle.dp - color: DefaultStyle.main2_500main - visible: parent.containsMouse + Item { + Layout.preferredWidth: historyAvatar.width + Layout.preferredHeight: historyAvatar.height + Layout.leftMargin: 5 * DefaultStyle.dp + MultiEffect { + source: historyAvatar + anchors.fill: historyAvatar + shadowEnabled: true + shadowBlur: 1 + shadowColor: DefaultStyle.grey_900 + shadowOpacity: 0.1 + } + Avatar { + id: historyAvatar + address: modelData.core.remoteAddress + width: 45 * DefaultStyle.dp + height: 45 * DefaultStyle.dp + } + } + ColumnLayout { + Layout.alignment: Qt.AlignVCenter + Text { + property var remoteAddress: modelData ? UtilsCpp.getDisplayName(modelData.core.remoteAddress) : undefined + text: remoteAddress ? remoteAddress.value : "" + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + RowLayout { + Image { + source: modelData.core.status === LinphoneEnums.CallStatus.Declined + || modelData.core.status === LinphoneEnums.CallStatus.DeclinedElsewhere + || modelData.core.status === LinphoneEnums.CallStatus.Aborted + || modelData.core.status === LinphoneEnums.CallStatus.EarlyAborted + ? modelData.core.isOutgoing + ? AppIcons.outgoingCallRejected + : AppIcons.incomingCallRejected + : modelData.core.status === LinphoneEnums.CallStatus.Missed + ? modelData.core.isOutgoing + ? AppIcons.outgoingCallMissed + : AppIcons.incomingCallMissed + : modelData.core.isOutgoing + ? AppIcons.outgoingCall + : AppIcons.incomingCall + Layout.preferredWidth: 5 * DefaultStyle.dp + Layout.preferredHeight: 5 * DefaultStyle.dp + sourceSize.width: 5 * DefaultStyle.dp + sourceSize.height: 5 * DefaultStyle.dp + } + Text { + // text: modelData.core.date + text: UtilsCpp.formatDateElapsedTime(modelData.core.date) + font { + pixelSize: 12 * DefaultStyle.dp + weight: 300 * DefaultStyle.dp + } + } + } + } + Item { + Layout.fillWidth: true + } + Control.Button { + implicitWidth: 24 * DefaultStyle.dp + implicitHeight: 24 * DefaultStyle.dp + Layout.rightMargin: 5 * DefaultStyle.dp + padding: 0 + background: Item { + visible: false + } + contentItem: Image { + source: AppIcons.phone + width: 24 * DefaultStyle.dp + sourceSize.width: 24 * DefaultStyle.dp + fillMode: Image.PreserveAspectFit + } + onClicked: { + var addr = modelData.core.remoteAddress + var addressEnd = "@sip.linphone.org" + if (!addr.endsWith(addressEnd)) addr += addressEnd + var callVarObject = UtilsCpp.createCall(addr) + } + } + } + MouseArea { + hoverEnabled: true + anchors.fill: parent + Rectangle { + anchors.fill: parent + opacity: 0.1 + color: DefaultStyle.main2_500main + visible: parent.containsMouse + } + onPressed: { + historyListView.currentIndex = model.index + } } - onPressed: listView.currentIndex = parent.index } + + onCountChanged: { + mainItem.showDefaultItem = historyListView.count === 0 && historyListView.visible + } + + onVisibleChanged: { + mainItem.showDefaultItem = historyListView.count === 0 && historyListView.visible + if (!visible) currentIndex = -1 + } + + Connections { + target: mainItem + onShowDefaultItemChanged: mainItem.showDefaultItem = mainItem.showDefaultItem && historyListView.count === 0 && historyListView.visible + onListViewUpdated: { + historyListView.model.updateView() + } + } + Control.ScrollBar.vertical: scrollbar } - - onCountChanged: mainItem.showDefaultItem = listView.count === 0 - - Connections { - target: mainItem - onShowDefaultItemChanged: mainItem.showDefaultItem = mainItem.showDefaultItem && listView.count === 0 - } - - Control.ScrollIndicator.vertical: Control.ScrollIndicator { } } } } + Control.ScrollBar { + id: scrollbar + active: true + Layout.fillHeight: true + } } } } @@ -223,7 +342,321 @@ AbstractMainPage { } } - rightPanelContent: ColumnLayout { + rightPanelContent: RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + visible: mainItem.selectedRowHistoryGui != undefined + Layout.topMargin: 45 * DefaultStyle.dp + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + ColumnLayout { + spacing: 30 * DefaultStyle.dp + Layout.fillWidth: true + Layout.fillHeight: true + Item { + Layout.preferredHeight: detailAvatar.height + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + Avatar { + id: detailAvatar + anchors.centerIn: parent + width: 100 * DefaultStyle.dp + height: 100 * DefaultStyle.dp + address: mainItem.selectedRowHistoryGui ? mainItem.selectedRowHistoryGui.core.remoteAddress : "" + } + PopupButton { + id: detailOptions + anchors.left: detailAvatar.right + anchors.leftMargin: 30 * DefaultStyle.dp + anchors.verticalCenter: parent.verticalCenter + popup.x: width + popup.contentItem: ColumnLayout { + Button { + background: Item {} + contentItem: IconLabel { + text: qsTr("Ajouter aux contacts") + iconSource: AppIcons.plusCircle + } + onClicked: console.debug("[CallPage.qml] TODO : add to contact") + } + Button { + background: Item {} + contentItem: IconLabel { + text: qsTr("Copier l'adresse SIP") + iconSource: AppIcons.copy + } + onClicked: console.debug("[CallPage.qml] TODO : copy SIP address") + } + Button { + background: Item {} + contentItem: IconLabel { + text: qsTr("Bloquer") + iconSource: AppIcons.empty + } + onClicked: console.debug("[CallPage.qml] TODO : block user") + } + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 2 * DefaultStyle.dp + color: DefaultStyle.main2_400 + } + Button { + background: Item {} + contentItem: IconLabel { + text: qsTr("Supprimer l'historique") + iconSource: AppIcons.trashCan + colorizationColor: DefaultStyle.danger_500main + } + onClicked: { + detailListView.model.removeEntriesWithFilter() + detailListView.currentIndex = -1 // reset index for ui + detailOptions.closePopup() + } + } + } + } + } + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Text { + property var remoteAddress: mainItem.selectedRowHistoryGui ? UtilsCpp.getDisplayName(mainItem.selectedRowHistoryGui.core.remoteAddress) : undefined + Layout.alignment: Qt.AlignHCenter + text: remoteAddress ? remoteAddress.value : "" + horizontalAlignment: Text.AlignHCenter + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + Text { + text: mainItem.selectedRowHistoryGui ? mainItem.selectedRowHistoryGui.core.remoteAddress : "" + horizontalAlignment: Text.AlignHCenter + font { + pixelSize: 12 * DefaultStyle.dp + weight: 300 * DefaultStyle.dp + } + } + Text { + // connection status + } + } + RowLayout { + spacing: 40 * DefaultStyle.dp + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + // Layout.fillHeight: true + Item {Layout.fillWidth: true} + LabelButton { + Layout.preferredWidth: 24 * DefaultStyle.dp//image.width + Layout.preferredHeight: image.height + image.source: AppIcons.phone + label: qsTr("Appel") + button.onClicked: { + var addr = mainItem.selectedRowHistoryGui.core.remoteAddress + var addressEnd = "@sip.linphone.org" + if (!addr.endsWith(addressEnd)) addr += addressEnd + UtilsCpp.createCall(addr) + } + } + LabelButton { + Layout.preferredWidth: image.width + Layout.preferredHeight: image.height + image.source: AppIcons.chatTeardropText + label: qsTr("Message") + button.onClicked: console.debug("[CallPage.qml] TODO : open conversation") + } + LabelButton { + Layout.preferredWidth: image.width + Layout.preferredHeight: image.height + image.source: AppIcons.videoCamera + label: qsTr("Appel Video") + button.onClicked: { + var addr = mainItem.selectedRowHistoryGui.core.remoteAddress + var addressEnd = "@sip.linphone.org" + if(!addr.endsWith(addressEnd)) addr += addressEnd + UtilsCpp.createCall(addr) + console.log("[CallPage.qml] TODO : enable video") + } + } + Item {Layout.fillWidth: true} + + } + + Control.Control { + id: detailControl + Layout.preferredWidth: detailListView.width + leftPadding + rightPadding + implicitHeight: 430 * DefaultStyle.dp + topPadding + bottomPadding + + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 30 * DefaultStyle.dp + + topPadding: 16 * DefaultStyle.dp + bottomPadding: 21 * DefaultStyle.dp + leftPadding: 17 * DefaultStyle.dp + rightPadding: 17 * DefaultStyle.dp + + background: Rectangle { + id: detailListBackground + width: parent.width + height: detailListView.height + color: DefaultStyle.grey_0 + radius: 15 * DefaultStyle.dp + } + ListView { + id: detailListView + width: 360 * DefaultStyle.dp + height: Math.min(detailControl.implicitHeight, contentHeight) + anchors.centerIn: detailListBackground + anchors.bottomMargin: 21 * DefaultStyle.dp + spacing: 10 * DefaultStyle.dp + clip: true + + onCountChanged: { + mainItem.listViewUpdated() + } + + model: CallHistoryProxy { + filterText: mainItem.selectedRowHistoryGui ? mainItem.selectedRowHistoryGui.core.remoteAddress : "" + } + delegate: Item { + width:detailListView.width + height: 56 * DefaultStyle.dp + // anchors.topMargin: 5 * DefaultStyle.dp + // anchors.bottomMargin: 5 * DefaultStyle.dp + RowLayout { + anchors.fill: parent + ColumnLayout { + Layout.alignment: Qt.AlignVCenter + RowLayout { + Image { + source: modelData.core.status === LinphoneEnums.CallStatus.Declined + || modelData.core.status === LinphoneEnums.CallStatus.DeclinedElsewhere + || modelData.core.status === LinphoneEnums.CallStatus.Aborted + || modelData.core.status === LinphoneEnums.CallStatus.EarlyAborted + ? modelData.core.isOutgoing + ? AppIcons.outgoingCallRejected + : AppIcons.incomingCallRejected + : modelData.core.status === LinphoneEnums.CallStatus.Missed + ? modelData.core.isOutgoing + ? AppIcons.outgoingCallMissed + : AppIcons.incomingCallMissed + : modelData.core.isOutgoing + ? AppIcons.outgoingCall + : AppIcons.incomingCall + Layout.preferredWidth: 6.67 * DefaultStyle.dp + Layout.preferredHeight: 6.67 * DefaultStyle.dp + sourceSize.width: 6.67 * DefaultStyle.dp + sourceSize.height: 6.67 * DefaultStyle.dp + } + Text { + Component.onCompleted: console.log("status", modelData.core.status) + text: modelData.core.status === LinphoneEnums.CallStatus.Missed + ? qsTr("Appel manqué") + : modelData.core.isOutgoing + ? qsTr("Appel sortant") + : qsTr("Appel entrant") + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + Text { + text: UtilsCpp.formatDate(modelData.core.date) + color: modelData.core.status === LinphoneEnums.CallStatus.Missed? DefaultStyle.danger_500main : DefaultStyle.main2_500main + font { + pixelSize: 12 * DefaultStyle.dp + weight: 300 * DefaultStyle.dp + } + } + } + Item { + Layout.fillWidth: true + } + Text { + text: UtilsCpp.formatElapsedTime(modelData.core.duration, false) + font { + pixelSize: 12 * DefaultStyle.dp + weight: 300 * DefaultStyle.dp + } + } + } + } + } + } + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + + component LabelButton: ColumnLayout { + id: labelButton + property alias image: buttonImg + property alias button: button + property string label + spacing: 8 * DefaultStyle.dp + Button { + id: button + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 56 * DefaultStyle.dp + Layout.preferredHeight: 56 * DefaultStyle.dp + topPadding: 16 * DefaultStyle.dp + bottomPadding: 16 * DefaultStyle.dp + leftPadding: 16 * DefaultStyle.dp + rightPadding: 16 * DefaultStyle.dp + background: Rectangle { + anchors.fill: parent + radius: 40 * DefaultStyle.dp + color: DefaultStyle.main2_200 + } + contentItem: Image { + id: buttonImg + source: labelButton.source + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + fillMode: Image.PreserveAspectFit + sourceSize.width: 24 * DefaultStyle.dp + sourceSize.height: 24 * DefaultStyle.dp + } + } + Text { + Layout.alignment: Qt.AlignHCenter + text: labelButton.label + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + + component IconLabel: RowLayout { + id: iconLabel + property string text + property string iconSource + property color colorizationColor: DefaultStyle.main2_500main + EffectImage { + image.source: iconLabel.iconSource + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + image.fillMode: Image.PreserveAspectFit + colorizationColor: iconLabel.colorizationColor + } + Text { + text: iconLabel.text + color: iconLabel.colorizationColor + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } } } diff --git a/Linphone/view/Style/AppIcons.qml b/Linphone/view/Style/AppIcons.qml index 7e257b839..832f49c69 100644 --- a/Linphone/view/Style/AppIcons.qml +++ b/Linphone/view/Style/AppIcons.qml @@ -46,6 +46,7 @@ QtObject { property string incomingCallRejected: "image://internal/incoming_call_rejected.svg" property string outgoingCall: "image://internal/outgoing_call.svg" property string outgoingCallMissed: "image://internal/outgoing_call_missed.svg" + property string outgoingCallRejected: "image://internal/outgoing_call_rejected.svg" property string microphone: "image://internal/microphone.svg" property string microphoneSlash: "image://internal/microphone-slash.svg" property string videoCamera: "image://internal/video-camera.svg" @@ -57,4 +58,7 @@ QtObject { property string pause: "image://internal/pause.svg" property string play: "image://internal/play.svg" property string smiley: "image://internal/smiley.svg" + property string trashCan: "image://internal/trash-simple.svg" + property string copy: "image://internal/copy.svg" + property string empty: "image://internal/empty.svg" }