Commit 05b99bf1 authored by Ronan's avatar Ronan
Browse files

feat(app): smart search can display contacts & unregistered contacts (unstable)

parent bed7cddf
......@@ -68,7 +68,8 @@ set(SOURCES
src/components/settings/SettingsModel.cpp
src/components/sip-addresses/SipAddressesModel.cpp
src/components/sip-addresses/UnregisteredSipAddressesModel.cpp
src/components/sip-addresses/UnregisteredSipAddressesProxyModel.hpp
src/components/sip-addresses/UnregisteredSipAddressesProxyModel.cpp
src/components/smart-search-bar/SmartSearchBarModel.cpp
src/components/timeline/TimelineModel.cpp
src/main.cpp
)
......@@ -94,6 +95,7 @@ set(HEADERS
src/components/sip-addresses/SipAddressesModel.hpp
src/components/sip-addresses/UnregisteredSipAddressesModel.hpp
src/components/sip-addresses/UnregisteredSipAddressesProxyModel.hpp
src/components/smart-search-bar/SmartSearchBarModel.hpp
src/components/timeline/TimelineModel.hpp
src/utils.hpp
)
......
......@@ -6,14 +6,13 @@
#include "../components/camera/Camera.hpp"
#include "../components/chat/ChatProxyModel.hpp"
#include "../components/contacts/ContactsListModel.hpp"
#include "../components/contacts/ContactsListProxyModel.hpp"
#include "../components/core/CoreManager.hpp"
#include "../components/notifier/Notifier.hpp"
#include "../components/settings/AccountSettingsModel.hpp"
#include "../components/sip-addresses/SipAddressesModel.hpp"
#include "../components/sip-addresses/UnregisteredSipAddressesProxyModel.hpp"
#include "../components/timeline/TimelineModel.hpp"
#include "../components/smart-search-bar/SmartSearchBarModel.hpp"
#include "App.hpp"
......@@ -140,6 +139,12 @@ void App::registerTypes () {
}
);
qmlRegisterSingletonType<SmartSearchBarModel>(
"Linphone", 1, 0, "SmartSearchBarModel",
[](QQmlEngine *, QJSEngine *) -> QObject *{
return new SmartSearchBarModel();
}
);
qmlRegisterType<Camera>("Linphone", 1, 0, "Camera");
qmlRegisterType<ContactsListProxyModel>("Linphone", 1, 0, "ContactsListProxyModel");
qmlRegisterType<ChatModel>("Linphone", 1, 0, "ChatModel");
......
......@@ -35,7 +35,6 @@ ContactsListProxyModel::ContactsListProxyModel (QObject *parent) : QSortFilterPr
m_list = CoreManager::getInstance()->getContactsListModel();
setSourceModel(m_list);
setFilterCaseSensitivity(Qt::CaseInsensitive);
setDynamicSortFilter(false);
for (const ContactModel *contact : m_list->m_list)
......@@ -88,12 +87,8 @@ float ContactsListProxyModel::computeStringWeight (const QString &string, float
if ((offset = tmp_offset) == 0) break;
}
// No weight.
if (offset == -1)
return 0;
// Weight & offset.
switch (offset) {
case -1: return 0;
case 0: return percentage * FACTOR_POS_0;
case 1: return percentage * FACTOR_POS_1;
case 2: return percentage * FACTOR_POS_2;
......
......@@ -25,7 +25,7 @@ public:
public slots:
void setFilter (const QString &pattern) {
setFilterFixedString(pattern);
invalidateFilter();
invalidate();
}
protected:
......
......@@ -10,5 +10,5 @@ UnregisteredSipAddressesModel::UnregisteredSipAddressesModel (QObject *parent) :
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");
return !index.data().toMap().contains("contact");
}
#include "../core/CoreManager.hpp"
#include "UnregisteredSipAddressesProxyModel.hpp"
#define WEIGHT_POS_0 5
#define WEIGHT_POS_1 4
#define WEIGHT_POS_2 3
#define WEIGHT_POS_3 2
#define WEIGHT_POS_OTHER 1
// =============================================================================
const QRegExp UnregisteredSipAddressesProxyModel::m_search_separators("^[^_.-;@ ][_.-;@ ]");
// -----------------------------------------------------------------------------
UnregisteredSipAddressesProxyModel::UnregisteredSipAddressesProxyModel (QObject *parent) :
QSortFilterProxyModel(parent) {
setSourceModel(CoreManager::getInstance()->getUnregisteredSipAddressesModel());
setDynamicSortFilter(false);
sort(0);
}
bool UnregisteredSipAddressesProxyModel::filterAcceptsRow (int source_row, const QModelIndex &source_parent) const {
QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
return computeStringWeight(index.data().toMap()["sipAddress"].toString()) > 0;
}
bool UnregisteredSipAddressesProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const {
return computeStringWeight(
sourceModel()->data(left).toMap()["sipAddress"].toString()
) > computeStringWeight(
sourceModel()->data(right).toMap()["sipAddress"].toString()
);
}
int UnregisteredSipAddressesProxyModel::computeStringWeight (const QString &string) const {
int index = -1;
int offset = -1;
while ((index = filterRegExp().indexIn(string, index + 1)) != -1) {
int tmp_offset = index - string.lastIndexOf(m_search_separators, index) - 1;
if ((tmp_offset != -1 && tmp_offset < offset) || offset == -1)
if ((offset = tmp_offset) == 0) break;
}
switch (offset) {
case -1: return 0;
case 0: return WEIGHT_POS_0;
case 1: return WEIGHT_POS_1;
case 2: return WEIGHT_POS_2;
case 3: return WEIGHT_POS_3;
default: break;
}
return WEIGHT_POS_OTHER;
}
......@@ -7,6 +7,25 @@
class UnregisteredSipAddressesProxyModel : public QSortFilterProxyModel {
Q_OBJECT;
public:
UnregisteredSipAddressesProxyModel (QObject *parent = Q_NULLPTR);
~UnregisteredSipAddressesProxyModel () = default;
public slots:
void setFilter (const QString &pattern) {
setFilterFixedString(pattern);
invalidate();
}
protected:
bool filterAcceptsRow (int source_row, const QModelIndex &source_parent) const override;
bool lessThan (const QModelIndex &left, const QModelIndex &right) const override;
private:
int computeStringWeight (const QString &string) const;
static const QRegExp m_search_separators;
};
#endif // UNREGISTERED_SIP_ADDRESSES_PROXY_MODEL_H_
#include "SmartSearchBarModel.hpp"
// =============================================================================
int SmartSearchBarModel::rowCount (const QModelIndex &) const {
return m_contacts.rowCount() + m_sip_addresses.rowCount();
}
QHash<int, QByteArray> SmartSearchBarModel::roleNames () const {
QHash<int, QByteArray> roles;
roles[Qt::DisplayRole] = "$entry";
return roles;
}
QVariant SmartSearchBarModel::data (const QModelIndex &index, int role) const {
int row = index.row();
int n_contacts = m_contacts.rowCount();
int n_sip_addresses = m_sip_addresses.rowCount();
if (row < 0 || row >= n_contacts + n_sip_addresses)
return QVariant();
if (role == Qt::DisplayRole) {
if (row < n_contacts)
return QVariant::fromValue(m_contacts.data(m_contacts.index(row, 0), role));
return QVariant::fromValue(m_sip_addresses.data(m_sip_addresses.index(row - n_contacts, 0), role));
}
return QVariant();
}
#ifndef SMART_SEARCH_BAR_MODEL_H_
#define SMART_SEARCH_BAR_MODEL_H_
#include <QAbstractListModel>
#include "../contacts/ContactsListProxyModel.hpp"
#include "../sip-addresses/UnregisteredSipAddressesProxyModel.hpp"
// =============================================================================
class SmartSearchBarModel : public QAbstractListModel {
Q_OBJECT;
public:
SmartSearchBarModel (QObject *parent = Q_NULLPTR) : QAbstractListModel(parent) {}
~SmartSearchBarModel () = default;
int rowCount (const QModelIndex &index = QModelIndex()) const override;
QHash<int, QByteArray> roleNames () const override;
QVariant data (const QModelIndex &index, int role) const override;
private:
ContactsListProxyModel m_contacts;
UnregisteredSipAddressesProxyModel m_sip_addresses;
};
#endif // SMART_SEARCH_BAR_MODEL_H_
......@@ -3,7 +3,7 @@
#include <QString>
// ===================================================================
// =============================================================================
namespace Utils {
inline QString linphoneStringToQString (const std::string &string) {
......
import Utils 1.0
// ===================================================================
// =============================================================================
// Menu which supports `ListView`.
// ===================================================================
// =============================================================================
AbstractDropDownMenu {
// Can be computed, but for performance usage, it must be given
......@@ -14,10 +14,7 @@ AbstractDropDownMenu {
var list = _content[0]
Utils.assert(list != null, 'No list found.')
Utils.assert(
Utils.qmlTypeof(list, 'QQuickListView'),
'No list view parameter.'
)
Utils.assert(Utils.qmlTypeof(list, 'QQuickListView'), 'No list view parameter.')
var height = list.count * entryHeight
......
......@@ -4,10 +4,10 @@ import Common 1.0
import Common.Styles 1.0
import Utils 1.0
// ===================================================================
// =============================================================================
// A reusable search input which display a entries model in a menu.
// Each entry can be filtered with the search input.
// ===================================================================
// =============================================================================
Item {
id: searchBox
......@@ -16,8 +16,7 @@ Item {
property alias entryHeight: menu.entryHeight
property alias maxMenuHeight: menu.maxMenuHeight
// This property must implement `setFilterFixedString` and/or
// `invalidate` functions.
// This property must implement `setFilter` function.
property alias model: list.model
property alias placeholderText: searchField.placeholderText
......@@ -27,7 +26,7 @@ Item {
signal menuClosed
signal menuOpened
// -----------------------------------------------------------------
// ---------------------------------------------------------------------------
function hideMenu () {
if (!_isOpen) {
......@@ -47,17 +46,14 @@ Item {
function _filter (text) {
Utils.assert(
model.setFilterFixedString != null,
'`model.setFilterFixedString` must be defined.'
model.setFilter != null,
'`model.setFilter` must be defined.'
)
model.setFilterFixedString(text)
if (model.invalidate) {
model.invalidate()
}
model.setFilter(text)
}
// -----------------------------------------------------------------
// ---------------------------------------------------------------------------
implicitHeight: searchField.height
......@@ -112,7 +108,7 @@ Item {
}
}
// -----------------------------------------------------------------
// ---------------------------------------------------------------------------
states: State {
name: 'opened'
......
import QtQuick 2.7
import QtQuick.Layouts 1.3
import Common 1.0
import Linphone 1.0
import LinphoneUtils 1.0
// ===================================================================
// =============================================================================
SearchBox {
id: searchBox
delegate: Contact {
// contact: $contact
delegate: Rectangle {
id: searchBoxEntry
width: parent.width
height: searchBox.entryHeight
color: '#FFFFFF'
Rectangle {
id: indicator
anchors.left: parent.left
color: 'transparent'
height: parent.height
width: 5
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
RowLayout {
anchors {
fill: parent
leftMargin: 22
rightMargin: 10
}
spacing: 15
// ---------------------------------------------------------------------
// Contact or address info
// ---------------------------------------------------------------------
Avatar {
id: avatar
Layout.preferredHeight: 30
Layout.preferredWidth: 30
image: $entry.vcard && $entry.vcard.avatar
presenceLevel: $entry.presenceLevel != null ? $entry.presenceLevel : -1
username: LinphoneUtils.getContactUsername($entry.sipAddress || $entry)
}
Text {
Layout.fillWidth: true
color: '#4B5964'
elide: Text.ElideRight
font {
bold: true
pointSize: 9
}
actions: [
ActionButton {
icon: 'call'
onClicked: CallsWindow.show()
},
text: $entry.vcard ? $entry.vcard.username : $entry.sipAddress
}
// ---------------------------------------------------------------------
// Actions
// ---------------------------------------------------------------------
ActionBar {
iconSize: 36
ActionButton {
icon: 'video_call'
onClicked: CallsWindow.show()
}
ActionButton {
icon: 'call'
onClicked: CallsWindow.show()
}
ActionButton {
icon: 'chat'
onClicked: window.setView('Conversation', {
sipAddress: $entry.sipAddress || $entry.vcard.sipAddresses[0] // FIXME: Display menu if many addresses.
})
}
}
}
}
// -------------------------------------------------------------------------
states: State {
when: mouseArea.containsMouse
PropertyChanges {
color: '#D0D8DE'
target: searchBoxEntry
}
ActionButton {
icon: 'video_call'
onClicked: CallsWindow.show()
PropertyChanges {
color: '#FF5E00'
target: indicator
}
]
}
}
}
......@@ -89,8 +89,7 @@ ApplicationWindow {
maxMenuHeight: MainWindowStyle.searchBox.maxHeight
placeholderText: qsTr('mainSearchBarPlaceholder')
contactsModel: ContactsListProxyModel {}
othersSipAddresses: UnregisteredSipAddressesProxyModel {}
model: SmartSearchBarModel
}
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment