Merge lp:~tiagosh/messaging-app/fix-1376793 into lp:messaging-app

Proposed by Tiago Salem Herrmann
Status: Merged
Approved by: Gustavo Pichorim Boiko
Approved revision: 294
Merged at revision: 293
Proposed branch: lp:~tiagosh/messaging-app/fix-1376793
Merge into: lp:messaging-app
Diff against target: 349 lines (+182/-26)
4 files modified
src/qml/MainPage.qml (+5/-0)
src/qml/Messages.qml (+49/-2)
src/qml/ThreadDelegate.qml (+87/-24)
tests/autopilot/messaging_app/tests/test_messaging.py (+41/-0)
To merge this branch: bzr merge lp:~tiagosh/messaging-app/fix-1376793
Reviewer Review Type Date Requested Status
Gustavo Pichorim Boiko (community) Approve
PS Jenkins bot continuous-integration Needs Fixing
Review via email: mp+245812@code.launchpad.net

Commit message

Query thread messages when searchTerm changes.

Description of the change

Query thread messages when searchTerm changes.

--Checklist--
Are there any related MPs required for this MP to build/function as expected? Please list.
https://code.launchpad.net/~boiko/history-service/fix_filter_passing/+merge/245799

Is your branch in sync with latest trunk (e.g. bzr pull lp:trunk -> no changes)
Yes

Did you perform an exploratory manual test run of your code change and any related functionality on device or emulator?
Yes

Did you successfully run all tests found in your component's Test Plan (https://wiki.ubuntu.com/Process/Merges/TestPlan/messaging-app) on device or emulator?
Yes

If you changed the UI, was the change specified/approved by design?
N/A

If you changed UI labels, did you update the pot file?
N/A

If you changed the packaging (debian), did you add a core-dev as a reviewer to this MP?
N/A

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
286. By Tiago Salem Herrmann

display information about the matched event

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
287. By Tiago Salem Herrmann

scroll to the item that matched the search

288. By Tiago Salem Herrmann

merge trunk

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 :

173 + HistoryEventModel {

Can you create this inside a loader so that when not searching there is no memory penalty?

review: Needs Fixing
289. By Tiago Salem Herrmann

use a Loader to avoid creating instances of the model when not required

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
290. By Tiago Salem Herrmann

- make loader asynchronous
- fix dates when searching

291. By Tiago Salem Herrmann

show spinner while searching

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
292. By Tiago Salem Herrmann

update search when delegates are created

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
293. By Tiago Salem Herrmann

hide bottom panel when searching
use a timer to retrieve more items from history

294. By Tiago Salem Herrmann

anchor to the bottom of the screen while searching

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 :

Did you perform an exploratory manual test run of the code change and any related functionality on device or emulator?
Yes

Did CI run pass? If not, please explain why.
No, but not related to this change

Have you checked that submitter has accurately filled out the submitter checklist and has taken no shortcut?
Yes

Code looks good and works as expected!

review: Approve
295. By Tiago Salem Herrmann

add autopilot test for message search

296. By Tiago Salem Herrmann

merge trunk

297. By Tiago Salem Herrmann

fix broken test

Revision history for this message
Gustavo Pichorim Boiko (boiko) wrote :

331 + time.sleep(2)
332 + count = 0
333 + for thread in threads:
334 + if thread.height != 0:
335 + count+=1
336 +
337 + self.assertThat(count, Equals(3))

I think it would be better to write a function to count the threads and use:
self.assertThat(count_visible_threads(threads), Eventually(Equals(3))

so that we don't have to rely on timers.

review: Needs Fixing
298. By Tiago Salem Herrmann

do not rely on timers

299. By Tiago Salem Herrmann

use Eventually()

300. By Tiago Salem Herrmann

use lamba functions

Revision history for this message
Gustavo Pichorim Boiko (boiko) wrote :

Looks good now!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/qml/MainPage.qml'
2--- src/qml/MainPage.qml 2015-01-05 20:35:11 +0000
3+++ src/qml/MainPage.qml 2015-01-23 15:31:27 +0000
4@@ -43,6 +43,7 @@
5
6 TextField {
7 id: searchField
8+ objectName: "searchField"
9 visible: mainPage.searching
10 anchors {
11 left: parent.left
12@@ -179,6 +180,7 @@
13 }
14 listModel: threadModel
15 clip: true
16+ cacheBuffer: threadList.height * 2
17 section.property: "eventDate"
18 //spacing: searchField.text === "" ? units.gu(-2) : 0
19 section.delegate: searching && searchField.text !== "" ? null : sectionDelegate
20@@ -203,6 +205,9 @@
21 } else {
22 var properties = model.properties
23 properties["keyboardFocus"] = false
24+ if (displayedEvent != null) {
25+ properties["scrollToEventId"] = displayedEvent.eventId
26+ }
27 if (model.participants[0].alias) {
28 properties["firstRecipientAlias"] = model.participants[0].alias;
29 }
30
31=== modified file 'src/qml/Messages.qml'
32--- src/qml/Messages.qml 2015-01-05 19:47:07 +0000
33+++ src/qml/Messages.qml 2015-01-23 15:31:27 +0000
34@@ -50,6 +50,8 @@
35 property alias contactWatcher: contactWatcherInternal
36 property string lastFilter: ""
37 property string text: ""
38+ property string scrollToEventId: ""
39+ property bool isSearching: scrollToEventId !== ""
40 property string latestEventId: ""
41
42 function addAttachmentsToModel(transfer) {
43@@ -109,6 +111,16 @@
44 onAccountChanged: messages.account = mainView.account
45 }
46
47+ ActivityIndicator {
48+ id: activityIndicator
49+ anchors {
50+ verticalCenter: parent.verticalCenter
51+ horizontalCenter: parent.horizontalCenter
52+ }
53+ running: isSearching
54+ visible: running
55+ }
56+
57 ListModel {
58 id: attachments
59 }
60@@ -549,12 +561,47 @@
61 latestEventId = eventModel.get(0).eventId
62 messageList.positionViewAtBeginning()
63 }
64+ if (isSearching) {
65+ // if we ask for more items manually listview will stop working,
66+ // so we only set again once the item was found
67+ messageList.listModel = null
68+ // always check last 15 items
69+ var maxItems = 15
70+ for (var i = count-1; count >= i; i--) {
71+ if (--maxItems < 0) {
72+ break;
73+ }
74+ if (eventModel.get(i).eventId == scrollToEventId) {
75+ scrollToEventId = ""
76+ messageList.listModel = eventModel
77+ messageList.positionViewAtIndex(i, ListView.Center)
78+ return;
79+ }
80+ }
81+
82+ if (eventModel.canFetchMore && isSearching) {
83+ fetchMoreTimer.running = true
84+ } else {
85+ // event not found
86+ scrollToEventId = ""
87+ messageList.listModel = eventModel
88+ }
89+ }
90 }
91 }
92
93+ Timer {
94+ id: fetchMoreTimer
95+ running: false
96+ interval: 100
97+ repeat: false
98+ onTriggered: eventModel.fetchMore()
99+ }
100+
101 MessagesListView {
102 id: messageList
103 objectName: "messageList"
104+ visible: !isSearching
105
106 // because of the header
107 clip: true
108@@ -568,11 +615,11 @@
109
110 Item {
111 id: bottomPanel
112- anchors.bottom: keyboard.top
113+ anchors.bottom: isSearching ? parent.bottom : keyboard.top
114 anchors.left: parent.left
115 anchors.right: parent.right
116 height: selectionMode ? 0 : textEntry.height + units.gu(2)
117- visible: !selectionMode
118+ visible: !selectionMode && !isSearching
119 clip: true
120 MouseArea {
121 anchors.fill: parent
122
123=== modified file 'src/qml/ThreadDelegate.qml'
124--- src/qml/ThreadDelegate.qml 2015-01-05 20:35:11 +0000
125+++ src/qml/ThreadDelegate.qml 2015-01-23 15:31:27 +0000
126@@ -22,6 +22,8 @@
127 import Ubuntu.Telephony 0.1
128 import Ubuntu.Contacts 0.1
129 import QtContacts 5.0
130+import Ubuntu.History 0.1
131+import "dateUtils.js" as DateUtils
132
133 ListItemWithActions {
134 id: delegate
135@@ -32,6 +34,10 @@
136 property string phoneNumber: delegateHelper.phoneNumber
137 property bool unknownContact: delegateHelper.isUnknown
138 property string threadId: model.threadId
139+ property var displayedEvent: null
140+ property var displayedEventTextAttachments: displayedEvent ? displayedEvent.textMessageAttachments : eventTextAttachments
141+ property var displayedEventTimestamp: displayedEvent ? displayedEvent.timestamp : eventTimestamp
142+ property var displayedEventTextMessage: displayedEvent ? displayedEvent.textMessage : eventTextMessage
143 property string groupChatLabel: {
144 var firstRecipient
145 if (unknownContact) {
146@@ -53,15 +59,15 @@
147 var videoCount = 0
148 var contactCount = 0
149 var attachmentCount = 0
150- for (var i = 0; i < eventTextAttachments.length; i++) {
151- if (startsWith(eventTextAttachments[i].contentType, "text/plain")) {
152- return application.readTextFile(eventTextAttachments[i].filePath)
153- } else if (startsWith(eventTextAttachments[i].contentType, "image/")) {
154+ for (var i = 0; i < displayedEventTextAttachments.length; i++) {
155+ if (startsWith(displayedEventTextAttachments[i].contentType, "text/plain")) {
156+ return application.readTextFile(displayedEventTextAttachments[i].filePath)
157+ } else if (startsWith(displayedEventTextAttachments[i].contentType, "image/")) {
158 imageCount++
159- } else if (startsWith(eventTextAttachments[i].contentType, "video/")) {
160+ } else if (startsWith(displayedEventTextAttachments[i].contentType, "video/")) {
161 videoCount++
162- } else if (startsWith(eventTextAttachments[i].contentType, "text/vcard") ||
163- startsWith(eventTextAttachments[i].contentType, "text/x-vcard")) {
164+ } else if (startsWith(displayedEventTextAttachments[i].contentType, "text/vcard") ||
165+ startsWith(displayedEventTextAttachments[i].contentType, "text/x-vcard")) {
166 contactCount++
167 }
168 }
169@@ -79,26 +85,11 @@
170 if (attachmentCount > 0) {
171 return i18n.tr("Attachment: %1 file", "Attachments: %1 files").arg(attachmentCount)
172 }
173- return eventTextMessage
174+ return displayedEventTextMessage
175 }
176 anchors.left: parent.left
177 anchors.right: parent.right
178 height: units.gu(10)
179- // WORKAROUND: history-service can't filter by contact names
180- onSearchTermChanged: {
181- var found = false
182- var searchTermLowerCase = searchTerm.toLowerCase()
183- if (searchTerm !== "") {
184- if ((delegateHelper.phoneNumber.toLowerCase().search(searchTermLowerCase) !== -1)
185- || (!unknownContact && delegateHelper.alias.toLowerCase().search(searchTermLowerCase) !== -1)) {
186- found = true
187- }
188- } else {
189- found = true
190- }
191-
192- height = found ? units.gu(8) : 0
193- }
194
195 leftSideAction: Action {
196 iconName: "delete"
197@@ -108,6 +99,12 @@
198 }
199 }
200
201+ Component.onCompleted: {
202+ if (searchTerm !== "") {
203+ delegateHelper.updateSearch()
204+ }
205+ }
206+
207 ContactAvatar {
208 id: avatar
209
210@@ -151,7 +148,13 @@
211 right: parent.right
212 }
213
214- text: Qt.formatTime(eventTimestamp, Qt.DefaultLocaleShortDate)
215+ text: {
216+ if (!displayedEvent) {
217+ Qt.formatTime(displayedEventTimestamp, Qt.DefaultLocaleShortDate)
218+ } else {
219+ DateUtils.friendlyDay(Qt.formatDate(displayedEventTimestamp, "yyyy/MM/dd"))
220+ }
221+ }
222 fontSize: "small"
223 }
224
225@@ -215,6 +218,66 @@
226 property alias contexts: phoneDetail.contexts
227 property bool isUnknown: contactId === ""
228 property string phoneNumberSubTypeLabel: ""
229+ property var searchHistoryFilter: HistoryIntersectionFilter {
230+ HistoryFilter { filterProperty: "threadId"; filterValue: model.threadId }
231+ HistoryFilter { filterProperty: "accountId"; filterValue: model.accountId }
232+ HistoryFilter { filterProperty: "message"; filterValue: searchTerm; matchFlags: HistoryFilter.MatchContains }
233+ }
234+
235+ function updateSearch() {
236+ var found = false
237+ var searchTermLowerCase = searchTerm.toLowerCase()
238+ if (searchTerm !== "") {
239+ if ((delegateHelper.phoneNumber.toLowerCase().search(searchTermLowerCase) !== -1)
240+ || (!unknownContact && delegateHelper.alias.toLowerCase().search(searchTermLowerCase) !== -1)) {
241+ found = true
242+ } else {
243+ searchEventModelLoader.active = true
244+ }
245+ } else {
246+ delegate.displayedEvent = null
247+ searchEventModelLoader.active = false
248+ found = true
249+ }
250+
251+ delegate.height = found ? units.gu(8) : 0
252+ }
253+
254+ // WORKAROUND: history-service can't filter by contact names
255+ Connections {
256+ target: delegate
257+ onSearchTermChanged: {
258+ delegateHelper.updateSearch()
259+ }
260+ }
261+
262+ Loader {
263+ id: searchEventModelLoader
264+ active: false
265+ asynchronous: true
266+ sourceComponent: searchEventModelComponent
267+ }
268+
269+ Component {
270+ id: searchEventModelComponent
271+ HistoryEventModel {
272+ id: eventModel
273+ type: HistoryThreadModel.EventTypeText
274+ filter: delegateHelper.searchHistoryFilter
275+ onCountChanged: {
276+ if (count > 0) {
277+ delegate.height = units.gu(8)
278+ delegate.displayedEvent = eventModel.get(0)
279+ } else if (searchTerm == "") {
280+ delegate.height = units.gu(8)
281+ delegate.displayedEvent = null
282+ } else {
283+ delegate.displayedEvent = null
284+ delegate.height = 0
285+ }
286+ }
287+ }
288+ }
289
290 function updateSubTypeLabel() {
291 var subLabel = "";
292
293=== modified file 'tests/autopilot/messaging_app/tests/test_messaging.py'
294--- tests/autopilot/messaging_app/tests/test_messaging.py 2014-12-09 17:46:14 +0000
295+++ tests/autopilot/messaging_app/tests/test_messaging.py 2015-01-23 15:31:27 +0000
296@@ -22,6 +22,8 @@
297 from messaging_app import helpers
298 from messaging_app.tests import MessagingAppTestCase
299
300+import ubuntuuitoolkit
301+
302
303 class BaseMessagingTestCase(MessagingAppTestCase):
304
305@@ -298,6 +300,45 @@
306 list_view = self.main_view.get_multiple_selection_list_view()
307 self.assertThat(list_view.count, Eventually(Equals(0)))
308
309+ def test_search_for_message(self):
310+ def count_visible_threads(threads):
311+ count = 0
312+ for thread in threads:
313+ if thread.height != 0:
314+ count += 1
315+ return count
316+
317+ # populate history
318+ helpers.receive_sms('08151', 'Ubuntu')
319+ helpers.receive_sms('08152', 'Ubuntu1')
320+ helpers.receive_sms('08153', 'Ubuntu2')
321+ helpers.receive_sms('08154', 'Ubuntu22')
322+ helpers.receive_sms('08155', 'Ubuntu23')
323+
324+ # verify that we got the messages
325+ self.assertThat(self.thread_list.count, Eventually(Equals(5)))
326+ threads = self.thread_list.select_many('ThreadDelegate')
327+
328+ # tap search
329+ self.main_view.click_header_action('searchAction')
330+ text_field = self.main_view.select_single(
331+ ubuntuuitoolkit.TextField,
332+ objectName='searchField')
333+
334+ text_field.write('Ubuntu2')
335+ self.assertThat(
336+ lambda: count_visible_threads(threads), Eventually(Equals(3)))
337+
338+ text_field.clear()
339+ text_field.write('Ubuntu1')
340+ self.assertThat(
341+ lambda: count_visible_threads(threads), Eventually(Equals(1)))
342+
343+ text_field.clear()
344+ text_field.write('Ubuntu')
345+ self.assertThat(
346+ lambda: count_visible_threads(threads), Eventually(Equals(5)))
347+
348
349 class MessagingTestCaseWithExistingThread(BaseMessagingTestCase):
350

Subscribers

People subscribed via source and target branches