Commit e5d9bf20 authored by Ronan's avatar Ronan

feat(app):

  - remove useless smart-search-bar from cpp
  - create a new `SipAddressesModel` component
  - create a `UnregisteredSipAddressesModel` (it uses the previous model)
  - `TimelineModel` uses too this model
  - many fixes and refactoring
  - `mapToSipAddress` moved from `ContactsListModel` to `SipAddressesModel`
  - ...
parent 758dd1c0
......@@ -67,9 +67,7 @@ set(SOURCES
src/components/settings/AccountSettingsModel.cpp
src/components/settings/SettingsModel.cpp
src/components/sip-addresses/SipAddressesModel.cpp
src/components/sip-addresses/SipAddressModel.cpp
src/components/smart-search-bar/SmartSearchBarModel.cpp
src/components/smart-search-bar/SmartSearchBarProxyModel.cpp
src/components/sip-addresses/UnregisteredSipAddressesModel.cpp
src/components/timeline/TimelineModel.cpp
src/main.cpp
)
......@@ -93,9 +91,7 @@ set(HEADERS
src/components/settings/AccountSettingsModel.hpp
src/components/settings/SettingsModel.hpp
src/components/sip-addresses/SipAddressesModel.hpp
src/components/sip-addresses/SipAddressModel.hpp
src/components/smart-search-bar/SmartSearchBarModel.hpp
src/components/smart-search-bar/SmartSearchBarProxyModel.hpp
src/components/sip-addresses/UnregisteredSipAddressesModel.hpp
src/components/timeline/TimelineModel.hpp
src/utils.hpp
)
......
......@@ -11,6 +11,7 @@
#include "../components/core/CoreManager.hpp"
#include "../components/notifier/Notifier.hpp"
#include "../components/settings/AccountSettingsModel.hpp"
#include "../components/sip-addresses/SipAddressesModel.hpp"
#include "../components/timeline/TimelineModel.hpp"
#include "App.hpp"
......@@ -66,6 +67,7 @@ void App::initContentApp () {
// Init core & contacts.
CoreManager::init();
ContactsListModel::init();
SipAddressesModel::init();
// Register types and load context properties.
registerTypes();
......@@ -120,6 +122,13 @@ void App::registerTypes () {
}
);
qmlRegisterSingletonType<SipAddressesModel>(
"Linphone", 1, 0, "SipAddressesModel",
[](QQmlEngine *, QJSEngine *) -> QObject *{
return SipAddressesModel::getInstance();
}
);
qmlRegisterSingletonType<AccountSettingsModel>(
"Linphone", 1, 0, "AccountSettingsModel",
[](QQmlEngine *, QJSEngine *) -> QObject *{
......@@ -144,6 +153,7 @@ void App::addContextProperties () {
qInfo() << "Adding context properties...";
QQmlContext *context = m_engine.rootContext();
// TODO: Avoid context properties. Use qmlRegister...
QQmlComponent component(&m_engine, QUrl(QML_VIEW_CALL_WINDOW));
if (component.isError()) {
qWarning() << component.errors();
......
......@@ -11,7 +11,7 @@ bool ChatModelFilter::filterAcceptsRow (int source_row, const QModelIndex &) con
return true;
QModelIndex index = sourceModel()->index(source_row, 0, QModelIndex());
const QVariantMap &data = qvariant_cast<QVariantMap>(index.data());
const QVariantMap &data = index.data().toMap();
return data["type"].toInt() == m_entry_type_filter;
}
......
......@@ -6,11 +6,8 @@ using namespace std;
// =============================================================================
const char *ContactModel::NAME = "contact-model";
ContactModel::ContactModel (shared_ptr<linphone::Friend> linphone_friend) {
m_linphone_friend = linphone_friend;
m_linphone_friend->setData(NAME, *this);
m_vcard = make_shared<VcardModel>(linphone_friend->getVcard());
App::getInstance()->getEngine()->setObjectOwnership(m_vcard.get(), QQmlEngine::CppOwnership);
......@@ -23,7 +20,6 @@ ContactModel::ContactModel (VcardModel *vcard) {
throw std::invalid_argument("A contact is already linked to this vcard.");
m_linphone_friend = linphone::Friend::newFromVcard(vcard->m_vcard);
m_linphone_friend->setData(NAME, *this);
m_vcard.reset(vcard);
engine->setObjectOwnership(vcard, QQmlEngine::CppOwnership);
......
#ifndef CONTACT_MODEL_H_
#define CONTACT_MODEL_H_
#include <linphone++/linphone.hh>
#include <QObject>
#include "../presence/Presence.hpp"
#include "VcardModel.hpp"
// =============================================================================
......@@ -25,7 +21,9 @@ public:
ContactModel (VcardModel *vcard);
~ContactModel () = default;
static const char *NAME;
std::shared_ptr<VcardModel> getVcardModel () const {
return m_vcard;
}
public slots:
void startEdit () {
......@@ -48,10 +46,6 @@ private:
Presence::PresenceStatus getPresenceStatus () const;
Presence::PresenceLevel getPresenceLevel () const;
std::shared_ptr<VcardModel> getVcardModel () const {
return m_vcard;
}
VcardModel *getVcardModelPtr () const {
return m_vcard.get();
}
......
......@@ -25,6 +25,7 @@ public:
~VcardModel ();
QString getUsername () const;
QVariantList getSipAddresses () const;
public slots:
bool addSipAddress (const QString &sip_address);
......@@ -58,7 +59,6 @@ private:
bool setAvatar (const QString &path);
QVariantMap getAddress () const;
QVariantList getSipAddresses () const;
QVariantList getCompanies () const;
QVariantList getEmails () const;
QVariantList getUrls () const;
......
......@@ -77,17 +77,6 @@ bool ContactsListModel::removeRows (int row, int count, const QModelIndex &paren
// -----------------------------------------------------------------------------
ContactModel *ContactsListModel::mapSipAddressToContact (const QString &sipAddress) const {
shared_ptr<linphone::Friend> friend_ = m_linphone_friends->findFriendByUri(
::Utils::qStringToLinphoneString(sipAddress)
);
if (!friend_)
return nullptr;
return &friend_->getData<ContactModel>(ContactModel::NAME);
}
ContactModel *ContactsListModel::addContact (VcardModel *vcard) {
ContactModel *contact = new ContactModel(vcard);
App::getInstance()->getEngine()->setObjectOwnership(contact, QQmlEngine::CppOwnership);
......
......@@ -12,6 +12,7 @@ class ContactsListModel : public QAbstractListModel {
Q_OBJECT;
friend class ContactsListProxyModel;
friend class SipAddressesModel;
public:
~ContactsListModel () = default;
......@@ -25,7 +26,7 @@ public:
bool removeRows (int row, int count, const QModelIndex &parent = QModelIndex()) override;
static void init () {
if (!ContactsListModel::m_instance) {
if (!m_instance) {
m_instance = new ContactsListModel();
}
}
......@@ -35,8 +36,6 @@ public:
}
public slots:
ContactModel *mapSipAddressToContact (const QString &sipAddress) const;
ContactModel *addContact (VcardModel *vcard);
void removeContact (ContactModel *contact);
......
......@@ -49,7 +49,7 @@ bool ContactsListProxyModel::filterAcceptsRow (
const QModelIndex &source_parent
) const {
QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
const ContactModel *contact = qvariant_cast<ContactModel *>(index.data());
const ContactModel *contact = index.data().value<ContactModel *>();
m_weights[contact] = static_cast<unsigned int>(computeContactWeight(*contact));
......@@ -60,8 +60,8 @@ bool ContactsListProxyModel::filterAcceptsRow (
}
bool ContactsListProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const {
const ContactModel *contact_a = qvariant_cast<ContactModel *>(sourceModel()->data(left));
const ContactModel *contact_b = qvariant_cast<ContactModel *>(sourceModel()->data(right));
const ContactModel *contact_a = sourceModel()->data(left).value<ContactModel *>();
const ContactModel *contact_b = sourceModel()->data(right).value<ContactModel *>();
unsigned int weight_a = m_weights[contact_a];
unsigned int weight_b = m_weights[contact_b];
......
#ifndef CORE_MANAGER_H_
#define CORE_MANAGER_H_
#include <QObject>
#include <linphone++/linphone.hh>
#include "../contact/VcardModel.hpp"
// =============================================================================
......@@ -30,7 +27,7 @@ public:
public slots:
// Must be used in a qml scene.
// The ownership of `VcardModel` is `QQmlEngine::JavaScriptOwnership` by default.
// Warning: The ownership of `VcardModel` is `QQmlEngine::JavaScriptOwnership` by default.
VcardModel *createDetachedVcardModel ();
private:
......
#include <QQmlComponent>
#include <QQuickWindow>
#include <QtDebug>
#include <QTimer>
......@@ -22,7 +23,7 @@
// =============================================================================
inline int getNotificationSize (const QObject &object, const char *property) {
QVariant variant(object.property(property));
QVariant variant = object.property(property);
bool so_far_so_good;
int size = variant.toInt(&so_far_so_good);
......@@ -56,7 +57,7 @@ Notifier::Notifier (QObject *parent) :
m_components[Notifier::Call] = new QQmlComponent(engine, QUrl(QML_NOTIFICATION_PATH));
// Check errors.
for (int i = 0; i < Notifier::MaxNbTypes; i++) {
for (int i = 0; i < Notifier::MaxNbTypes; ++i) {
QQmlComponent *component = m_components[i];
if (component->isError()) {
qWarning() << QStringLiteral("Errors found in `Notification` component %1:").arg(i) <<
......
......@@ -3,10 +3,11 @@
#include <QMutex>
#include <QObject>
#include <QQmlComponent>
// =============================================================================
class QQmlComponent;
class Notifier : public QObject {
Q_OBJECT;
......
#include "SipAddressModel.hpp"
// =============================================================================
#ifndef SIP_ADDRESS_MODEL_H_
#define SIP_ADDRESS_MODEL_H_
#include <QObject>
// =============================================================================
class SipAddressModel : public QObject {
Q_OBJECT;
public:
};
#endif // SIP_ADDRESS_H_
#include <QDateTime>
#include <QSet>
#include "../../utils.hpp"
#include "../contacts/ContactsListModel.hpp"
#include "../core/CoreManager.hpp"
#include "SipAddressesModel.hpp"
// =============================================================================
SipAddressesModel *SipAddressesModel::m_instance = nullptr;
SipAddressesModel::SipAddressesModel (QObject *parent) : QAbstractListModel(parent) {
shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
// Get sip addresses from chatrooms.
for (const auto &chat_room : core->getChatRooms()) {
list<shared_ptr<linphone::ChatMessage> > history = chat_room->getHistory(0);
if (history.size() == 0)
continue;
QString sip_address = ::Utils::linphoneStringToQString(chat_room->getPeerAddress()->asString());
QVariantMap map;
map["sipAddress"] = sip_address;
map["timestamp"] = QDateTime::fromMSecsSinceEpoch(static_cast<qint64>(history.back()->getTime()) * 1000);
m_sip_addresses[sip_address] = map;
}
// Get sip addresses from calls.
QSet<QString> address_done;
for (const auto &call_log : core->getCallLogs()) {
QString sip_address = ::Utils::linphoneStringToQString(call_log->getRemoteAddress()->asString());
if (address_done.contains(sip_address))
continue; // Already used.
address_done << sip_address;
QVariantMap map;
map["sipAddress"] = sip_address;
map["timestamp"] = QDateTime::fromMSecsSinceEpoch(
static_cast<qint64>(call_log->getStartDate() + call_log->getDuration()) * 1000
);
auto it = m_sip_addresses.find(sip_address);
if (it == m_sip_addresses.end() || map["timestamp"] > (*it)["timestamp"])
m_sip_addresses[sip_address] = map;
}
// Get sip addresses from contacts.
for (auto &contact : ContactsListModel::getInstance()->m_list) {
for (const auto &sip_address : contact->getVcardModel()->getSipAddresses()) {
auto it = m_sip_addresses.find(sip_address.toString());
if (it == m_sip_addresses.end()) {
QVariantMap map;
map["sipAddress"] = sip_address;
map["contact"] = QVariant::fromValue(contact);
m_sip_addresses[sip_address.toString()] = map;
} else
(*it)["contact"] = QVariant::fromValue(contact);
}
}
for (const auto &map : m_sip_addresses)
m_refs << &map;
}
// -----------------------------------------------------------------------------
int SipAddressesModel::rowCount (const QModelIndex &) const {
return m_refs.count();
}
QHash<int, QByteArray> SipAddressesModel::roleNames () const {
QHash<int, QByteArray> roles;
roles[Qt::DisplayRole] = "$sipAddress";
return roles;
}
QVariant SipAddressesModel::data (const QModelIndex &index, int role) const {
int row = index.row();
if (row < 0 || row >= m_refs.count())
return QVariant();
if (role == Qt::DisplayRole)
return QVariant::fromValue(*m_refs[row]);
return QVariant();
}
// ------------------------------------------------------------------------------
ContactModel *SipAddressesModel::mapSipAddressToContact (const QString &sip_address) const {
auto it = m_sip_addresses.find(sip_address);
if (it == m_sip_addresses.end())
return nullptr;
return it->value("contact").value<ContactModel *>();
}
......@@ -3,12 +3,40 @@
#include <QAbstractListModel>
#include "../contact/ContactModel.hpp"
// =============================================================================
class SipAddresses : public QAbstractListModel {
class SipAddressesModel : public QAbstractListModel {
Q_OBJECT;
public:
~SipAddressesModel () = default;
int rowCount (const QModelIndex &index = QModelIndex()) const override;
QHash<int, QByteArray> roleNames () const override;
QVariant data (const QModelIndex &index, int role) const override;
static void init () {
if (!m_instance)
m_instance = new SipAddressesModel();
}
static SipAddressesModel *getInstance () {
return m_instance;
}
public slots:
ContactModel *mapSipAddressToContact (const QString &sip_address) const;
private:
SipAddressesModel (QObject *parent = Q_NULLPTR);
QHash<QString, QVariantMap> m_sip_addresses;
QList<const QVariantMap *> m_refs;
static SipAddressesModel *m_instance;
};
#endif // SIP_ADDRESSES_MODEL_H_
#include "../sip-addresses/SipAddressesModel.hpp"
#include "UnregisteredSipAddressesModel.hpp"
// =============================================================================
UnregisteredSipAddressesModel::UnregisteredSipAddressesModel (QObject *parent) : QSortFilterProxyModel(parent) {
setSourceModel(SipAddressesModel::getInstance());
}
bool UnregisteredSipAddressesModel::filterAcceptsRow (int source_row, const QModelIndex &source_parent) const {
QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
return index.data().toMap().contains("contact");
}
#ifndef UNREGISTERED_SIP_ADDRESSES_MODEL_H_
#define UNREGISTERED_SIP_ADDRESSES_MODEL_H_
#include <QSortFilterProxyModel>
// =============================================================================
class UnregisteredSipAddressesModel : public QSortFilterProxyModel {
Q_OBJECT;
public:
UnregisteredSipAddressesModel (QObject *parent = Q_NULLPTR);
~UnregisteredSipAddressesModel () = default;
protected:
bool filterAcceptsRow (int source_row, const QModelIndex &source_parent) const override;
};
#endif // UNREGISTERED_SIP_ADDRESSES_MODEL_H_
#ifndef SMART_SEARCH_BAR_MODEL_H_
#define SMART_SEARCH_BAR_MODEL_H_
class SmartSearchBarModel {
};
#endif // SMART_SEARCH_BAR_MODEL_H_
#ifndef SMART_SEARCH_BAR_PROXY_MODEL_H_
#define SMART_SEARCH_BAR_PROXY_MODEL_H_
class SmartSearchBarProxyModel {
};
#endif // SMART_SEARCH_BAR_PROXY_MODEL_H_
#include <algorithm>
#include <linphone++/linphone.hh>
#include <QDateTime>
#include <QSet>
#include "../../utils.hpp"
#include "../contacts/ContactsListModel.hpp"
#include "../core/CoreManager.hpp"
#include "../sip-addresses/SipAddressesModel.hpp"
#include "TimelineModel.hpp"
using namespace std;
// ===================================================================
TimelineModel::TimelineModel (QObject *parent) : QAbstractListModel(parent) {
init_entries();
// Invalidate model if a contact is removed.
// Better than compare each sip address.
connect(
ContactsListModel::getInstance(), &ContactsListModel::rowsRemoved, this,
[this](const QModelIndex &, int, int) {
beginResetModel();
// Nothing.
endResetModel();
}
);
}
// =============================================================================
int TimelineModel::rowCount (const QModelIndex &) const {
return m_entries.count();
TimelineModel::TimelineModel (QObject *parent) : QSortFilterProxyModel(parent) {
setSourceModel(SipAddressesModel::getInstance());
sort(0);
}
QHash<int, QByteArray> TimelineModel::roleNames () const {
......@@ -39,96 +15,16 @@ QHash<int, QByteArray> TimelineModel::roleNames () const {
return roles;
}
QVariant TimelineModel::data (const QModelIndex &index, int role) const {
int row = index.row();
// -----------------------------------------------------------------------------
if (row < 0 || row >= m_entries.count())
return QVariant();
if (role == Qt::DisplayRole)
return m_entries[row];
return QVariant();
bool TimelineModel::filterAcceptsRow (int source_row, const QModelIndex &source_parent) const {
QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
return index.data().toMap().contains("timestamp");
}
// -------------------------------------------------------------------
void TimelineModel::init_entries () {
// Returns an iterator entry position to insert a new entry.
auto search_entry = [this](
const QVariantMap &map,
const QList<QMap<QString, QVariant> >::iterator *start = NULL
) {
return lower_bound(
start ? *start : m_entries.begin(), m_entries.end(), map,
[](const QVariantMap &a, const QVariantMap &b) {
return a["timestamp"] > b["timestamp"];
}
);
};
shared_ptr<linphone::Core> core(CoreManager::getInstance()->getCore());
// Insert chat rooms events.
for (const auto &chat_room : core->getChatRooms()) {
list<shared_ptr<linphone::ChatMessage> > history = chat_room->getHistory(0);
if (history.size() == 0)
continue;
// Last message must be at the end of history.
shared_ptr<linphone::ChatMessage> message = history.back();
// Insert event message in timeline entries.
QVariantMap map;
map["timestamp"] = QDateTime::fromMSecsSinceEpoch(
static_cast<qint64>(message->getTime()) * 1000
);
map["sipAddresses"] = ::Utils::linphoneStringToQString(
chat_room->getPeerAddress()->asString()
);
m_entries.insert(search_entry(map), map);
}
// Insert calls events.
QSet<QString> address_done;
for (const auto &call_log : core->getCallLogs()) {
// Get a sip uri to check.
QString address = ::Utils::linphoneStringToQString(
call_log->getRemoteAddress()->asString()
);
if (address_done.contains(address))
continue; // Already used.
address_done << address;
// Make a new map.
QVariantMap map;
map["timestamp"] = QDateTime::fromMSecsSinceEpoch(
static_cast<qint64>(call_log->getStartDate() + call_log->getDuration()) * 1000
);
map["sipAddresses"] = address;
// Search existing entry.
auto it = find_if(
m_entries.begin(), m_entries.end(), [&address](const QVariantMap &map) {
return address == map["sipAddresses"].toString();
}
);
// Is it a new entry?
if (it == m_entries.cend())
m_entries.insert(search_entry(map), map);
else if (map["timestamp"] > (*it)["timestamp"]) {
// Remove old entry and insert.
it = m_entries.erase(it);
if (it != m_entries.cbegin())
it--;
bool TimelineModel::lessThan (const QModelIndex &left, const QModelIndex &right) const {
const QVariantMap &sip_address_a = sourceModel()->data(left).toMap();
const QVariantMap &sip_address_b = sourceModel()->data(right).toMap();
m_entries.insert(search_entry(map, &it), map);
}
}
return sip_address_a["timestamp"] > sip_address_b["timestamp"];
}
#ifndef TIMELINE_MODEL_H_
#define TIMELINE_MODEL_H_
#include <QAbstractListModel>
class ContactsListModel;
#include <QSortFilterProxyModel>
// =============================================================================
class TimelineModel : public QAbstractListModel {
class TimelineModel : public QSortFilterProxyModel {
Q_OBJECT;
public:
TimelineModel (QObject *parent = Q_NULLPTR);
~TimelineModel () = default;
int rowCount (const QModelIndex &) const override;
QHash<int, QByteArray> roleNames () const override;
QVariant data (const QModelIndex &index, int role) const override;
private:
void init_entries ();
// A timeline enty is a object that contains:
// - A QDateTime `timestamp`.
// - A `sipAddresses` value, if it exists only one address, it's
// a string, otherwise it's a string array.
QList<QVariantMap> m_entries;
protected:
bool filterAcceptsRow (int source_row, const QModelIndex &source_parent) const override;
bool lessThan (const QModelIndex &left, const QModelIndex &right) const override;
};
#endif // TIMELINE_MODEL_H_
......@@ -11,7 +11,7 @@ import Linphone.Styles 1.0
ColumnLayout {
property alias proxyModel: chat.model
property var _contact: ContactsListModel.mapSipAddressToContact(
property var _contact: SipAddressesModel.mapSipAddressToContact(
proxyModel.sipAddress
) || proxyModel.sipAddress
......
......@@ -15,7 +15,7 @@ Rectangle {
property alias sipAddressColor: description.sipAddressColor
property alias usernameColor: description.usernameColor
property string sipAddress
property var _contact: ContactsListModel.mapSipAddressToContact(sipAddress)
property var _contact: SipAddressesModel.mapSipAddressToContact(sipAddress)
color: 'transparent' // No color by default.
height: ContactStyle.height
......
......@@ -66,17 +66,17 @@ ColumnLayout {
delegate: Item {
property var contact: {
Utils.assert(
!Utils.isArray($timelineEntry.sipAddresses),
!Utils.isArray($timelineEntry.sipAddress),
'Conferences are not supported at this moment.'
)
return ContactsListModel.mapSipAddressToContact(
$timelineEntry.sipAddresses
) || $timelineEntry.sipAddresses
return SipAddressesModel.mapSipAddressToContact(