Merge lp:~phablet-team/history-service/improve_participant_changes_notification into lp:history-service/staging

Proposed by Tiago Salem Herrmann
Status: Superseded
Proposed branch: lp:~phablet-team/history-service/improve_participant_changes_notification
Merge into: lp:history-service/staging
Diff against target: 1002 lines (+285/-64)
27 files modified
Ubuntu/History/historyeventmodel.cpp (+2/-2)
Ubuntu/History/historymodel.cpp (+1/-1)
Ubuntu/History/historythreadmodel.cpp (+18/-0)
Ubuntu/History/historythreadmodel.h (+1/-0)
daemon/HistoryService.xml (+13/-0)
daemon/callchannelobserver.cpp (+16/-2)
daemon/callchannelobserver.h (+2/-1)
daemon/historydaemon.cpp (+66/-33)
daemon/historydaemon.h (+7/-7)
daemon/historyservicedbus.cpp (+8/-0)
daemon/historyservicedbus.h (+8/-0)
plugins/sqlite/sqlitehistoryplugin.cpp (+7/-2)
src/contactmatcher.cpp (+37/-15)
src/contactmatcher_p.h (+2/-0)
src/manager.cpp (+3/-0)
src/manager.h (+1/-0)
src/managerdbus.cpp (+17/-0)
src/managerdbus_p.h (+8/-0)
src/participant.cpp (+9/-0)
src/participant.h (+1/-0)
src/thread.cpp (+16/-0)
src/thread.h (+2/-0)
src/threadview.cpp (+15/-0)
src/threadview.h (+8/-0)
src/threadview_p.h (+4/-0)
src/utils.cpp (+12/-1)
src/utils_p.h (+1/-0)
To merge this branch: bzr merge lp:~phablet-team/history-service/improve_participant_changes_notification
Reviewer Review Type Date Requested Status
Ubuntu Phablet Team Pending
Review via email: mp+316150@code.launchpad.net

This proposal supersedes a proposal from 2017-01-26.

This proposal has been superseded by a proposal from 2017-03-22.

Commit message

Improve the notifications of participants changing

Description of the change

Improve the notifications of participants changing

To post a comment you must log in.
257. By Tiago Salem Herrmann

merge parent branch

258. By Tiago Salem Herrmann

only remove participants from irc rooms

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Ubuntu/History/historyeventmodel.cpp'
2--- Ubuntu/History/historyeventmodel.cpp 2016-09-16 11:59:52 +0000
3+++ Ubuntu/History/historyeventmodel.cpp 2017-02-21 15:00:24 +0000
4@@ -95,7 +95,7 @@
5 result = event.eventId();
6 break;
7 case SenderIdRole:
8- result = event.senderId();
9+ result = History::ContactMatcher::normalizeId(event.senderId());
10 break;
11 case SenderRole:
12 result = History::ContactMatcher::instance()->contactInfo(event.accountId(), event.senderId());
13@@ -168,7 +168,7 @@
14 break;
15 case RemoteParticipantRole:
16 if (!voiceEvent.isNull()) {
17- result = voiceEvent.remoteParticipant();
18+ result = History::ContactMatcher::normalizeId(voiceEvent.remoteParticipant());
19 }
20 break;
21 case SubjectAsAliasRole:
22
23=== modified file 'Ubuntu/History/historymodel.cpp'
24--- Ubuntu/History/historymodel.cpp 2016-11-24 12:22:11 +0000
25+++ Ubuntu/History/historymodel.cpp 2017-02-21 15:00:24 +0000
26@@ -385,7 +385,7 @@
27 // FIXME: right now we might be grouping threads from different accounts, so we are not enforcing
28 // the accountId to be the same as the one from the contact info, but maybe we need to do that
29 // in the future?
30- if (History::Utils::compareIds(accountId, participant.identifier(), identifier)) {
31+ if (History::Utils::compareIds(accountId, History::ContactMatcher::normalizeId(participant.identifier()), identifier)) {
32 changedIndexes << idx;
33 }
34 }
35
36=== modified file 'Ubuntu/History/historythreadmodel.cpp'
37--- Ubuntu/History/historythreadmodel.cpp 2016-06-17 01:49:46 +0000
38+++ Ubuntu/History/historythreadmodel.cpp 2017-02-21 15:00:24 +0000
39@@ -294,6 +294,10 @@
40 SIGNAL(threadsRemoved(History::Threads)),
41 SLOT(onThreadsRemoved(History::Threads)));
42 connect(mThreadView.data(),
43+ SIGNAL(threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)),
44+ SLOT(onThreadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)));
45+
46+ connect(mThreadView.data(),
47 SIGNAL(invalidated()),
48 SLOT(triggerQueryUpdate()));
49
50@@ -311,6 +315,20 @@
51 fetchMore(QModelIndex());
52 }
53
54+
55+void HistoryThreadModel::onThreadParticipantsChanged(const History::Thread &thread, const History::Participants &added, const History::Participants &removed, const History::Participants &modified)
56+{
57+ int pos = mThreads.indexOf(thread);
58+ if (pos >= 0) {
59+ mThreads[pos].removeParticipants(removed);
60+ mThreads[pos].removeParticipants(modified);
61+ mThreads[pos].addParticipants(added);
62+ mThreads[pos].addParticipants(modified);
63+ QModelIndex idx = index(pos);
64+ Q_EMIT dataChanged(idx, idx);
65+ }
66+}
67+
68 void HistoryThreadModel::onThreadsAdded(const History::Threads &threads)
69 {
70 if (threads.isEmpty()) {
71
72=== modified file 'Ubuntu/History/historythreadmodel.h'
73--- Ubuntu/History/historythreadmodel.h 2016-06-17 01:49:46 +0000
74+++ Ubuntu/History/historythreadmodel.h 2017-02-21 15:00:24 +0000
75@@ -76,6 +76,7 @@
76 virtual void onThreadsAdded(const History::Threads &threads);
77 virtual void onThreadsModified(const History::Threads &threads);
78 virtual void onThreadsRemoved(const History::Threads &threads);
79+ virtual void onThreadParticipantsChanged(const History::Thread &thread, const History::Participants &added, const History::Participants &removed, const History::Participants &modified);
80
81 protected:
82 History::Threads fetchNextPage();
83
84=== modified file 'daemon/HistoryService.xml'
85--- daemon/HistoryService.xml 2016-04-15 22:23:21 +0000
86+++ daemon/HistoryService.xml 2017-02-21 15:00:24 +0000
87@@ -159,5 +159,18 @@
88 <arg name="events" type="a(a{sv})"/>
89 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList &lt; QVariantMap &gt;"/>
90 </signal>
91+ <signal name="ThreadParticipantsChanged">
92+ <dox:d><![CDATA[
93+ Participants changed in a certain thread changed.
94+ ]]></dox:d>
95+ <arg name="thread" type="a{sv}"/>
96+ <arg name="added" type="a(a{sv})"/>
97+ <arg name="removed" type="a(a{sv})"/>
98+ <arg name="modified" type="a(a{sv})"/>
99+ <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
100+ <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QList &lt; QVariantMap &gt;"/>
101+ <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QList &lt; QVariantMap &gt;"/>
102+ <annotation name="org.qtproject.QtDBus.QtTypeName.In3" value="QList &lt; QVariantMap &gt;"/>
103+ </signal>
104 </interface>
105 </node>
106
107=== modified file 'daemon/callchannelobserver.cpp'
108--- daemon/callchannelobserver.cpp 2013-07-12 14:30:18 +0000
109+++ daemon/callchannelobserver.cpp 2017-02-21 15:00:24 +0000
110@@ -40,6 +40,7 @@
111 SLOT(onCallStateChanged(Tp::CallState)));
112
113 mChannels.append(callChannel);
114+ mCallStates[callChannel.data()] = callChannel->callState();
115 }
116
117
118@@ -51,11 +52,24 @@
119 }
120
121 switch (state) {
122- case Tp::CallStateEnded:
123- Q_EMIT callEnded(Tp::CallChannelPtr(channel));
124+ case Tp::CallStateEnded: {
125+ bool incoming = !channel->isRequested();
126+ bool missed = incoming && channel->callStateReason().reason == Tp::CallStateChangeReasonNoAnswer;
127+
128+ // If the call state is not missed at this point, we need to check from which state we transitioned to ended,
129+ // if from Initialised, it means it was indeed missed
130+ if (incoming && !missed) {
131+ missed = mCallStates[channel] == Tp::CallStateInitialised;
132+ }
133+ mCallStates.remove(channel);
134+ mChannels.removeOne(Tp::CallChannelPtr(channel));
135+ Q_EMIT callEnded(Tp::CallChannelPtr(channel), missed);
136 break;
137+ }
138 case Tp::CallStateActive:
139 channel->setProperty("activeTimestamp", QDateTime::currentDateTime());
140+ default:
141+ mCallStates[channel] = state;
142 break;
143 }
144 }
145
146=== modified file 'daemon/callchannelobserver.h'
147--- daemon/callchannelobserver.h 2013-07-12 14:30:18 +0000
148+++ daemon/callchannelobserver.h 2017-02-21 15:00:24 +0000
149@@ -35,13 +35,14 @@
150 void onCallChannelAvailable(Tp::CallChannelPtr callChannel);
151
152 Q_SIGNALS:
153- void callEnded(Tp::CallChannelPtr callChannel);
154+ void callEnded(Tp::CallChannelPtr callChannel, bool missed);
155
156 protected Q_SLOTS:
157 void onCallStateChanged(Tp::CallState state);
158
159 private:
160 QList<Tp::CallChannelPtr> mChannels;
161+ QMap<Tp::CallChannel*,Tp::CallState> mCallStates;
162 };
163
164 #endif // CALLCHANNELOBSERVER_H
165
166=== modified file 'daemon/historydaemon.cpp'
167--- daemon/historydaemon.cpp 2016-11-24 02:02:32 +0000
168+++ daemon/historydaemon.cpp 2017-02-21 15:00:24 +0000
169@@ -131,8 +131,8 @@
170 History::TelepathyHelper::instance()->registerChannelObserver();
171
172 connect(&mCallObserver,
173- SIGNAL(callEnded(Tp::CallChannelPtr)),
174- SLOT(onCallEnded(Tp::CallChannelPtr)));
175+ SIGNAL(callEnded(Tp::CallChannelPtr, bool)),
176+ SLOT(onCallEnded(Tp::CallChannelPtr, bool)));
177 connect(&mTextObserver,
178 SIGNAL(messageReceived(Tp::TextChannelPtr,Tp::ReceivedMessage)),
179 SLOT(onMessageReceived(Tp::TextChannelPtr,Tp::ReceivedMessage)));
180@@ -187,6 +187,7 @@
181 QVariantMap properties;
182 QVariantList participants;
183 QStringList participantIds;
184+ QString accountId = textChannel->property(History::FieldAccountId).toString();
185
186 ChannelInterfaceRolesInterface *roles_interface = textChannel->optionalInterface<ChannelInterfaceRolesInterface>();
187 RolesMap roles;
188@@ -197,7 +198,7 @@
189 Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupContacts(false)) {
190 QVariantMap contactProperties;
191 contactProperties[History::FieldAlias] = contact->alias();
192- contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
193+ contactProperties[History::FieldAccountId] = accountId;
194 contactProperties[History::FieldIdentifier] = contact->id();
195 contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
196 contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
197@@ -208,7 +209,7 @@
198 Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupRemotePendingContacts(false)) {
199 QVariantMap contactProperties;
200 contactProperties[History::FieldAlias] = contact->alias();
201- contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
202+ contactProperties[History::FieldAccountId] = accountId;
203 contactProperties[History::FieldIdentifier] = contact->id();
204 contactProperties[History::FieldParticipantState] = History::ParticipantStateRemotePending;
205 contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
206@@ -219,7 +220,7 @@
207 Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupLocalPendingContacts(false)) {
208 QVariantMap contactProperties;
209 contactProperties[History::FieldAlias] = contact->alias();
210- contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
211+ contactProperties[History::FieldAccountId] = accountId;
212 contactProperties[History::FieldIdentifier] = contact->id();
213 contactProperties[History::FieldParticipantState] = History::ParticipantStateLocalPending;
214 contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
215@@ -231,7 +232,7 @@
216 textChannel->targetContact() == textChannel->connection()->selfContact()) {
217 QVariantMap contactProperties;
218 contactProperties[History::FieldAlias] = textChannel->targetContact()->alias();
219- contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
220+ contactProperties[History::FieldAccountId] = accountId;
221 contactProperties[History::FieldIdentifier] = textChannel->targetContact()->id();
222 contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
223 participantIds << textChannel->targetContact()->id();
224@@ -370,7 +371,7 @@
225 return mBackend->getSingleEvent((History::EventType)type, accountId, threadId, eventId);
226 }
227
228-bool HistoryDaemon::writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties)
229+bool HistoryDaemon::writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties, bool notify)
230 {
231 if (!mBackend) {
232 return false;
233@@ -427,13 +428,13 @@
234 mBackend->endBatchOperation();
235
236 // and last but not least, notify the results
237- if (!newEvents.isEmpty()) {
238+ if (!newEvents.isEmpty() && notify) {
239 mDBus.notifyEventsAdded(newEvents);
240 }
241- if (!modifiedEvents.isEmpty()) {
242+ if (!modifiedEvents.isEmpty() && notify) {
243 mDBus.notifyEventsModified(modifiedEvents);
244 }
245- if (!threads.isEmpty()) {
246+ if (!threads.isEmpty() && notify) {
247 mDBus.notifyThreadsModified(threads.values());
248 }
249 return true;
250@@ -562,7 +563,7 @@
251 &mTextObserver, SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));
252 }
253
254-void HistoryDaemon::onCallEnded(const Tp::CallChannelPtr &channel)
255+void HistoryDaemon::onCallEnded(const Tp::CallChannelPtr &channel, bool missed)
256 {
257 QVariantMap properties = propertiesFromChannel(channel);
258 QVariantList participants;
259@@ -592,7 +593,6 @@
260 // FIXME: check if checking for isRequested() is enough
261 bool incoming = !channel->isRequested();
262 int duration;
263- bool missed = incoming && channel->callStateReason().reason == Tp::CallStateChangeReasonNoAnswer;
264
265 if (!missed) {
266 QDateTime activeTime = channel->property("activeTimestamp").toDateTime();
267@@ -620,6 +620,7 @@
268 // for Rooms we need to explicitly create the thread to allow users to send messages to groups even
269 // before they receive any message.
270 // for other types, we can wait until messages are received
271+ bool notify = false;
272 if (channel->targetHandleType() == Tp::HandleTypeRoom) {
273 QString accountId = channel->property(History::FieldAccountId).toString();
274 QVariantMap properties = propertiesFromChannel(channel);
275@@ -641,12 +642,13 @@
276
277 // write information event including all initial invitees
278 Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
279- writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias());
280+ writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias(), QString(), QString(), false);
281 }
282
283 // update participants only if the thread is not available previously. Otherwise we'll wait for membersChanged event
284 // for reflect in conversation information events for modified participants.
285- updateRoomParticipants(channel);
286+ updateRoomParticipants(channel, false);
287+ notify = true;
288 }
289
290 // write an entry saying you joined the group if 'joined' flag in thread is false and modify that flag.
291@@ -657,7 +659,8 @@
292 writeInformationEvent(thread, History::InformationTypeSelfJoined);
293 }
294 // update backend
295- updateRoomProperties(channel, QVariantMap{{"Joined", true}});
296+ updateRoomProperties(channel, QVariantMap{{"Joined", true}}, false);
297+ notify = true;
298 }
299
300 Tp::AbstractInterface *room_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceRoomInterface>();
301@@ -685,6 +688,10 @@
302
303 connect(roles_interface, SIGNAL(RolesChanged(const HandleRolesMap&, const HandleRolesMap&)), SLOT(onRolesChanged(const HandleRolesMap&, const HandleRolesMap&)));
304 }
305+
306+ if (notify) {
307+ updateRoomParticipants(channel, true);
308+ }
309 }
310
311 void HistoryDaemon::onGroupMembersChanged(const Tp::Contacts &groupMembersAdded,
312@@ -711,18 +718,33 @@
313 matchFlagsForChannel(channel),
314 false);
315 if (!thread.isEmpty()) {
316+ QList<QVariantMap> added;
317+ QList<QVariantMap> removed;
318+ QList<QVariantMap> modified;
319 if (hasRemotePendingMembersAdded) {
320 Q_FOREACH (const Tp::ContactPtr& contact, groupRemotePendingMembersAdded) {
321 if (!foundInThread(contact, thread)) {
322- writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias());
323+ writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias(), QString(), QString(), true);
324+ QVariantMap participant;
325+ participant[History::FieldIdentifier] = contact->id();
326+ participant[History::FieldAlias] = contact->alias();
327+ participant[History::FieldParticipantState] = History::ParticipantStateRemotePending;
328+ added << participant;
329 }
330 }
331+
332 }
333 if (hasMembersAdded) {
334 Q_FOREACH (const Tp::ContactPtr& contact, groupMembersAdded) {
335 // if this member was not previously regular member in thread, notify about his join
336 if (!foundAsMemberInThread(contact, thread)) {
337- writeInformationEvent(thread, History::InformationTypeJoined, contact->alias());
338+ writeInformationEvent(thread, History::InformationTypeJoined, contact->alias(), QString(), QString(), true);
339+
340+ QVariantMap participant;
341+ participant[History::FieldIdentifier] = contact->id();
342+ participant[History::FieldAlias] = contact->alias();
343+ participant[History::FieldParticipantState] = History::ParticipantStateRegular;
344+ added << participant;
345 }
346 }
347 }
348@@ -743,25 +765,30 @@
349 }
350 writeInformationEvent(thread, type);
351 // update backend
352- updateRoomProperties(channel, QVariantMap{{"Joined", false}});
353+ updateRoomProperties(channel, QVariantMap{{"Joined", false}}, true);
354 }
355 else // don't notify any other group member removal if we are leaving the group
356 {
357 Q_FOREACH (const Tp::ContactPtr& contact, groupMembersRemoved) {
358 // inform about removed members other than us
359 if (contact->id() != channel->groupSelfContact()->id()) {
360- writeInformationEvent(thread, History::InformationTypeLeaving, contact->alias());
361+ writeInformationEvent(thread, History::InformationTypeLeaving, contact->alias(), QString(), QString(), true);
362 }
363+ QVariantMap participant;
364+ participant[History::FieldIdentifier] = contact->id();
365+ participant[History::FieldAlias] = contact->alias();
366+ removed << participant;
367 }
368 }
369 }
370+ mDBus.notifyThreadParticipantsChanged(thread, added, removed, QList<QVariantMap>());
371 }
372 }
373
374- updateRoomParticipants(channel);
375+ updateRoomParticipants(channel, true);
376 }
377
378-void HistoryDaemon::updateRoomParticipants(const Tp::TextChannelPtr channel)
379+void HistoryDaemon::updateRoomParticipants(const Tp::TextChannelPtr channel, bool notify)
380 {
381 if (!channel) {
382 return;
383@@ -811,12 +838,14 @@
384 QString accountId = channel->property(History::FieldAccountId).toString();
385 QString threadId = channel->targetId();
386 if (mBackend->updateRoomParticipants(accountId, threadId, History::EventTypeText, participants)) {
387- QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
388- mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
389+ if (notify) {
390+ QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
391+ mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
392+ }
393 }
394 }
395
396-void HistoryDaemon::updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap)
397+void HistoryDaemon::updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap, bool notify)
398 {
399 if (!channel) {
400 return;
401@@ -841,8 +870,10 @@
402 QString accountId = channel->property(History::FieldAccountId).toString();
403 QString threadId = channel->targetId();
404 if (mBackend->updateRoomParticipantsRoles(accountId, threadId, History::EventTypeText, participantsRoles)) {
405- QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
406- mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
407+ if (notify) {
408+ QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
409+ mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
410+ }
411 }
412
413 // update self roles in room properties
414@@ -865,19 +896,21 @@
415 updateRoomProperties(accountId, threadId, type, properties, invalidated);
416 }
417
418-void HistoryDaemon::updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties)
419+void HistoryDaemon::updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties, bool notify)
420 {
421 QString accountId = channel->property(History::FieldAccountId).toString();
422 QString threadId = channel->targetId();
423 History::EventType type = History::EventTypeText;
424- updateRoomProperties(accountId, threadId, type, properties, QStringList());
425+ updateRoomProperties(accountId, threadId, type, properties, QStringList(), notify);
426 }
427
428-void HistoryDaemon::updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated)
429+void HistoryDaemon::updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated, bool notify)
430 {
431 if (mBackend->updateRoomInfo(accountId, threadId, type, properties, invalidated)) {
432- QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
433- mDBus.notifyThreadsModified(QList<QVariantMap>() << thread);
434+ if (notify) {
435+ QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
436+ mDBus.notifyThreadsModified(QList<QVariantMap>() << thread);
437+ }
438 }
439 }
440
441@@ -1191,7 +1224,7 @@
442 return reply.value();
443 }
444
445-void HistoryDaemon::writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject, const QString &sender, const QString &text)
446+void HistoryDaemon::writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject, const QString &sender, const QString &text, bool notify)
447 {
448 History::TextEvent historyEvent = History::TextEvent(thread[History::FieldAccountId].toString(),
449 thread[History::FieldThreadId].toString(),
450@@ -1207,7 +1240,7 @@
451 QDateTime::currentDateTime(),
452 subject,
453 type);
454- writeEvents(QList<QVariantMap>() << historyEvent.properties(), thread);
455+ writeEvents(QList<QVariantMap>() << historyEvent.properties(), thread, notify);
456 }
457
458 void HistoryDaemon::writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties)
459
460=== modified file 'daemon/historydaemon.h'
461--- daemon/historydaemon.h 2016-10-20 13:56:10 +0000
462+++ daemon/historydaemon.h 2017-02-21 15:00:24 +0000
463@@ -54,13 +54,13 @@
464 QVariantMap getSingleEvent(int type, const QString &accountId, const QString &threadId, const QString &eventId);
465 QVariantMap getSingleEventFromTextChannel(const Tp::TextChannelPtr textChannel, const QString &messageId);
466
467- bool writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties);
468+ bool writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties, bool notify = true);
469 bool removeEvents(const QList<QVariantMap> &events);
470 bool removeThreads(const QList<QVariantMap> &threads);
471
472 private Q_SLOTS:
473 void onObserverCreated();
474- void onCallEnded(const Tp::CallChannelPtr &channel);
475+ void onCallEnded(const Tp::CallChannelPtr &channel, bool missed);
476 void onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
477 void onMessageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
478 void onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken);
479@@ -73,14 +73,14 @@
480
481 protected:
482 History::MatchFlags matchFlagsForChannel(const Tp::ChannelPtr &channel);
483- void updateRoomParticipants(const Tp::TextChannelPtr channel);
484- void updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap);
485+ void updateRoomParticipants(const Tp::TextChannelPtr channel, bool notify = true);
486+ void updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap, bool notify = true);
487 QString hashThread(const QVariantMap &thread);
488 static QVariantMap getInterfaceProperties(const Tp::AbstractInterface *interface);
489- void updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties);
490- void updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated);
491+ void updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties, bool notify = true);
492+ void updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated, bool notify = true);
493
494- void writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject = QString(), const QString &sender = QString("self"), const QString &text = QString());
495+ void writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject = QString(), const QString &sender = QString("self"), const QString &text = QString(), bool notify = true);
496
497 void writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties);
498 void writeRolesInformationEvents(const QVariantMap &thread, const Tp::ChannelPtr &channel, const RolesMap &rolesMap);
499
500=== modified file 'daemon/historyservicedbus.cpp'
501--- daemon/historyservicedbus.cpp 2016-11-24 01:56:01 +0000
502+++ daemon/historyservicedbus.cpp 2017-02-21 15:00:24 +0000
503@@ -75,6 +75,14 @@
504 Q_EMIT EventsRemoved(events);
505 }
506
507+void HistoryServiceDBus::notifyThreadParticipantsChanged(const QVariantMap &thread,
508+ const QList<QVariantMap> &added,
509+ const QList<QVariantMap> &removed,
510+ const QList<QVariantMap> &modified)
511+{
512+ Q_EMIT ThreadParticipantsChanged(thread, added, removed, modified);
513+}
514+
515 QVariantMap HistoryServiceDBus::ThreadForProperties(const QString &accountId,
516 int type,
517 const QVariantMap &properties,
518
519=== modified file 'daemon/historyservicedbus.h'
520--- daemon/historyservicedbus.h 2016-06-17 01:49:46 +0000
521+++ daemon/historyservicedbus.h 2017-02-21 15:00:24 +0000
522@@ -39,6 +39,10 @@
523 void notifyThreadsAdded(const QList<QVariantMap> &threads);
524 void notifyThreadsModified(const QList<QVariantMap> &threads);
525 void notifyThreadsRemoved(const QList<QVariantMap> &threads);
526+ void notifyThreadParticipantsChanged(const QVariantMap &thread,
527+ const QList<QVariantMap> &added,
528+ const QList<QVariantMap> &removed,
529+ const QList<QVariantMap> &modified);
530
531 void notifyEventsAdded(const QList<QVariantMap> &events);
532 void notifyEventsModified(const QList<QVariantMap> &events);
533@@ -70,6 +74,10 @@
534 void ThreadsAdded(const QList<QVariantMap> &threads);
535 void ThreadsModified(const QList<QVariantMap> &threads);
536 void ThreadsRemoved(const QList<QVariantMap> &threads);
537+ void ThreadParticipantsChanged(const QVariantMap &thread,
538+ const QList<QVariantMap> &added,
539+ const QList<QVariantMap> &removed,
540+ const QList<QVariantMap> &modified);
541
542 void EventsAdded(const QList<QVariantMap> &events);
543 void EventsModified(const QList<QVariantMap> &events);
544
545=== modified file 'plugins/sqlite/sqlitehistoryplugin.cpp'
546--- plugins/sqlite/sqlitehistoryplugin.cpp 2016-11-24 01:50:48 +0000
547+++ plugins/sqlite/sqlitehistoryplugin.cpp 2017-02-21 15:00:24 +0000
548@@ -1290,6 +1290,9 @@
549 chatRoomInfo["SelfRoles"] = query1.value(20).toInt();
550
551 thread[History::FieldChatRoomInfo] = chatRoomInfo;
552+ if (!History::Utils::shouldIncludeParticipants(thread)) {
553+ thread.remove(History::FieldParticipants);
554+ }
555 }
556 break;
557 case History::EventTypeVoice:
558@@ -1353,8 +1356,10 @@
559 event[History::FieldSenderId] = query.value(3);
560 event[History::FieldTimestamp] = toLocalTimeString(query.value(4).toDateTime());
561 event[History::FieldNewEvent] = query.value(5).toBool();
562- QStringList participants = query.value(6).toString().split("|,|");
563- event[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);
564+ if (type != History::EventTypeText) {
565+ QStringList participants = query.value(6).toString().split("|,|");
566+ event[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);
567+ }
568
569 switch (type) {
570 case History::EventTypeText:
571
572=== modified file 'src/contactmatcher.cpp'
573--- src/contactmatcher.cpp 2016-06-17 01:49:46 +0000
574+++ src/contactmatcher.cpp 2017-02-21 15:00:24 +0000
575@@ -99,21 +99,24 @@
576 {
577 InternalContactMap &internalMap = mContactMap[accountId];
578
579+
580+ QString normalizedId = normalizeId(identifier);
581+
582 // first do a simple string match on the map
583- if (internalMap.contains(identifier)) {
584- return internalMap[identifier];
585+ if (internalMap.contains(normalizedId)) {
586+ return internalMap[normalizedId];
587 }
588
589 QVariantMap map;
590-
591 // and if there was no match, asynchronously request the info, and return an empty map for now
592 if (History::TelepathyHelper::instance()->ready()) {
593- map = requestContactInfo(accountId, identifier, synchronous);
594+ map = requestContactInfo(accountId, normalizedId, synchronous);
595 } else if (!synchronous) {
596- RequestInfo info{accountId, identifier};
597+ RequestInfo info{accountId, normalizedId};
598 mPendingRequests.append(info);
599 }
600- map[History::FieldIdentifier] = identifier;
601+
602+ map[History::FieldIdentifier] = normalizedId;
603 map[History::FieldAccountId] = accountId;
604
605 QMapIterator<QString, QVariant> i(properties);
606@@ -124,7 +127,7 @@
607 }
608 }
609
610- mContactMap[accountId][identifier] = map;
611+ mContactMap[accountId][normalizedId] = map;
612 return map;
613 }
614
615@@ -309,6 +312,7 @@
616 */
617 QVariantMap ContactMatcher::requestContactInfo(const QString &accountId, const QString &identifier, bool synchronous)
618 {
619+ QString normalizedId = normalizeId(identifier);
620 QStringList addressableVCardFields = addressableFields(accountId);
621 if (addressableVCardFields.isEmpty()) {
622 // FIXME: add support for generic accounts
623@@ -328,7 +332,7 @@
624 QContactUnionFilter topLevelFilter;
625 Q_FOREACH(const QString &field, addressableVCardFields) {
626 if (field == "tel") {
627- topLevelFilter.append(QContactPhoneNumber::match(identifier));
628+ topLevelFilter.append(QContactPhoneNumber::match(normalizedId));
629 } else {
630 // FIXME: handle more fields
631 // rely on a generic field filter
632@@ -340,7 +344,7 @@
633 QContactDetailFilter valueFilter = QContactDetailFilter();
634 valueFilter.setDetailType(QContactExtendedDetail::Type, QContactExtendedDetail::FieldData);
635 valueFilter.setMatchFlags(QContactFilter::MatchExactly);
636- valueFilter.setValue(identifier);
637+ valueFilter.setValue(normalizedId);
638
639 QContactIntersectionFilter intersectionFilter;
640 intersectionFilter.append(nameFilter);
641@@ -356,7 +360,7 @@
642 return QVariantMap();
643 }
644 // for synchronous requests, return the results right away.
645- return matchAndUpdate(accountId, identifier, contacts.first());
646+ return matchAndUpdate(accountId, normalizedId, contacts.first());
647 } else {
648 // check if there is a request already going on for the given contact
649 Q_FOREACH(const RequestInfo &info, mRequests.values()) {
650@@ -365,7 +369,7 @@
651 continue;
652 }
653
654- if (info.identifier == identifier) {
655+ if (info.identifier == normalizedId) {
656 // if so, just wait for it to finish
657 return QVariantMap();
658 }
659@@ -381,7 +385,7 @@
660
661 RequestInfo info;
662 info.accountId = accountId;
663- info.identifier = identifier;
664+ info.identifier = normalizedId;
665 mRequests[request] = info;
666 request->start();
667 }
668@@ -414,7 +418,6 @@
669 QStringList fields = addressableFields(accountId);
670 bool match = false;
671
672- int fieldsCount = fields.count();
673 Q_FOREACH(const QString &field, fields) {
674 if (field == "tel") {
675 QList<QContactDetail> details = contact.details(QContactDetail::TypePhoneNumber);
676@@ -467,8 +470,14 @@
677 QStringList fields;
678 if (!account.isNull()) {
679 fields = account->protocolInfo().addressableVCardFields();
680- mAddressableFields[accountId] = fields;
681- }
682+ }
683+
684+ // fallback to phone number matching in case everything else fails
685+ if (fields.isEmpty()) {
686+ fields << "tel";
687+ }
688+
689+ mAddressableFields[accountId] = fields;
690
691 return fields;
692 }
693@@ -478,4 +487,17 @@
694 return (map.contains(History::FieldContactId) && !map[History::FieldContactId].toString().isEmpty());
695 }
696
697+QString ContactMatcher::normalizeId(const QString &id)
698+{
699+ QString normalizedId = id;
700+
701+ // FIXME: this is a hack so that SIP URIs get converted into phone numbers for contact matching
702+ if (normalizedId.startsWith("sip:")) {
703+ normalizedId.remove("sip:").remove(QRegularExpression("@.*$"));
704+ }
705+
706+ return normalizedId;
707+}
708+
709+
710 }
711
712=== modified file 'src/contactmatcher_p.h'
713--- src/contactmatcher_p.h 2016-06-17 01:49:46 +0000
714+++ src/contactmatcher_p.h 2017-02-21 15:00:24 +0000
715@@ -51,6 +51,8 @@
716 // this will only watch for contact changes affecting the identifier, but won't fetch contact info
717 void watchIdentifier(const QString &accountId, const QString &identifier, const QVariantMap &currentInfo = QVariantMap());
718
719+ static QString normalizeId(const QString &id);
720+
721 Q_SIGNALS:
722 void contactInfoChanged(const QString &acountId, const QString &identifier, const QVariantMap &contactInfo);
723
724
725=== modified file 'src/manager.cpp'
726--- src/manager.cpp 2016-06-17 01:49:46 +0000
727+++ src/manager.cpp 2017-02-21 15:00:24 +0000
728@@ -65,6 +65,9 @@
729 SIGNAL(threadsRemoved(History::Threads)),
730 SIGNAL(threadsRemoved(History::Threads)));
731 connect(d->dbus.data(),
732+ SIGNAL(threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)),
733+ SIGNAL(threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)));
734+ connect(d->dbus.data(),
735 SIGNAL(eventsAdded(History::Events)),
736 SIGNAL(eventsAdded(History::Events)));
737 connect(d->dbus.data(),
738
739=== modified file 'src/manager.h'
740--- src/manager.h 2016-06-17 01:49:46 +0000
741+++ src/manager.h 2017-02-21 15:00:24 +0000
742@@ -79,6 +79,7 @@
743 void threadsAdded(const History::Threads &threads);
744 void threadsModified(const History::Threads &threads);
745 void threadsRemoved(const History::Threads &threads);
746+ void threadParticipantsChanged(const History::Thread &thread, const History::Participants &added, const History::Participants &removed, const History::Participants &modified);
747
748 void eventsAdded(const History::Events &events);
749 void eventsModified(const History::Events &events);
750
751=== modified file 'src/managerdbus.cpp'
752--- src/managerdbus.cpp 2016-06-17 01:49:46 +0000
753+++ src/managerdbus.cpp 2017-02-21 15:00:24 +0000
754@@ -50,6 +50,12 @@
755 connection.connect(DBusService, DBusObjectPath, DBusInterface, "ThreadsRemoved",
756 this, SLOT(onThreadsRemoved(QList<QVariantMap>)));
757
758+ connection.connect(DBusService, DBusObjectPath, DBusInterface, "ThreadParticipantsChanged",
759+ this, SLOT(onThreadParticipantsChanged(QVariantMap,
760+ QList<QVariantMap>,
761+ QList<QVariantMap>,
762+ QList<QVariantMap>)));
763+
764 connection.connect(DBusService, DBusObjectPath, DBusInterface, "EventsAdded",
765 this, SLOT(onEventsAdded(QList<QVariantMap>)));
766 connection.connect(DBusService, DBusObjectPath, DBusInterface, "EventsModified",
767@@ -168,6 +174,17 @@
768 Q_EMIT threadsRemoved(threadsFromProperties(threads));
769 }
770
771+void ManagerDBus::onThreadParticipantsChanged(const QVariantMap &thread,
772+ const QList<QVariantMap> &added,
773+ const QList<QVariantMap> &removed,
774+ const QList<QVariantMap> &modified)
775+{
776+ Q_EMIT threadParticipantsChanged(threadsFromProperties(QList<QVariantMap>() << thread).first(),
777+ Participants::fromVariantMapList(added),
778+ Participants::fromVariantMapList(removed),
779+ Participants::fromVariantMapList(modified));
780+}
781+
782 void ManagerDBus::onEventsAdded(const QList<QVariantMap> &events)
783 {
784 Q_EMIT eventsAdded(eventsFromProperties(events));
785
786=== modified file 'src/managerdbus_p.h'
787--- src/managerdbus_p.h 2016-06-17 01:49:46 +0000
788+++ src/managerdbus_p.h 2017-02-21 15:00:24 +0000
789@@ -62,6 +62,10 @@
790 void threadsAdded(const History::Threads &threads);
791 void threadsModified(const History::Threads &threads);
792 void threadsRemoved(const History::Threads &threads);
793+ void threadParticipantsChanged(const History::Thread &thread,
794+ const History::Participants &added,
795+ const History::Participants &removed,
796+ const History::Participants &modified);
797
798 void eventsAdded(const History::Events &events);
799 void eventsModified(const History::Events &events);
800@@ -71,6 +75,10 @@
801 void onThreadsAdded(const QList<QVariantMap> &threads);
802 void onThreadsModified(const QList<QVariantMap> &threads);
803 void onThreadsRemoved(const QList<QVariantMap> &threads);
804+ void onThreadParticipantsChanged(const QVariantMap &thread,
805+ const QList<QVariantMap> &added,
806+ const QList<QVariantMap> &removed,
807+ const QList<QVariantMap> &modified);
808
809 void onEventsAdded(const QList<QVariantMap> &events);
810 void onEventsModified(const QList<QVariantMap> &events);
811
812=== modified file 'src/participant.cpp'
813--- src/participant.cpp 2016-11-24 01:04:37 +0000
814+++ src/participant.cpp 2017-02-21 15:00:24 +0000
815@@ -233,6 +233,15 @@
816 return participants;
817 }
818
819+Participants Participants::fromVariantMapList(const QList<QVariantMap> &list)
820+{
821+ Participants participants;
822+ Q_FOREACH(const QVariantMap& entry, list) {
823+ participants << Participant::fromProperties(entry);
824+ }
825+ return participants;
826+}
827+
828 QVariantList Participants::toVariantList() const
829 {
830 QVariantList list;
831
832=== modified file 'src/participant.h'
833--- src/participant.h 2016-11-24 01:04:37 +0000
834+++ src/participant.h 2017-02-21 15:00:24 +0000
835@@ -79,6 +79,7 @@
836 QStringList identifiers() const;
837 static Participants fromVariant(const QVariant &variant);
838 static Participants fromVariantList(const QVariantList &list);
839+ static Participants fromVariantMapList(const QList<QVariantMap> &list);
840 static Participants fromStringList(const QStringList &list);
841 QVariantList toVariantList() const;
842 History::Participants filterByState(uint state) const;
843
844=== modified file 'src/thread.cpp'
845--- src/thread.cpp 2016-07-12 02:08:11 +0000
846+++ src/thread.cpp 2017-02-21 15:00:24 +0000
847@@ -192,6 +192,22 @@
848 return selfData < otherData;
849 }
850
851+void Thread::removeParticipants(const Participants &participants)
852+{
853+ Q_D(Thread);
854+ Q_FOREACH(const Participant &participant, participants) {
855+ d->participants.removeAll(participant);
856+ }
857+}
858+
859+void Thread::addParticipants(const Participants &participants)
860+{
861+ Q_D(Thread);
862+ Q_FOREACH(const Participant &participant, participants) {
863+ d->participants.append(participant);
864+ }
865+}
866+
867 QVariantMap Thread::properties() const
868 {
869 Q_D(const Thread);
870
871=== modified file 'src/thread.h'
872--- src/thread.h 2016-07-12 01:59:06 +0000
873+++ src/thread.h 2017-02-21 15:00:24 +0000
874@@ -73,6 +73,8 @@
875 ChatType chatType() const;
876 Threads groupedThreads() const;
877 QVariantMap chatRoomInfo() const;
878+ void addParticipants(const History::Participants &participants);
879+ void removeParticipants(const History::Participants &participants);
880
881 bool isNull() const;
882 bool operator==(const Thread &other) const;
883
884=== modified file 'src/threadview.cpp'
885--- src/threadview.cpp 2015-10-01 19:44:45 +0000
886+++ src/threadview.cpp 2017-02-21 15:00:24 +0000
887@@ -89,6 +89,18 @@
888 }
889 }
890
891+void ThreadViewPrivate::_d_threadParticipantsChanged(const History::Thread &thread,
892+ const History::Participants &added,
893+ const History::Participants &removed,
894+ const History::Participants &modified)
895+{
896+ Q_Q(ThreadView);
897+ Threads filtered = filteredThreads(History::Threads() << thread);
898+ if (!filtered.isEmpty()) {
899+ Q_EMIT q->threadParticipantsChanged(filtered.first(), added, removed, modified);
900+ }
901+}
902+
903 // ------------- ThreadView -------------------------------------------------------
904
905 ThreadView::ThreadView(History::EventType type,
906@@ -132,6 +144,9 @@
907 connect(Manager::instance(),
908 SIGNAL(threadsRemoved(History::Threads)),
909 SLOT(_d_threadsRemoved(History::Threads)));
910+ connect(Manager::instance(),
911+ SIGNAL(threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)),
912+ SLOT(_d_threadParticipantsChanged(History::Thread, History::Participants, History::Participants, History::Participants)));
913 }
914
915 ThreadView::~ThreadView()
916
917=== modified file 'src/threadview.h'
918--- src/threadview.h 2015-09-21 20:05:06 +0000
919+++ src/threadview.h 2017-02-21 15:00:24 +0000
920@@ -52,12 +52,20 @@
921 void threadsAdded(const History::Threads &threads);
922 void threadsModified(const History::Threads &threads);
923 void threadsRemoved(const History::Threads &threads);
924+ void threadParticipantsChanged(const History::Thread &thread,
925+ const History::Participants &added,
926+ const History::Participants &removed,
927+ const History::Participants &modified);
928 void invalidated();
929
930 private:
931 Q_PRIVATE_SLOT(d_func(), void _d_threadsAdded(const History::Threads &threads))
932 Q_PRIVATE_SLOT(d_func(), void _d_threadsModified(const History::Threads &threads))
933 Q_PRIVATE_SLOT(d_func(), void _d_threadsRemoved(const History::Threads &threads))
934+ Q_PRIVATE_SLOT(d_func(), void _d_threadParticipantsChanged(const History::Thread &thread,
935+ const History::Participants &added,
936+ const History::Participants &removed,
937+ const History::Participants &modified))
938 QScopedPointer<ThreadViewPrivate> d_ptr;
939
940 };
941
942=== modified file 'src/threadview_p.h'
943--- src/threadview_p.h 2013-09-17 21:33:34 +0000
944+++ src/threadview_p.h 2017-02-21 15:00:24 +0000
945@@ -50,6 +50,10 @@
946 void _d_threadsAdded(const History::Threads &threads);
947 void _d_threadsModified(const History::Threads &threads);
948 void _d_threadsRemoved(const History::Threads &threads);
949+ void _d_threadParticipantsChanged(const History::Thread &thread,
950+ const History::Participants &added,
951+ const History::Participants &removed,
952+ const History::Participants &modified);
953
954 ThreadView *q_ptr;
955 };
956
957=== modified file 'src/utils.cpp'
958--- src/utils.cpp 2016-11-08 16:02:18 +0000
959+++ src/utils.cpp 2017-02-21 15:00:24 +0000
960@@ -50,6 +50,7 @@
961 if (protocolFlags.isEmpty()) {
962 protocolFlags["ofono"] = MatchPhoneNumber;
963 protocolFlags["multimedia"] = MatchPhoneNumber;
964+ protocolFlags["sip"] = MatchPhoneNumber;
965 }
966
967 QString protocol = protocolFromAccountId(accountId);
968@@ -57,7 +58,7 @@
969 return protocolFlags[protocol];
970 }
971
972- // default to this value
973+ // default to phone number matching for now
974 return History::MatchCaseSensitive;
975 }
976
977@@ -175,4 +176,14 @@
978 return QVariant();
979 }
980
981+bool Utils::shouldIncludeParticipants(const Thread &thread)
982+{
983+ // FIXME
984+ // this is obviously incorrect. we have to query the protocol files as a final solution
985+ if (protocolFromAccountId(thread.accountId()) == "irc") {
986+ return thread.chatType() != History::ChatTypeRoom;
987+ }
988+ return true;
989+}
990+
991 }
992
993=== modified file 'src/utils_p.h'
994--- src/utils_p.h 2016-11-08 16:02:18 +0000
995+++ src/utils_p.h 2017-02-21 15:00:24 +0000
996@@ -36,6 +36,7 @@
997 static bool compareParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags);
998 static bool compareNormalizedParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags);
999 static bool shouldGroupThread(const Thread &thread);
1000+ static bool shouldIncludeParticipants(const Thread &thread);
1001 static QString normalizeId(const QString &accountId, const QString &id);
1002 static QVariant getUserValue(const QString &interface, const QString &propName);
1003

Subscribers

People subscribed via source and target branches

to all changes: