Merge lp:~renatofilho/address-book-app/hide-favorite-if-empty into lp:address-book-app

Proposed by Renato Araujo Oliveira Filho
Status: Approved
Approved by: Gustavo Pichorim Boiko
Approved revision: 514
Proposed branch: lp:~renatofilho/address-book-app/hide-favorite-if-empty
Merge into: lp:address-book-app
Diff against target: 989 lines (+678/-78)
13 files modified
src/imports/ABContactEditorPage.qml (+0/-1)
src/imports/ABContactListPage.qml (+20/-1)
src/imports/Ubuntu/Contacts/CMakeLists.txt (+2/-0)
src/imports/Ubuntu/Contacts/ContactListView.qml (+11/-0)
src/imports/Ubuntu/Contacts/MostCalledList.qml (+5/-1)
src/imports/Ubuntu/Contacts/contactmap.cpp (+293/-0)
src/imports/Ubuntu/Contacts/contactmap.h (+96/-0)
src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp (+8/-69)
src/imports/Ubuntu/Contacts/mostcalledproxymodel.h (+0/-4)
src/imports/Ubuntu/Contacts/plugin.cpp (+2/-0)
tests/qml/CMakeLists.txt (+6/-0)
tests/qml/ContactUtil.js (+9/-2)
tests/qml/tst_ContactMap.qml (+226/-0)
To merge this branch: bzr merge lp:~renatofilho/address-book-app/hide-favorite-if-empty
Reviewer Review Type Date Requested Status
Gustavo Pichorim Boiko (community) Approve
PS Jenkins bot continuous-integration Needs Fixing
Review via email: mp+276126@code.launchpad.net

Commit message

Only show favorite sections if the list contains favorite contacts.

To post a comment you must log in.
494. By Renato Araujo Oliveira Filho

Only show header section if there is favorite contacts.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
495. By Renato Araujo Oliveira Filho

Trunk merged.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
496. By Renato Araujo Oliveira Filho

Implemented contacts map.
Used to map contacts in groups, necessary to show groups in the page header.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
497. By Renato Araujo Oliveira Filho

Update unit test to work with the new SDK API.

498. By Renato Araujo Oliveira Filho

Implemented unit test for ContactMap.

499. By Renato Araujo Oliveira Filho

Renamed ContactsMap to ContactMap.
[Header.sections] Reset selectedIndex to 0 if model changes, to avoid it to stay on 'Favorite' Section

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
500. By Renato Araujo Oliveira Filho

Notify hasFavorites changed only if it really changed.

501. By Renato Araujo Oliveira Filho

[ContactMap] Fetch contacts async.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
502. By Renato Araujo Oliveira Filho

[ContactMap] Fixed contact population.

503. By Renato Araujo Oliveira Filho

Trunk merged.

504. By Renato Araujo Oliveira Filho

Removed debug messages.
Fixed code style.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
505. By Renato Araujo Oliveira Filho

Full load contacts before populate contact map.

This is necessary to avoid problems if the contact map changes while it still loading.

506. By Renato Araujo Oliveira Filho

Does not request contact name on ContactMap.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
507. By Renato Araujo Oliveira Filho

Optimize contact map.

Fetch only favorite contacts on first query, to show the favorite information as soon as possible.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
508. By Renato Araujo Oliveira Filho

Trunk merged.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
509. By Renato Araujo Oliveira Filho

Revert changes on contact map to fetch favorites first.

510. By Renato Araujo Oliveira Filho

Async load contacts on ContactMap.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
511. By Renato Araujo Oliveira Filho

Updated mostcalledproxymodel to work with the new history API.

512. By Renato Araujo Oliveira Filho

Show favorite section if the most called model is not empty.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
513. By Renato Araujo Oliveira Filho

Trunk merged.

514. By Renato Araujo Oliveira Filho

Trunk merged.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Gustavo Pichorim Boiko (boiko) wrote :

Looks good!

review: Approve
515. By Renato Araujo Oliveira Filho

Trunk merged.

516. By Renato Araujo Oliveira Filho

Trunk merged.

517. By Renato Araujo Oliveira Filho

Revert wrong change on last commit.

518. By Renato Araujo Oliveira Filho

Show all contacts if the header model is reseted.

519. By Renato Araujo Oliveira Filho

Trunk merged.

Unmerged revisions

519. By Renato Araujo Oliveira Filho

Trunk merged.

518. By Renato Araujo Oliveira Filho

Show all contacts if the header model is reseted.

517. By Renato Araujo Oliveira Filho

Revert wrong change on last commit.

516. By Renato Araujo Oliveira Filho

Trunk merged.

515. By Renato Araujo Oliveira Filho

Trunk merged.

514. By Renato Araujo Oliveira Filho

Trunk merged.

513. By Renato Araujo Oliveira Filho

Trunk merged.

512. By Renato Araujo Oliveira Filho

Show favorite section if the most called model is not empty.

511. By Renato Araujo Oliveira Filho

Updated mostcalledproxymodel to work with the new history API.

510. By Renato Araujo Oliveira Filho

Async load contacts on ContactMap.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/imports/ABContactEditorPage.qml'
2--- src/imports/ABContactEditorPage.qml 2016-01-04 20:03:58 +0000
3+++ src/imports/ABContactEditorPage.qml 2016-05-16 15:05:43 +0000
4@@ -26,7 +26,6 @@
5 objectName: "contactEditorPage"
6
7 property alias backIconName: backAction.iconName
8- // Property used on unit tests
9 readonly property alias saveActionEnabled: saveAction.enabled
10
11 leadingActions: [
12
13=== modified file 'src/imports/ABContactListPage.qml'
14--- src/imports/ABContactListPage.qml 2016-03-21 12:33:51 +0000
15+++ src/imports/ABContactListPage.qml 2016-05-16 15:05:43 +0000
16@@ -214,6 +214,19 @@
17 }
18 }
19
20+ function filterContactsBySection() {
21+ switch (mainPage.head.sections.selectedIndex) {
22+ case 0:
23+ contactList.showAllContacts()
24+ break;
25+ case 1:
26+ contactList.showFavoritesContacts()
27+ break;
28+ default:
29+ break;
30+ }
31+ }
32+
33 // Delay contact fetch for some msecs (check 'fetchNewContactTimer')
34 function delayFetchContact()
35 {
36@@ -263,6 +276,7 @@
37 }
38 onSelectedIndexChanged: {
39 switch (selectedIndex) {
40+ case -1:
41 case 0:
42 contactList.showAllContacts()
43 break;
44@@ -474,7 +488,8 @@
45 target: pageHeader
46
47 // TRANSLATORS: this refers to all contacts
48- sectionsModel: [i18n.tr("All"), i18n.tr("Favorites")]
49+ sectionsModel: contactMap.hasFavorites || (contactList.mostCalledCount > 0) ?
50+ [ i18n.tr("All"), i18n.tr("Favorites") ] : []
51 leadingActions: defaultState.leadingActions
52 trailingActions: defaultState.trailingActions
53 }
54@@ -823,6 +838,10 @@
55 when: bottomEdgeLoader.status == Loader.Ready
56 }
57
58+ ContactsUI.ContactMap {
59+ id: contactMap
60+ }
61+
62 Connections {
63 target: mainPage.contactModel
64
65
66=== modified file 'src/imports/Ubuntu/Contacts/CMakeLists.txt'
67--- src/imports/Ubuntu/Contacts/CMakeLists.txt 2016-01-22 14:51:39 +0000
68+++ src/imports/Ubuntu/Contacts/CMakeLists.txt 2016-05-16 15:05:43 +0000
69@@ -38,6 +38,8 @@
70 set(CONTACT_COMPONENTS_SRC
71 contacts.h
72 contacts.cpp
73+ contactmap.h
74+ contactmap.cpp
75 imagescalethread.h
76 imagescalethread.cpp
77 mostcalledproxymodel.h
78
79=== modified file 'src/imports/Ubuntu/Contacts/ContactListView.qml'
80--- src/imports/Ubuntu/Contacts/ContactListView.qml 2016-03-21 12:32:15 +0000
81+++ src/imports/Ubuntu/Contacts/ContactListView.qml 2016-05-16 15:05:43 +0000
82@@ -247,6 +247,10 @@
83
84 property alias highlightSelected: view.highlightSelected
85
86+ property alias mostCalledCount: view.mostCalledCount
87+
88+ readonly property bool favouritesIsSelected: view.favouritesIsSelected
89+
90 property var _busyDialog: null
91
92 //WORKAROUND: SDK does not allow us to disable focus for items due bug: #1514822
93@@ -380,6 +384,7 @@
94 property bool showFavourites: true
95 property alias favouritesIsSelected: contactsModel.onlyFavorites
96 property bool contactsLoaded: false
97+ property int mostCalledCount: 0
98
99 function getSectionText(index) {
100 var tag = listModel.contacts[index].tag.tag
101@@ -553,6 +558,12 @@
102 parentView: view
103 visible: view.favouritesIsSelected
104 height: visible && (count > 0) ? childrenRect.height : 0
105+
106+ Binding {
107+ target: view
108+ property: "mostCalledCount"
109+ value: mostCalledView.count
110+ }
111 }
112 }
113 onError: root.error(message)
114
115=== modified file 'src/imports/Ubuntu/Contacts/MostCalledList.qml'
116--- src/imports/Ubuntu/Contacts/MostCalledList.qml 2015-10-26 13:18:11 +0000
117+++ src/imports/Ubuntu/Contacts/MostCalledList.qml 2016-05-16 15:05:43 +0000
118@@ -25,6 +25,10 @@
119 /* internal */
120 property int _nextCurrentIndex: -1
121
122+ function update()
123+ {
124+ calledModel.model.update()
125+ }
126
127 function makeItemVisible(item)
128 {
129@@ -113,7 +117,7 @@
130 // update the model every time that it became visible
131 // in fact calling update only reloads the model data if it has changed
132 if (visible) {
133- calledModel.model.update()
134+ root.update()
135 }
136 }
137 }
138
139=== added file 'src/imports/Ubuntu/Contacts/contactmap.cpp'
140--- src/imports/Ubuntu/Contacts/contactmap.cpp 1970-01-01 00:00:00 +0000
141+++ src/imports/Ubuntu/Contacts/contactmap.cpp 2016-05-16 15:05:43 +0000
142@@ -0,0 +1,293 @@
143+#include "contactmap.h"
144+
145+#include <QtContacts/QContactName>
146+#include <QtContacts/QContactFavorite>
147+#include <QtContacts/QContactTag>
148+#include <QtContacts/QContactFetchRequest>
149+#include <QtContacts/QContactDetailFilter>
150+#include <QDebug>
151+
152+
153+#define FAVORITE_GROUP_NAME QStringLiteral("favorite")
154+
155+ContactMap::ContactMap(QObject *parent)
156+ : QObject(parent),
157+ m_loadTags(false),
158+ m_currentFetch(0)
159+{
160+}
161+
162+ContactMap::~ContactMap()
163+{
164+ if (m_currentFetch) {
165+ m_currentFetch->disconnect(this);
166+ m_currentFetch->cancel();
167+ m_currentFetch->deleteLater();
168+ }
169+}
170+
171+// The manager paramentar can contains a dict that can be passsed to contact managar
172+// supported format is: managername[:<paramenter0-name>=<paramenter0-value>;<paramenter1-name>=<paramenter1-value>;...]
173+void ContactMap::setManager(const QString &manager)
174+{
175+ QStringList paramenters = manager.split(":", QString::SkipEmptyParts);
176+ QString managerName = paramenters.takeFirst();
177+ qDebug() << "Loading map from" << managerName;
178+
179+ QMap<QString, QString> paramenterMap;
180+ while (!paramenters.isEmpty()) {
181+ QStringList param = paramenters.takeFirst().split("=");
182+ paramenterMap.insert(param.first(), param.last());
183+ }
184+
185+ m_contactManager.reset(new QContactManager(managerName, paramenterMap));
186+ m_fetchHint.setDetailTypesHint(QList<QContactDetail::DetailType>()
187+ << QContactFavorite::Type);
188+
189+ connect(m_contactManager.data(),
190+ SIGNAL(contactsAdded(QList<QContactId>)),
191+ SLOT(onContactsAdded(QList<QContactId>)));
192+ connect(m_contactManager.data(),
193+ SIGNAL(contactsRemoved(QList<QContactId>)),
194+ SLOT(onContactsRemoved(QList<QContactId>)));
195+ connect(m_contactManager.data(),
196+ SIGNAL(contactsChanged(QList<QContactId>)),
197+ SLOT(onContactsChanged(QList<QContactId>)));
198+ connect(m_contactManager.data(),
199+ SIGNAL(dataChanged()),
200+ SLOT(onDataChanged()));
201+ reload();
202+ Q_EMIT managerChanged();
203+}
204+
205+QString ContactMap::manager() const
206+{
207+ if (m_contactManager)
208+ return m_contactManager->managerName();
209+ else
210+ return QString();
211+}
212+
213+QStringList ContactMap::groupNames() const
214+{
215+ return m_groupMap.keys();
216+}
217+
218+QVariantMap ContactMap::groupsMap() const
219+{
220+ QVariantMap result;
221+ Q_FOREACH(const QString &key, m_groupMap.keys())
222+ result.insert(key, m_groupMap[key].size());
223+
224+ return result;
225+}
226+
227+QVariantMap ContactMap::managerParameters() const
228+{
229+ QVariantMap map;
230+
231+ if (m_contactManager) {
232+ QMap<QString, QString> parameters = m_contactManager->managerParameters();
233+ Q_FOREACH(const QString &key, parameters.keys())
234+ map.insert(key, parameters[key]);
235+ }
236+ return map;
237+}
238+
239+bool ContactMap::hasFavorites() const
240+{
241+ return (m_groupMap.contains(FAVORITE_GROUP_NAME) && !m_groupMap.value(FAVORITE_GROUP_NAME).isEmpty());
242+}
243+
244+void ContactMap::setLoadTags(bool flag)
245+{
246+ if (m_loadTags != flag) {
247+ m_loadTags = flag;
248+ Q_EMIT loadTagsChanged();
249+ }
250+}
251+
252+bool ContactMap::loadTags() const
253+{
254+ return m_loadTags;
255+}
256+
257+void ContactMap::classBegin()
258+{
259+}
260+
261+void ContactMap::componentComplete()
262+{
263+ if (!m_contactManager) {
264+ QByteArray defaultManager("galera");
265+ if (qEnvironmentVariableIsSet("QTCONTACTS_MANAGER_OVERRIDE"))
266+ defaultManager = qgetenv("QTCONTACTS_MANAGER_OVERRIDE");
267+
268+ setManager(defaultManager);
269+ }
270+}
271+
272+void ContactMap::onContactsAdded(const QList<QContactId> addedContacts)
273+{
274+ if (m_currentFetch) {
275+ m_pendingActions.append(qMakePair<>(ContactMap::ActionAdd, addedContacts));
276+ return;
277+ }
278+
279+ QList<QContact> contacts = m_contactManager->contacts(addedContacts, m_fetchHint);
280+ if (insertContacts(contacts))
281+ Q_EMIT groupsMapChanged();
282+}
283+
284+void ContactMap::onContactsRemoved(const QList<QContactId> removedContacts)
285+{
286+ if (m_currentFetch) {
287+ m_pendingActions.append(qMakePair<>(ContactMap::ActionRemoved, removedContacts));
288+ return;
289+ }
290+
291+ bool changed = false;
292+ bool hasFavoritesBefore = hasFavorites();
293+ int groupSize = m_groupMap.size();
294+
295+ QStringList emptyKeys;
296+ Q_FOREACH(const QString &key, m_groupMap.keys()) {
297+ QList<QContactId> &ids = m_groupMap[key];
298+ Q_FOREACH(const QContactId &id, removedContacts) {
299+ if (ids.removeOne(id))
300+ changed = true;
301+ }
302+ if (ids.isEmpty())
303+ emptyKeys << key;
304+ }
305+
306+ Q_FOREACH(const QString &key, emptyKeys)
307+ m_groupMap.remove(key);
308+
309+ if (groupSize != m_groupMap.size())
310+ Q_EMIT groupNamesChanged();
311+
312+ if (hasFavoritesBefore != hasFavorites())
313+ Q_EMIT hasFavoritesChanged();
314+
315+ if (changed)
316+ Q_EMIT groupsMapChanged();
317+}
318+
319+void ContactMap::onContactsChanged(const QList<QContactId> changedContacts)
320+{
321+ if (m_currentFetch) {
322+ m_pendingActions.append(qMakePair<>(ContactMap::ActionRemoved, changedContacts));
323+ return;
324+ }
325+
326+ onContactsRemoved(changedContacts);
327+ onContactsAdded(changedContacts);
328+}
329+
330+void ContactMap::onDataChanged()
331+{
332+ reload();
333+}
334+
335+bool ContactMap::insertContacts(const QList<QContact> &contacts)
336+{
337+ bool changed = false;
338+ bool hasFavoritesBefore = hasFavorites();
339+ int groupSize = m_groupMap.size();
340+
341+ Q_FOREACH(const QContact &contact, contacts) {
342+ if (m_loadTags) {
343+ Q_FOREACH(const QContactTag &tag, contact.details<QContactTag>()) {
344+ bool inserted = insertTag(contact.id(), tag.tag());
345+ changed = changed || inserted;
346+ }
347+ }
348+
349+ bool isFavorite = contact.detail<QContactFavorite>().isFavorite();
350+ if (isFavorite) {
351+ bool inserted = insertTag(contact.id() , FAVORITE_GROUP_NAME);
352+ changed = changed || inserted;
353+ }
354+ }
355+
356+ if (groupSize != m_groupMap.size())
357+ Q_EMIT groupNamesChanged();
358+
359+ if (hasFavoritesBefore != hasFavorites())
360+ Q_EMIT hasFavoritesChanged();
361+
362+ return changed;
363+}
364+
365+bool ContactMap::insertTag(const QContactId &id, const QString &tag)
366+{
367+ QList<QContactId> tags = m_groupMap.value(tag);
368+ if (!tags.contains(id)) {
369+ tags << id;
370+ m_groupMap[tag] = tags;
371+ return true;
372+ }
373+ return false;
374+}
375+
376+void ContactMap::reload()
377+{
378+ if (m_currentFetch) {
379+ m_currentFetch->disconnect(this);
380+ m_currentFetch->cancel();
381+ m_currentFetch->deleteLater();
382+ }
383+
384+ if (!m_groupMap.isEmpty()) {
385+ bool hasFavoritesBefore = hasFavorites();
386+ m_groupMap.clear();
387+
388+ Q_EMIT groupNamesChanged();
389+ Q_EMIT groupsMapChanged();
390+ if (hasFavoritesBefore)
391+ Q_EMIT hasFavoritesChanged();
392+ }
393+
394+ m_currentFetch = new QContactFetchRequest(this);
395+ m_currentFetch->setManager(m_contactManager.data());
396+ m_currentFetch->setFetchHint(m_fetchHint);
397+ connect(m_currentFetch,
398+ SIGNAL(resultsAvailable()),
399+ SLOT(onFetchResultsAvailable()));
400+ connect(m_currentFetch,
401+ SIGNAL(stateChanged(QContactAbstractRequest::State)),
402+ SLOT(onFetchStateChanged(QContactAbstractRequest::State)));
403+ m_currentFetch->start();
404+}
405+
406+void ContactMap::onFetchStateChanged(QContactAbstractRequest::State state)
407+{
408+ if (state == QContactAbstractRequest::FinishedState) {
409+ m_currentFetch->deleteLater();
410+ m_currentFetch = 0;
411+
412+ while(!m_pendingActions.isEmpty()) {
413+ PendingAction action = m_pendingActions.dequeue();
414+
415+ switch(action.first) {
416+ case ContactMap::ActionAdd:
417+ onContactsAdded(action.second);
418+ break;
419+ case ContactMap::ActionRemoved:
420+ onContactsRemoved(action.second);
421+ break;
422+ case ContactMap::ActionChanged:
423+ onContactsChanged(action.second);
424+ break;
425+ }
426+ }
427+ }
428+}
429+
430+void ContactMap::onFetchResultsAvailable()
431+{
432+ bool changed = insertContacts(m_currentFetch->contacts());
433+ if (changed)
434+ Q_EMIT groupsMapChanged();
435+}
436
437=== added file 'src/imports/Ubuntu/Contacts/contactmap.h'
438--- src/imports/Ubuntu/Contacts/contactmap.h 1970-01-01 00:00:00 +0000
439+++ src/imports/Ubuntu/Contacts/contactmap.h 2016-05-16 15:05:43 +0000
440@@ -0,0 +1,96 @@
441+/*
442+ * Copyright (C) 2015 Canonical, Ltd.
443+ *
444+ * This program is free software; you can redistribute it and/or modify
445+ * it under the terms of the GNU General Public License as published by
446+ * the Free Software Foundation; version 3.
447+ *
448+ * This program is distributed in the hope that it will be useful,
449+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
450+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
451+ * GNU General Public License for more details.
452+ *
453+ * You should have received a copy of the GNU General Public License
454+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
455+ */
456+
457+#ifndef _UBUNTU_CONTACT_MAP_
458+#define _UBUNTU_CONTACT_MAP_
459+
460+#include <QtCore/QObject>
461+#include <QtCore/QString>
462+#include <QtCore/QMap>
463+#include <QtCore/QScopedPointer>
464+#include <QtCore/QQueue>
465+
466+#include <QtQml/QQmlParserStatus>
467+
468+#include <QtContacts/QContactManager>
469+#include <QtContacts/QContactFetchRequest>
470+
471+using namespace QtContacts;
472+
473+class ContactMap : public QObject, public QQmlParserStatus
474+{
475+ Q_OBJECT
476+ Q_INTERFACES(QQmlParserStatus)
477+ Q_PROPERTY(QStringList groupNames READ groupNames NOTIFY groupNamesChanged)
478+ Q_PROPERTY(QVariantMap groupsMap READ groupsMap NOTIFY groupsMapChanged)
479+ Q_PROPERTY(bool hasFavorites READ hasFavorites NOTIFY hasFavoritesChanged)
480+ Q_PROPERTY(QString manager READ manager WRITE setManager NOTIFY managerChanged)
481+ Q_PROPERTY(QVariantMap managerParameters READ managerParameters)
482+ Q_PROPERTY(bool loadTags READ loadTags WRITE setLoadTags NOTIFY loadTagsChanged)
483+
484+public:
485+ ContactMap(QObject *parent = 0);
486+ ~ContactMap();
487+
488+ void setManager(const QString &manager);
489+ QString manager() const;
490+ QStringList groupNames() const;
491+ QVariantMap groupsMap() const;
492+ QVariantMap managerParameters() const;
493+ bool hasFavorites() const;
494+ void setLoadTags(bool flag);
495+ bool loadTags() const;
496+
497+ void classBegin();
498+ void componentComplete();
499+
500+Q_SIGNALS:
501+ void groupNamesChanged();
502+ void groupsMapChanged();
503+ void managerChanged();
504+ void loadTagsChanged();
505+ void hasFavoritesChanged();
506+
507+private Q_SLOTS:
508+ void onContactsAdded(const QList<QContactId> addedContacts);
509+ void onContactsRemoved(const QList<QContactId> removedContacts);
510+ void onContactsChanged(const QList<QContactId> changedContacts);
511+ void onDataChanged();
512+ void onFetchResultsAvailable();
513+ void onFetchStateChanged(QContactAbstractRequest::State state);
514+
515+private:
516+ enum ContactAction {
517+ ActionAdd,
518+ ActionRemoved,
519+ ActionChanged
520+ };
521+ typedef QPair<ContactAction, QList<QtContacts::QContactId> > PendingAction;
522+ typedef QQueue<PendingAction> PendingActionQueue;
523+
524+ bool m_loadTags;
525+ QtContacts::QContactFetchHint m_fetchHint;
526+ QMap<QString, QList<QtContacts::QContactId> > m_groupMap;
527+ QScopedPointer<QtContacts::QContactManager> m_contactManager;
528+ PendingActionQueue m_pendingActions;
529+ QtContacts::QContactFetchRequest *m_currentFetch;
530+
531+ bool insertContacts(const QList<QtContacts::QContact> &contacts);
532+ bool insertTag(const QtContacts::QContactId &id, const QString &tag);
533+ void reload();
534+};
535+
536+#endif
537
538=== modified file 'src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp'
539--- src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp 2015-05-07 17:27:16 +0000
540+++ src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp 2016-05-16 15:05:43 +0000
541@@ -49,7 +49,6 @@
542 MostCalledContactsModel::~MostCalledContactsModel()
543 {
544 m_aboutToQuit = true;
545- m_phones.clear();
546
547 if (m_currentFetch) {
548 m_currentFetch->cancel();
549@@ -195,8 +194,6 @@
550 }
551
552 m_totalCalls = 0;
553- m_phones.clear();
554- m_phoneToContactCache.clear();
555 m_contactsData.clear();
556 queryContacts();
557 }
558@@ -227,8 +224,13 @@
559
560 QVariant participants = getSourceData(row, participantsRole);
561 if (participants.isValid()) {
562- Q_FOREACH(const QString phone, participants.toStringList()) {
563- m_phones << phone;
564+ Q_FOREACH(const QVariant participantI, participants.toList()) {
565+ QVariantMap participant = participantI.toMap();
566+ QString contactId = participant.value("contactId").toString();
567+ if (!contactId.isEmpty()) {
568+ QString phone = participant.value("identifier").toString();
569+ registerCall(phone, contactId);
570+ }
571 }
572 }
573
574@@ -236,70 +238,11 @@
575 row++;
576 }
577
578- // query for all phones
579- nextContact();
580-}
581-
582-void MostCalledContactsModel::nextContact()
583-{
584- if (m_phones.isEmpty()) {
585- parseResult();
586- return;
587- }
588-
589- QString nextPhone = m_phones.takeFirst();
590-
591- if (m_phoneToContactCache.contains(nextPhone)) {
592- registerCall(nextPhone, m_phoneToContactCache.value(nextPhone));
593- nextContact();
594- return;
595- } else {
596- QContactFilter filter(QContactPhoneNumber::match(nextPhone));
597- QContactFetchHint hint;
598- hint.setDetailTypesHint(QList<QContactDetail::DetailType>() << QContactDetail::TypeGuid);
599-
600- m_currentFetch = new QContactFetchRequest;
601- m_currentFetch->setProperty("PHONE", nextPhone);
602- m_currentFetch->setFilter(filter);
603- m_currentFetch->setFetchHint(hint);
604- m_currentFetch->setManager(m_manager.data());
605-
606- connect(m_currentFetch,
607- SIGNAL(stateChanged(QContactAbstractRequest::State)),
608- SLOT(fetchContactIdDone()));
609-
610- m_currentFetch->start();
611- }
612-}
613-
614-void MostCalledContactsModel::fetchContactIdDone()
615-{
616- Q_ASSERT(m_currentFetch);
617-
618- if (m_aboutToQuit) {
619- m_currentFetch->deleteLater();
620- m_currentFetch = 0;
621- return;
622- }
623-
624- if (m_currentFetch->state() == QContactAbstractRequest::ActiveState) {
625- return;
626- }
627-
628- if (!m_currentFetch->contacts().isEmpty()) {
629- QString id = m_currentFetch->contacts().at(0).id().toString();
630- registerCall(m_currentFetch->property("PHONE").toString(), id);
631- }
632- m_currentFetch->deleteLater();
633- m_currentFetch = 0;
634- nextContact();
635+ parseResult();
636 }
637
638 void MostCalledContactsModel::registerCall(const QString &phone, const QString &contactId)
639 {
640- // update cache
641- m_phoneToContactCache.insert(phone, contactId);
642-
643 if (m_contactsData.contains(contactId)) {
644 MostCalledContactsModelData &data = m_contactsData[contactId];
645 data.callCount++;
646@@ -344,8 +287,6 @@
647 }
648
649 m_totalCalls = 0;
650- m_phones.clear();
651- m_phoneToContactCache.clear();
652 m_contactsData.clear();
653
654 Q_EMIT endResetModel();
655@@ -367,5 +308,3 @@
656 Q_EMIT outdatedChange(m_outdated);
657 }
658 }
659-
660-
661
662=== modified file 'src/imports/Ubuntu/Contacts/mostcalledproxymodel.h'
663--- src/imports/Ubuntu/Contacts/mostcalledproxymodel.h 2015-05-07 17:27:16 +0000
664+++ src/imports/Ubuntu/Contacts/mostcalledproxymodel.h 2016-05-16 15:05:43 +0000
665@@ -80,7 +80,6 @@
666
667 private Q_SLOTS:
668 void markAsOutdated();
669- void fetchContactIdDone();
670
671 private:
672 QAbstractItemModel *m_sourceModel;
673@@ -95,12 +94,9 @@
674 bool m_reloadingModel;
675 bool m_aboutToQuit;
676
677- QStringList m_phones;
678- QMap<QString, QString> m_phoneToContactCache;
679 QMap<QString, MostCalledContactsModelData > m_contactsData;
680 int m_totalCalls;
681
682- void fetchContactId(const QString &phoneNumber);
683 QVariant getSourceData(int row, int role);
684 void queryContacts();
685 void nextContact();
686
687=== modified file 'src/imports/Ubuntu/Contacts/plugin.cpp'
688--- src/imports/Ubuntu/Contacts/plugin.cpp 2015-05-07 17:27:16 +0000
689+++ src/imports/Ubuntu/Contacts/plugin.cpp 2016-05-16 15:05:43 +0000
690@@ -18,6 +18,7 @@
691 #include "mostcalledproxymodel.h"
692 #include "contacts.h"
693 #include "simcardcontacts.h"
694+#include "contactmap.h"
695
696 #include <QQmlEngine>
697 #include <qqml.h>
698@@ -41,4 +42,5 @@
699 qmlRegisterSingletonType<UbuntuContacts>(uri, 0, 1, "Contacts", contactsProvider);
700 qmlRegisterType<MostCalledContactsModel>(uri, 0, 1, "MostCalledContactsModel");
701 qmlRegisterType<SimCardContacts>(uri, 0, 1, "SimCardContacts");
702+ qmlRegisterType<ContactMap>(uri, 0, 1, "ContactMap");
703 }
704
705=== modified file 'tests/qml/CMakeLists.txt'
706--- tests/qml/CMakeLists.txt 2015-06-29 15:35:07 +0000
707+++ tests/qml/CMakeLists.txt 2016-05-16 15:05:43 +0000
708@@ -18,6 +18,10 @@
709 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
710 COMMAND ${COMMAND_PREFIX} ${QMLTESTRUNNER_BIN} -import ${imports_BINARY_DIR} -input ${CMAKE_CURRENT_SOURCE_DIR}/${TST_QML_FILE}
711 )
712+ set(TEST_ENVIRONMENT )
713+ set_tests_properties(${TST_NAME} PROPERTIES
714+ ENVIRONMENT "QTCONTACTS_MANAGER_OVERRIDE=memory")
715+
716 endmacro()
717
718 if(QMLTESTRUNNER_BIN AND XVFB_RUN_BIN)
719@@ -30,6 +34,7 @@
720 declare_qml_test("contact_preview_page" tst_ContactPreviewPage.qml)
721 declare_qml_test("vcard_parser" tst_VCardParser.qml)
722 declare_qml_test("ubuntu_contact" tst_UbuntuContacts.qml)
723+ declare_qml_test("contact_map" tst_ContactMap.qml)
724 else()
725 if (NOT QMLTESTRUNNER_BIN)
726 message(WARNING "Qml tests disabled: qmltestrunner not found")
727@@ -45,6 +50,7 @@
728 tst_ContactList.qml
729 tst_ContactListModel.qml
730 tst_ContactListView.qml
731+ tst_ContactMap.qml
732 tst_ListWithActions.qml
733 tst_ContactPreviewPage.qml
734 tst_VCardParser.qml
735
736=== modified file 'tests/qml/ContactUtil.js'
737--- tests/qml/ContactUtil.js 2014-07-05 22:00:45 +0000
738+++ tests/qml/ContactUtil.js 2016-05-16 15:05:43 +0000
739@@ -18,10 +18,17 @@
740 var newContact = Qt.createQmlObject(
741 'import QtContacts 5.0; Contact{ }', parent);
742 var detailSourceTemplate = 'import QtContacts 5.0; %1{ %2: "%3" }';
743+ var detailNonStringSourceTemplate = 'import QtContacts 5.0; %1{ %2: (%3 == 1) }';
744 for (var i=0; i < detailsMap.length; i++) {
745 var detailMetaData = detailsMap[i];
746- var template = detailSourceTemplate.arg(detailMetaData.detail).arg(
747- detailMetaData.field).arg(detailMetaData.value);
748+ var template;
749+ if (detailMetaData.detail === 'Favorite') {
750+ template = detailNonStringSourceTemplate.arg(detailMetaData.detail).arg(
751+ detailMetaData.field).arg(detailMetaData.value === 0);
752+ } else {
753+ template = detailSourceTemplate.arg(detailMetaData.detail).arg(
754+ detailMetaData.field).arg(detailMetaData.value);
755+ }
756 var newDetail = Qt.createQmlObject(template, parent);
757 newContact.addDetail(newDetail);
758 }
759
760=== added file 'tests/qml/tst_ContactMap.qml'
761--- tests/qml/tst_ContactMap.qml 1970-01-01 00:00:00 +0000
762+++ tests/qml/tst_ContactMap.qml 2016-05-16 15:05:43 +0000
763@@ -0,0 +1,226 @@
764+/*
765+ * Copyright (C) 2014 Canonical, Ltd.
766+ *
767+ * This program is free software; you can redistribute it and/or modify
768+ * it under the terms of the GNU General Public License as published by
769+ * the Free Software Foundation; version 3.
770+ *
771+ * This program is distributed in the hope that it will be useful,
772+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
773+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
774+ * GNU General Public License for more details.
775+ *
776+ * You should have received a copy of the GNU General Public License
777+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
778+ */
779+
780+import QtQuick 2.4
781+import QtTest 1.0
782+import QtContacts 5.0
783+import Ubuntu.Contacts 0.1
784+
785+import "ContactUtil.js" as ContactUtilJS
786+
787+Item {
788+ id: root
789+
790+ function createContact(firstName, phoneNumber, email, favorite) {
791+ var details = [
792+ {detail: 'PhoneNumber', field: 'number', value: phoneNumber},
793+ {detail: 'EmailAddress', field: 'emailAddress', value: email},
794+ {detail: 'Name', field: 'firstName', value: firstName},
795+ {detail: 'Favorite', field: 'favorite', value: favorite === 0},
796+ ];
797+ if (favorite) {
798+ details.push({detail: 'Tag', field: 'tag', value: 'favorite'})
799+ }
800+
801+ return ContactUtilJS.createContact(details, root)
802+ }
803+
804+ function createSignalSpy(target, signalName) {
805+ var spy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', root, "")
806+ spy.target = target
807+ spy.signalName = signalName
808+ return spy
809+ }
810+
811+ Component {
812+ id: contactMapCmp
813+
814+ ContactMap {
815+ loadTags: true
816+ }
817+ }
818+
819+ Component {
820+ id: contactModelCmp
821+
822+ ContactModel {
823+ readonly property bool ready: memoryId != ""
824+ property string memoryId: ""
825+
826+ manager: "memory"
827+
828+ // we use this to identify the id used by the memory manager created by
829+ // ContactModel to use the same in the contactMap component
830+ function updateMemoryId() {
831+ var newContact = createContact('foo', '123', '', false)
832+ saveContact(newContact)
833+ var contactIds = newContact.contactId.split(":")
834+ var memoryIdValue = contactIds[contactIds.length - 2]
835+ memoryIdValue = memoryIdValue.split("=")
836+ memoryId = memoryIdValue[memoryIdValue.length - 1]
837+ console.debug("Memory Id:" + memoryId)
838+ }
839+ }
840+ }
841+
842+ TestCase {
843+ id: uContactsTest
844+ name: 'ContactMapTestCase'
845+
846+ property var contactModel
847+ property var contactMap
848+
849+ function init()
850+ {
851+ contactModel = contactModelCmp.createObject(root)
852+ contactModel.updateMemoryId()
853+ tryCompare(contactModel, 'ready', true)
854+
855+ contactMap = contactMapCmp.createObject(root, {"manager": "memory:id=" + contactModel.memoryId})
856+ }
857+
858+ function test_map_parameters()
859+ {
860+ tryCompare(contactMap, 'manager', 'memory')
861+ tryCompare(contactMap, 'managerParameters', {'id': contactModel.memoryId } )
862+ }
863+
864+ function test_empty_model()
865+ {
866+ compare(contactMap.groupNames, [])
867+ compare(contactMap.groupsMap, {})
868+ compare(contactMap.hasFavorites, false)
869+ }
870+
871+ function test_add_a_non_favoriteContact()
872+ {
873+ var spyGroupNamesChanged = createSignalSpy(contactMap, 'groupNamesChanged')
874+ var spyGroupMapsChanged = createSignalSpy(contactMap, 'groupsMapChanged')
875+ var spyHasFavoritesChanged = createSignalSpy(contactMap, 'hasFavoritesChanged')
876+
877+ var newContact = createContact('foo', '12345678', 'foo@bar.com', false)
878+ contactModel.saveContact(newContact)
879+
880+ // check if singnals was not fired
881+ tryCompare(spyGroupNamesChanged, 'count', 0)
882+ tryCompare(spyGroupMapsChanged, 'count', 0)
883+ tryCompare(spyHasFavoritesChanged, 'count', 0)
884+
885+ compare(contactMap.groupNames, [])
886+ compare(contactMap.groupsMap, {})
887+ compare(contactMap.hasFavorites, false)
888+ }
889+
890+ function test_add_a_favoriteContact()
891+ {
892+ var spyGroupNamesChanged = createSignalSpy(contactMap, 'groupNamesChanged')
893+ var spyGroupMapsChanged = createSignalSpy(contactMap, 'groupsMapChanged')
894+ var spyHasFavoritesChanged = createSignalSpy(contactMap, 'hasFavoritesChanged')
895+
896+ var newContact = createContact('favorite foo', '12345678', 'favorite_foo@bar.com', true)
897+ contactModel.saveContact(newContact)
898+
899+ // check if singnals was fired once
900+ tryCompare(spyGroupNamesChanged, 'count', 1)
901+ tryCompare(spyGroupMapsChanged, 'count', 1)
902+ tryCompare(spyHasFavoritesChanged, 'count', 1)
903+
904+ tryCompare(contactMap, 'groupNames', ['favorite'])
905+ tryCompare(contactMap, 'hasFavorites', true)
906+ }
907+
908+ function test_add_and_remove_a_favorite()
909+ {
910+ var spyGroupNamesChanged = createSignalSpy(contactMap, 'groupNamesChanged')
911+ var spyGroupMapsChanged = createSignalSpy(contactMap, 'groupsMapChanged')
912+ var spyHasFavoritesChanged = createSignalSpy(contactMap, 'hasFavoritesChanged')
913+
914+ var newContact = createContact('favorite foo', '12345678', 'favorite_foo@bar.com', true)
915+ contactModel.saveContact(newContact)
916+
917+ // wait contact be added
918+ tryCompare(spyHasFavoritesChanged, 'count', 1)
919+ tryCompare(contactMap, 'hasFavorites', true)
920+
921+ // remove contact
922+ contactModel.removeContact(newContact.contactId)
923+
924+ // check if singnals was fired twice (when contact added and removed)
925+ tryCompare(spyGroupNamesChanged, 'count', 2)
926+ tryCompare(spyGroupMapsChanged, 'count', 2)
927+ tryCompare(spyHasFavoritesChanged, 'count', 2)
928+
929+ tryCompare(contactMap, 'groupNames', [])
930+ tryCompare(contactMap, 'groupsMap', {})
931+ tryCompare(contactMap, 'hasFavorites', false)
932+ }
933+
934+ function test_add_two_favorite_contacts()
935+ {
936+ var spyGroupNamesChanged = createSignalSpy(contactMap, 'groupNamesChanged')
937+ var spyGroupMapsChanged = createSignalSpy(contactMap, 'groupsMapChanged')
938+ var spyHasFavoritesChanged = createSignalSpy(contactMap, 'hasFavoritesChanged')
939+
940+ var contactFoo = createContact('favorite foo', '12345678', 'favorite_foo@bar.com', true)
941+ contactModel.saveContact(contactFoo)
942+ tryCompare(spyGroupNamesChanged, 'count', 1)
943+ tryCompare(spyGroupMapsChanged, 'count', 1)
944+ tryCompare(spyHasFavoritesChanged, 'count', 1)
945+
946+ var contactBar = createContact('favorite bar', '87654321', 'favorite_bar@bar.com', true)
947+ contactModel.saveContact(contactBar)
948+ tryCompare(spyGroupNamesChanged, 'count', 1)
949+ tryCompare(spyGroupMapsChanged, 'count', 2)
950+ tryCompare(spyHasFavoritesChanged, 'count', 1)
951+ }
952+
953+ function test_modify_contact()
954+ {
955+ var spyGroupNamesChanged = createSignalSpy(contactMap, 'groupNamesChanged')
956+ var spyGroupMapsChanged = createSignalSpy(contactMap, 'groupsMapChanged')
957+ var spyHasFavoritesChanged = createSignalSpy(contactMap, 'hasFavoritesChanged')
958+
959+ var contactFoo = createContact('favorite foo', '12345678', 'favorite_foo@bar.com', true)
960+ contactModel.saveContact(contactFoo)
961+
962+ // wait contact to be saved
963+ tryCompare(spyHasFavoritesChanged, 'count', 1)
964+
965+ // remove tag 'favorite'
966+ var details = contactFoo.contactDetails
967+ var found = false
968+ for(var i=0; i < details.length; i++) {
969+ var det = details[i]
970+ if ((det.type === ContactDetail.Tag) &&
971+ (det.tag === "favorite")) {
972+ contactFoo.removeDetail(det)
973+ found = true
974+ break;
975+ }
976+ }
977+ compare(found, true)
978+ contactModel.saveContact(contactFoo)
979+
980+ // check signal and hasFavorite
981+ tryCompare(spyGroupNamesChanged, 'count', 2)
982+ tryCompare(spyGroupMapsChanged, 'count', 2)
983+ tryCompare(spyHasFavoritesChanged, 'count', 2)
984+ compare(contactMap.groupNames, [])
985+ compare(contactMap.groupsMap, {})
986+ compare(contactMap.hasFavorites, false)
987+ }
988+ }
989+}

Subscribers

People subscribed via source and target branches