Merge lp:~tiagosh/messaging-app/fix-1376793 into lp:messaging-app
- fix-1376793
- Merge into trunk
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 | ||||
Related bugs: |
|
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:/
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:/
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
PS Jenkins bot (ps-jenkins) wrote : | # |
- 286. By Tiago Salem Herrmann
-
display information about the matched event
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:286
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 287. By Tiago Salem Herrmann
-
scroll to the item that matched the search
- 288. By Tiago Salem Herrmann
-
merge trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:288
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Gustavo Pichorim Boiko (boiko) wrote : | # |
173 + HistoryEventModel {
Can you create this inside a loader so that when not searching there is no memory penalty?
- 289. By Tiago Salem Herrmann
-
use a Loader to avoid creating instances of the model when not required
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:289
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 290. By Tiago Salem Herrmann
-
- make loader asynchronous
- fix dates when searching - 291. By Tiago Salem Herrmann
-
show spinner while searching
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:291
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 292. By Tiago Salem Herrmann
-
update search when delegates are created
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:292
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 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
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:294
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
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!
- 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
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
I think it would be better to write a function to count the threads and use:
self.assertThat
so that we don't have to rely on timers.
- 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
Gustavo Pichorim Boiko (boiko) wrote : | # |
Looks good now!
Preview Diff
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 |
FAILED: Continuous integration, rev:285 jenkins. qa.ubuntu. com/job/ messaging- app-ci/ 500/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- vivid-touch/ 714 jenkins. qa.ubuntu. com/job/ generic- mediumtests- vivid/374 jenkins. qa.ubuntu. com/job/ messaging- app-vivid- i386-ci/ 13 jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- runner- vivid-mako/ 618 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 712 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 712/artifact/ work/output/ *zip*/output. zip s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 16911 jenkins. qa.ubuntu. com/job/ autopilot- testrunner- otto-vivid/ 307 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-amd64/ 380 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-amd64/ 380/artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/messaging- app-ci/ 500/rebuild
http://