Merge lp:~renatofilho/address-book-app/save-state into lp:address-book-app
- save-state
- Merge into trunk
Status: | Work in progress |
---|---|
Proposed branch: | lp:~renatofilho/address-book-app/save-state |
Merge into: | lp:address-book-app |
Diff against target: |
962 lines (+457/-54) 20 files modified
debian/control (+1/-0) src/app/addressbookapp.cpp (+12/-0) src/app/addressbookapp.h (+1/-0) src/imports/Common/CMakeLists.txt (+1/-0) src/imports/Common/ContactPage.qml (+34/-0) src/imports/ContactEdit/CMakeLists.txt (+1/-1) src/imports/ContactEdit/ContactDetailGroupWithTypeEditor.qml (+21/-6) src/imports/ContactEdit/ContactDetailNameEditor.qml (+4/-7) src/imports/ContactEdit/ContactDetailWithTypeEditor.qml (+7/-16) src/imports/ContactEdit/ContactEditPage.qml (+91/-5) src/imports/ContactEdit/TextInputDetail.qml (+9/-0) src/imports/ContactEdit/ValueSelector.qml (+7/-1) src/imports/ContactList/ContactListPage.qml (+34/-6) src/imports/ContactView/CMakeLists.txt (+1/-1) src/imports/ContactView/ContactViewPage.qml (+22/-5) src/imports/MainWindow.qml (+3/-1) src/imports/Ubuntu/Contacts/ContactFetch.qml (+2/-0) tests/autopilot/address_book_app/emulators/main_window.py (+2/-2) tests/autopilot/address_book_app/tests/__init__.py (+43/-3) tests/autopilot/address_book_app/tests/test_save_state.py (+161/-0) |
To merge this branch: | bzr merge lp:~renatofilho/address-book-app/save-state |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Bill Filler | Pending | ||
Review via email: mp+206083@code.launchpad.net |
This proposal supersedes a proposal from 2014-01-09.
Commit message
save state
Description of the change
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:119
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:119
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:120
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Bill Filler (bfiller) wrote : Posted in a previous version of this proposal | # |
Did some testing, found some issues:
1) Add new contact and then quit app before filling in any fields. When restore app, the focused is given to the Home address field and the view is scrolled down to there. Focus should either be at the first name field at the top or should be with whatever field last had focus
2) If I edit a contact, and put cursor in email field then quit the app I get the main list when restore, not the edited contact. Seems this only happens when a contact doesn't have any phone numbers.
3) edit a contact and restore, focus always put in the phone number field even if that's not what last had focus. If no phone number the contact is not displayed.
4) scroll position in the main list is not maintained after restore. is this a known bug in state saver? if so, what is bug number.
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:122
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:122
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Unmerged revisions
- 122. By Renato Araujo Oliveira Filho
-
Removed debug messages.
- 121. By Renato Araujo Oliveira Filho
-
Move focust to name field after restore the state on edit mode.
- 120. By Renato Araujo Oliveira Filho
-
Removed debug messages.
- 119. By Renato Araujo Oliveira Filho
-
Fixed name detail save.
- 118. By Renato Araujo Oliveira Filho
-
Created test to save state on create contact state.
- 117. By Renato Araujo Oliveira Filho
-
Fixed autopilot test with the new component type.
- 116. By Renato Araujo Oliveira Filho
-
Save state implementation.
Preview Diff
1 | === modified file 'debian/control' |
2 | --- debian/control 2013-12-11 15:03:32 +0000 |
3 | +++ debian/control 2014-02-13 02:55:34 +0000 |
4 | @@ -69,5 +69,6 @@ |
5 | libqt5widgets5, |
6 | ubuntu-ui-toolkit-autopilot, |
7 | address-book-app (>= ${binary:Version}), |
8 | + unity8-autopilot, |
9 | Description: Test package for address-book-app |
10 | Autopilot tests for the address-book-app package |
11 | |
12 | === modified file 'src/app/addressbookapp.cpp' |
13 | --- src/app/addressbookapp.cpp 2014-01-10 15:07:11 +0000 |
14 | +++ src/app/addressbookapp.cpp 2014-02-13 02:55:34 +0000 |
15 | @@ -225,6 +225,18 @@ |
16 | } |
17 | } |
18 | |
19 | +QUrl AddressBookApp::tempFile(const QString &templateName) |
20 | +{ |
21 | + // Create the temporary file |
22 | + QTemporaryFile tmpFile(templateName); |
23 | + tmpFile.setAutoRemove(false); |
24 | + if (tmpFile.open()) { |
25 | + return QUrl::fromLocalFile(tmpFile.fileName()); |
26 | + } |
27 | + qWarning() << "Fail to create temp file for:" << templateName; |
28 | + return QUrl(); |
29 | +} |
30 | + |
31 | void AddressBookApp::parseUrl(const QString &arg) |
32 | { |
33 | QUrl url = QUrl::fromPercentEncoding(arg.toUtf8()); |
34 | |
35 | === modified file 'src/app/addressbookapp.h' |
36 | --- src/app/addressbookapp.h 2013-12-12 15:55:19 +0000 |
37 | +++ src/app/addressbookapp.h 2014-02-13 02:55:34 +0000 |
38 | @@ -39,6 +39,7 @@ |
39 | void parseUrl(const QString &arg); |
40 | void onViewStatusChanged(QQuickView::Status status); |
41 | void returnVcard(const QUrl &url); |
42 | + QUrl tempFile(const QString &baseName); |
43 | |
44 | private: |
45 | void callQMLMethod(const QString name, QStringList args); |
46 | |
47 | === modified file 'src/imports/Common/CMakeLists.txt' |
48 | --- src/imports/Common/CMakeLists.txt 2013-07-31 19:36:55 +0000 |
49 | +++ src/imports/Common/CMakeLists.txt 2014-02-13 02:55:34 +0000 |
50 | @@ -3,6 +3,7 @@ |
51 | ContactDetailItem.qml |
52 | ContactDetailGroupBase.qml |
53 | ContactDetailGroupWithTypeBase.qml |
54 | + ContactPage.qml |
55 | ) |
56 | |
57 | install(FILES ${CONTACT_COMMON_QMLS} |
58 | |
59 | === added file 'src/imports/Common/ContactPage.qml' |
60 | --- src/imports/Common/ContactPage.qml 1970-01-01 00:00:00 +0000 |
61 | +++ src/imports/Common/ContactPage.qml 2014-02-13 02:55:34 +0000 |
62 | @@ -0,0 +1,34 @@ |
63 | +/* |
64 | + * Copyright (C) 2012-2013 Canonical, Ltd. |
65 | + * |
66 | + * This program is free software; you can redistribute it and/or modify |
67 | + * it under the terms of the GNU General Public License as published by |
68 | + * the Free Software Foundation; version 3. |
69 | + * |
70 | + * This program is distributed in the hope that it will be useful, |
71 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
72 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
73 | + * GNU General Public License for more details. |
74 | + * |
75 | + * You should have received a copy of the GNU General Public License |
76 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
77 | + */ |
78 | + |
79 | +import QtQuick 2.0 |
80 | +import Ubuntu.Components 0.1 |
81 | + |
82 | +Page { |
83 | + property string pageName |
84 | + |
85 | + Component.onCompleted: { |
86 | + if (pageStack && pageName) { |
87 | + pageStack.currentStack += ";" + pageName |
88 | + } |
89 | + } |
90 | + |
91 | + Component.onDestruction: { |
92 | + if (pageStack && pageName) { |
93 | + pageStack.currentStack = pageStack.currentStack.replace(";" + pageName, "") |
94 | + } |
95 | + } |
96 | +} |
97 | |
98 | === modified file 'src/imports/ContactEdit/CMakeLists.txt' |
99 | --- src/imports/ContactEdit/CMakeLists.txt 2013-09-24 15:07:46 +0000 |
100 | +++ src/imports/ContactEdit/CMakeLists.txt 2014-02-13 02:55:34 +0000 |
101 | @@ -8,7 +8,7 @@ |
102 | ContactDetailOrganizationsEditor.qml |
103 | ContactDetailPhoneNumbersEditor.qml |
104 | ContactDetailWithTypeEditor.qml |
105 | - ContactEditor.qml |
106 | + ContactEditPage.qml |
107 | ContactFetchError.qml |
108 | EditToolbar.qml |
109 | KeyboardRectangle.qml |
110 | |
111 | === modified file 'src/imports/ContactEdit/ContactDetailGroupWithTypeEditor.qml' |
112 | --- src/imports/ContactEdit/ContactDetailGroupWithTypeEditor.qml 2014-01-14 20:43:59 +0000 |
113 | +++ src/imports/ContactEdit/ContactDetailGroupWithTypeEditor.qml 2014-02-13 02:55:34 +0000 |
114 | @@ -38,8 +38,22 @@ |
115 | root.newDetails = [] |
116 | } |
117 | |
118 | + function updateType() { |
119 | + for(var i=0; i < detailDelegates.length; i++) { |
120 | + var delegate = detailDelegates[i] |
121 | + // Get item from Loader |
122 | + if (delegate.item) { |
123 | + delegate = delegate.item |
124 | + } |
125 | + if (delegate.save) { |
126 | + return updateDetail(delegate.detail, delegate.selectedTypeIndex) |
127 | + } |
128 | + } |
129 | + return false |
130 | + } |
131 | + |
132 | function save() { |
133 | - var changed = false |
134 | + var changed = updateType() |
135 | for(var i=0; i < detailDelegates.length; i++) { |
136 | var delegate = detailDelegates[i] |
137 | |
138 | @@ -49,11 +63,6 @@ |
139 | } |
140 | |
141 | if (delegate.save) { |
142 | - // save type |
143 | - if (updateDetail(delegate.detail, delegate.selectedTypeIndex)) { |
144 | - changed = true |
145 | - } |
146 | - |
147 | // save fields |
148 | if (delegate.save()) { |
149 | changed = true |
150 | @@ -63,6 +72,12 @@ |
151 | return changed |
152 | } |
153 | |
154 | + onActiveFocusChanged: { |
155 | + if (!activeFocus) { |
156 | + updateType() |
157 | + } |
158 | + } |
159 | + |
160 | focus: true |
161 | minimumHeight: units.gu(5) |
162 | headerDelegate: ListItem.Empty { |
163 | |
164 | === modified file 'src/imports/ContactEdit/ContactDetailNameEditor.qml' |
165 | --- src/imports/ContactEdit/ContactDetailNameEditor.qml 2014-01-23 01:09:04 +0000 |
166 | +++ src/imports/ContactEdit/ContactDetailNameEditor.qml 2014-02-13 02:55:34 +0000 |
167 | @@ -27,9 +27,9 @@ |
168 | property variant emptyFields: [] |
169 | |
170 | function save() { |
171 | - var changed = false; |
172 | + var detailChanged = false |
173 | |
174 | - for(var i=0; i < fieldDelegates.length; i++) { |
175 | + for (var i=0; i < fieldDelegates.length; i++) { |
176 | var delegate = fieldDelegates[i] |
177 | |
178 | // Get item from Loader |
179 | @@ -38,14 +38,11 @@ |
180 | } |
181 | |
182 | if (delegate.detail && (delegate.field >= 0)) { |
183 | - if (delegate.text != delegate.detail.value(delegate.field)) { |
184 | - delegate.detail.setValue(delegate.field, delegate.text) |
185 | - changed = true; |
186 | - } |
187 | + detailChanged = detailChanged || delegate.changed |
188 | } |
189 | } |
190 | |
191 | - return changed |
192 | + return detailChanged |
193 | } |
194 | |
195 | detail: root.contact ? root.contact.name : null |
196 | |
197 | === modified file 'src/imports/ContactEdit/ContactDetailWithTypeEditor.qml' |
198 | --- src/imports/ContactEdit/ContactDetailWithTypeEditor.qml 2013-11-22 01:05:52 +0000 |
199 | +++ src/imports/ContactEdit/ContactDetailWithTypeEditor.qml 2014-02-13 02:55:34 +0000 |
200 | @@ -37,26 +37,18 @@ |
201 | } |
202 | |
203 | function save() { |
204 | - var detailchanged = false |
205 | - |
206 | - // save field values |
207 | + var detailChanged = detailTypeSelector.changed |
208 | var isEmpty = true |
209 | + |
210 | for (var i=0; i < fieldValues.children.length; i++) { |
211 | var input = fieldValues.children[i] |
212 | if (input.detail && (input.field >= 0)) { |
213 | - var originalValue = input.detail.value(input.field) |
214 | - originalValue = originalValue ? String(originalValue) : "" |
215 | - if (input.text !== "") { |
216 | - isEmpty = false |
217 | - } |
218 | - |
219 | - if (originalValue !== input.text) { |
220 | - input.detail.setValue(input.field, input.text) |
221 | - detailchanged = true |
222 | - } |
223 | + isEmpty = isEmpty && input.isEmpty |
224 | + detailChanged = detailChanged || input.changed |
225 | } |
226 | } |
227 | |
228 | + // remove empty detail |
229 | if (isEmpty) { |
230 | // unfavorite the contact if the favorite number was removed |
231 | if (contact.isPreferredDetail("TEL", detail)) { |
232 | @@ -64,8 +56,7 @@ |
233 | } |
234 | contact.removeDetail(input.detail) |
235 | } |
236 | - |
237 | - return detailchanged |
238 | + return detailChanged |
239 | } |
240 | |
241 | focus: true |
242 | @@ -91,7 +82,7 @@ |
243 | |
244 | height: visible ? (root.active ? units.gu(4) : units.gu(3)) : 0 |
245 | onExpandedChanged: { |
246 | - // Make sure that the inputfield get focus when clicking on type selector |
247 | + // Make sure that the input field get focus when clicking on type selector |
248 | if (expanded) { |
249 | root.forceActiveFocus() |
250 | } |
251 | |
252 | === renamed file 'src/imports/ContactEdit/ContactEditor.qml' => 'src/imports/ContactEdit/ContactEditPage.qml' |
253 | --- src/imports/ContactEdit/ContactEditor.qml 2013-11-21 12:14:04 +0000 |
254 | +++ src/imports/ContactEdit/ContactEditPage.qml 2014-02-13 02:55:34 +0000 |
255 | @@ -19,14 +19,22 @@ |
256 | import Ubuntu.Components 0.1 |
257 | import Ubuntu.Components.ListItems 0.1 as ListItem |
258 | import Ubuntu.Components.Popups 0.1 |
259 | +import "../Common" |
260 | |
261 | -Page { |
262 | +ContactPage { |
263 | id: contactEditor |
264 | objectName: "contactEditorPage" |
265 | |
266 | property QtObject contact: null |
267 | property QtObject model: null |
268 | |
269 | + // state saver control |
270 | + property string persistentContactId: "" |
271 | + property string persistentContactVCardFile: "" |
272 | + |
273 | + readonly property bool appActive: Qt.application.active |
274 | + property QtObject pendingContact: null |
275 | + |
276 | // this is used to add a phone number to a existing contact |
277 | property int currentFetchOperation: -1 |
278 | property string contactId: "" |
279 | @@ -34,6 +42,9 @@ |
280 | |
281 | property QtObject activeItem: null |
282 | |
283 | + pageName: "contactEditPage" |
284 | + StateSaver.properties: "persistentContactVCardFile, persistentContactId" |
285 | + |
286 | // we use a custom toolbar in this view |
287 | tools: ToolbarItems { |
288 | locked: true |
289 | @@ -110,6 +121,63 @@ |
290 | scrollArea.returnToBounds() |
291 | } |
292 | |
293 | + function saveState() { |
294 | + var fileName = "savedContact_XXXXXX.vcf" |
295 | + fileName = application.tempFile(fileName); |
296 | + if (fileName != "") { |
297 | + // save current contact info |
298 | + model.exportContacts(fileName, ["Sync", "Backup"], [contact]) |
299 | + persistentContactVCardFile = fileName |
300 | + } |
301 | + if (contact.contactId != "qtcontacts:::") { |
302 | + persistentContactId = contact.contactId |
303 | + } else { |
304 | + persistentContactId = "" |
305 | + } |
306 | + pendingContact = null |
307 | + } |
308 | + |
309 | + function restoreState() { |
310 | + // if was editing a contact load the contact first |
311 | + if (persistentContactId != "") { |
312 | + contactEditor.contactId = persistentContactId |
313 | + } else if (persistentContactVCardFile != "") { |
314 | + importerModel.importContacts(persistentContactVCardFile, ["Sync", "Backup"]) |
315 | + } |
316 | + } |
317 | + |
318 | + onAppActiveChanged: if (!appActive) saveState(); |
319 | + |
320 | + ContactModel { |
321 | + id: importerModel |
322 | + |
323 | + manager: "memory" |
324 | + onContactsChanged: { |
325 | + var contact = importerModel.contacts[0] |
326 | + var newContact = null |
327 | + |
328 | + if (contactEditor.pendingContact) { |
329 | + newContact = contactEditor.pendingContact |
330 | + newContact.clearDetails() |
331 | + } else { |
332 | + // is a new contact we need to remove the contactId |
333 | + newContact = Qt.createQmlObject("import QtContacts 5.0; Contact{}", contactEditor) |
334 | + } |
335 | + |
336 | + var details = contact.contactDetails |
337 | + for(var index in details) { |
338 | + newContact.addDetail(details[index]) |
339 | + } |
340 | + |
341 | + contactEditor.contact = newContact |
342 | + contactEditor.pendingContact = null |
343 | + contactEditor.persistentContactVCardFile = "" |
344 | + |
345 | + // move focus to first field until SDK implement a way to save it |
346 | + nameEditor.forceActiveFocus() |
347 | + } |
348 | + } |
349 | + |
350 | ContactFetchError { |
351 | id: fetchErrorDialog |
352 | } |
353 | @@ -119,16 +187,26 @@ |
354 | onContactsFetched: { |
355 | if (requestId == currentFetchOperation) { |
356 | currentFetchOperation = -1 |
357 | + |
358 | // this fetch request can only return one contact |
359 | if(fetchedContacts.length !== 1) { |
360 | PopupUtils.open(fetchErrorDialog, null) |
361 | } |
362 | - contact = fetchedContacts[0] |
363 | + |
364 | + if (persistentContactVCardFile != "") { |
365 | + // we need to merge the contact information with the saved state vcard |
366 | + contactEditor.pendingContact = fetchedContacts[0] |
367 | + persistentContactId = "" |
368 | + // go to second step of state save restore |
369 | + contactEditor.restoreState() |
370 | + } else { |
371 | + contact = fetchedContacts[0] |
372 | + } |
373 | } |
374 | } |
375 | } |
376 | |
377 | - onContactIdChanged: { |
378 | + onContactIdChanged: { |
379 | if (contactId) { |
380 | currentFetchOperation = model.fetchContacts(contactId) |
381 | } |
382 | @@ -286,8 +364,6 @@ |
383 | } |
384 | } |
385 | |
386 | - Component.onCompleted: nameEditor.forceActiveFocus() |
387 | - |
388 | ActivityIndicator { |
389 | id: busyIndicator |
390 | |
391 | @@ -358,4 +434,14 @@ |
392 | } |
393 | } |
394 | } |
395 | + |
396 | + Component.onCompleted: { |
397 | + nameEditor.forceActiveFocus() |
398 | + contactEditor.restoreState() |
399 | + } |
400 | + |
401 | + Component.onDestruction: { |
402 | + contactEditor.persistentContactId = "" |
403 | + contactEditor.persistentContactVCardFile = "" |
404 | + } |
405 | } |
406 | |
407 | === modified file 'src/imports/ContactEdit/TextInputDetail.qml' |
408 | --- src/imports/ContactEdit/TextInputDetail.qml 2013-10-08 12:58:44 +0000 |
409 | +++ src/imports/ContactEdit/TextInputDetail.qml 2014-02-13 02:55:34 +0000 |
410 | @@ -26,10 +26,13 @@ |
411 | property QtObject detail |
412 | property int field: -1 |
413 | property variant originalValue: root.detail && (root.field >= 0) ? root.detail.value(root.field) : null |
414 | + property bool changed: false |
415 | + readonly property bool isEmpty: (text == "") |
416 | |
417 | signal removeClicked() |
418 | |
419 | Component.onCompleted: makeMeVisible(root) |
420 | + StateSaver.properties: "cursorPosition" |
421 | |
422 | focus: true |
423 | text: originalValue ? originalValue : "" |
424 | @@ -41,6 +44,12 @@ |
425 | onActiveFocusChanged: { |
426 | if (activeFocus) { |
427 | makeMeVisible(root) |
428 | + } else if (root.detail) { |
429 | + var value = root.detail.value(root.field) |
430 | + if (String(value) != root.text) { |
431 | + root.changed = true |
432 | + root.detail.setValue(root.field, root.text) |
433 | + } |
434 | } |
435 | } |
436 | |
437 | |
438 | === modified file 'src/imports/ContactEdit/ValueSelector.qml' |
439 | --- src/imports/ContactEdit/ValueSelector.qml 2013-11-22 00:47:10 +0000 |
440 | +++ src/imports/ContactEdit/ValueSelector.qml 2014-02-13 02:55:34 +0000 |
441 | @@ -21,6 +21,7 @@ |
442 | id: root |
443 | |
444 | property bool active: false |
445 | + property bool changed: false |
446 | property alias values: listView.model |
447 | property alias currentIndex: listView.currentIndex |
448 | readonly property bool expanded: (state === "expanded") && listView.opacity == 1.0 |
449 | @@ -161,7 +162,12 @@ |
450 | width: parent.width + units.gu(0.5) |
451 | height: parent.height + units.gu(0.5) |
452 | anchors.centerIn: parent |
453 | - onClicked: currentIndex = index |
454 | + onClicked: { |
455 | + if (currentIndex != index) { |
456 | + root.changed = true |
457 | + currentIndex = index |
458 | + } |
459 | + } |
460 | } |
461 | } |
462 | |
463 | |
464 | === modified file 'src/imports/ContactList/ContactListPage.qml' |
465 | --- src/imports/ContactList/ContactListPage.qml 2013-12-17 23:47:28 +0000 |
466 | +++ src/imports/ContactList/ContactListPage.qml 2014-02-13 02:55:34 +0000 |
467 | @@ -21,7 +21,9 @@ |
468 | import Ubuntu.Contacts 0.1 as ContactsUI |
469 | import QtContacts 5.0 |
470 | |
471 | -Page { |
472 | +import "../Common" |
473 | + |
474 | +ContactPage { |
475 | id: mainPage |
476 | objectName: "contactListPage" |
477 | |
478 | @@ -48,7 +50,24 @@ |
479 | return newContact |
480 | } |
481 | |
482 | + function restoreState() |
483 | + { |
484 | + // store current page before load contact list because this will overwrite it |
485 | + var stack = pageStack.currentStack.split(";") |
486 | + pageStack.currentStack = "" |
487 | + for (var index in stack) { |
488 | + var page = stack[index] |
489 | + // load previous state/page |
490 | + if (page == "contactEditPage") { |
491 | + pageStack.push(Qt.resolvedUrl("../ContactEdit/ContactEditPage.qml"), |
492 | + {model: contactList.listModel}) |
493 | + } else if (page == "contactViewPage") { |
494 | + pageStack.push(Qt.resolvedUrl("../ContactView/ContactViewPage.qml"), |
495 | + {model: contactList.listModel}) |
496 | + } |
497 | + } |
498 | |
499 | + } |
500 | |
501 | title: i18n.tr("Contacts") |
502 | Component { |
503 | @@ -97,7 +116,7 @@ |
504 | } |
505 | |
506 | onContactClicked: { |
507 | - pageStack.push(Qt.resolvedUrl("../ContactView/ContactView.qml"), |
508 | + pageStack.push(Qt.resolvedUrl("../ContactView/ContactViewPage.qml"), |
509 | {model: contactList.listModel, contactId: contact.contactId}) |
510 | } |
511 | |
512 | @@ -118,6 +137,7 @@ |
513 | contactList.listModel.removeContacts(ids) |
514 | } |
515 | } |
516 | + |
517 | onSelectionCanceled: { |
518 | if (pickMode) { |
519 | if (contactContentHub) { |
520 | @@ -154,7 +174,7 @@ |
521 | iconSource: "artwork:/add.png" |
522 | onTriggered: { |
523 | var newContact = mainPage.createEmptyContact("") |
524 | - pageStack.push(Qt.resolvedUrl("../ContactEdit/ContactEditor.qml"), |
525 | + pageStack.push(Qt.resolvedUrl("../ContactEdit/ContactEditPage.qml"), |
526 | {model: contactList.listModel, contact: newContact}) |
527 | } |
528 | } |
529 | @@ -164,16 +184,16 @@ |
530 | Connections { |
531 | target: pageStack |
532 | onContactRequested: { |
533 | - pageStack.push(Qt.resolvedUrl("../ContactView/ContactView.qml"), |
534 | + pageStack.push(Qt.resolvedUrl("../ContactView/ContactViewPage.qml"), |
535 | {model: contactList.listModel, contactId: contactId}) |
536 | } |
537 | onCreateContactRequested: { |
538 | var newContact = mainPage.createEmptyContact(phoneNumber) |
539 | - pageStack.push(Qt.resolvedUrl("../ContactEdit/ContactEditor.qml"), |
540 | + pageStack.push(Qt.resolvedUrl("../ContactEdit/ContactEditPage.qml"), |
541 | {model: contactList.listModel, contact: newContact}) |
542 | } |
543 | onEditContatRequested: { |
544 | - pageStack.push(Qt.resolvedUrl("../ContactEdit/ContactEditor.qml"), |
545 | + pageStack.push(Qt.resolvedUrl("../ContactEdit/ContactEditPage.qml"), |
546 | {model: contactList.listModel, contactId: contactId, newPhoneNumber: phoneNumber }) |
547 | } |
548 | onContactCreated: { |
549 | @@ -206,4 +226,12 @@ |
550 | contactList.listModel.importContacts("file://" + TEST_DATA) |
551 | } |
552 | } |
553 | + |
554 | + // WORKAROUND: Page stack fails to push page using Component.onCompleted. Check bug #1262224 |
555 | + Timer { |
556 | + interval: 500 |
557 | + running: true |
558 | + repeat: false |
559 | + onTriggered: restoreState() |
560 | + } |
561 | } |
562 | |
563 | === modified file 'src/imports/ContactView/CMakeLists.txt' |
564 | --- src/imports/ContactView/CMakeLists.txt 2013-10-21 19:25:51 +0000 |
565 | +++ src/imports/ContactView/CMakeLists.txt 2014-02-13 02:55:34 +0000 |
566 | @@ -13,7 +13,7 @@ |
567 | ContactDetailPhoneNumberView.qml |
568 | ContactDetailWithTypeView.qml |
569 | ContactHeaderView.qml |
570 | - ContactView.qml |
571 | + ContactViewPage.qml |
572 | ) |
573 | |
574 | install(FILES ${CONTACT_VIEW_QMLS} |
575 | |
576 | === renamed file 'src/imports/ContactView/ContactView.qml' => 'src/imports/ContactView/ContactViewPage.qml' |
577 | --- src/imports/ContactView/ContactView.qml 2013-11-14 17:48:48 +0000 |
578 | +++ src/imports/ContactView/ContactViewPage.qml 2014-02-13 02:55:34 +0000 |
579 | @@ -19,13 +19,15 @@ |
580 | import Ubuntu.Components 0.1 |
581 | import Ubuntu.Components.ListItems 0.1 as ListItem |
582 | import Ubuntu.Contacts 0.1 as ContactsUI |
583 | +import "../Common" |
584 | |
585 | -Page { |
586 | +ContactPage { |
587 | id: root |
588 | objectName: "contactViewPage" |
589 | |
590 | readonly property alias contact: contactFetch.contact |
591 | - property variant contactId: null |
592 | + property string contactId: "" |
593 | + property string savedContactId: "" |
594 | property alias model: contactFetch.model |
595 | |
596 | function formatNameToDisplay(contact) { |
597 | @@ -43,12 +45,21 @@ |
598 | } |
599 | } |
600 | |
601 | + onSavedContactIdChanged: { |
602 | + if (root.contactId == "") { |
603 | + root.contactId = savedContactId |
604 | + contactFetch.fetchContact(savedContactId) |
605 | + } |
606 | + } |
607 | |
608 | + pageName: "contactViewPage" |
609 | + StateSaver.properties: "savedContactId" |
610 | title: formatNameToDisplay(contact) |
611 | onActiveChanged: { |
612 | if (active) { |
613 | - contactFetch.fetchContact(root.contactId) |
614 | - |
615 | + if (root.contactId != "") { |
616 | + contactFetch.fetchContact(root.contactId) |
617 | + } |
618 | //WORKAROUND: to correct scroll back the page |
619 | flickable.contentY = -100 |
620 | flickable.returnToBounds() |
621 | @@ -147,6 +158,7 @@ |
622 | id: contactFetch |
623 | |
624 | onContactRemoved: pageStack.pop() |
625 | + onContactFetched: root.savedContactId = contact.contactId |
626 | } |
627 | |
628 | tools: ToolbarItems { |
629 | @@ -169,10 +181,15 @@ |
630 | text: i18n.tr("Edit") |
631 | iconSource: "artwork:/edit.png" |
632 | onTriggered: { |
633 | - pageStack.push(Qt.resolvedUrl("../ContactEdit/ContactEditor.qml"), |
634 | + pageStack.push(Qt.resolvedUrl("../ContactEdit/ContactEditPage.qml"), |
635 | { model: root.model, contact: root.contact}) |
636 | } |
637 | } |
638 | } |
639 | } |
640 | + |
641 | + Component.onDestruction: { |
642 | + // avoid save contact Id by stateSave |
643 | + savedContactId = "" |
644 | + } |
645 | } |
646 | |
647 | === modified file 'src/imports/MainWindow.qml' |
648 | --- src/imports/MainWindow.qml 2013-12-17 14:57:28 +0000 |
649 | +++ src/imports/MainWindow.qml 2014-02-13 02:55:34 +0000 |
650 | @@ -49,6 +49,8 @@ |
651 | id: mainStack |
652 | |
653 | property string newPhoneNumber: "" |
654 | + property string currentStack |
655 | + StateSaver.properties: "currentStack" |
656 | |
657 | signal contactRequested(string contactId) |
658 | signal createContactRequested(string phoneNumber) |
659 | @@ -68,7 +70,7 @@ |
660 | |
661 | Component.onCompleted: { |
662 | Theme.name = "Ubuntu.Components.Themes.SuruGradient" |
663 | - mainStack.push(Qt.createComponent("ContactList/ContactListPage.qml")) |
664 | + mainStack.push(Qt.resolvedUrl("ContactList/ContactListPage.qml")) |
665 | mainWindow.applicationReady() |
666 | } |
667 | |
668 | |
669 | === modified file 'src/imports/Ubuntu/Contacts/ContactFetch.qml' |
670 | --- src/imports/Ubuntu/Contacts/ContactFetch.qml 2013-11-04 14:56:40 +0000 |
671 | +++ src/imports/Ubuntu/Contacts/ContactFetch.qml 2014-02-13 02:55:34 +0000 |
672 | @@ -23,6 +23,7 @@ |
673 | property bool running: false |
674 | property QtObject contact: null |
675 | property bool contactIsDirty: false |
676 | + property string contactId: "" |
677 | |
678 | property string _pendingId: "" |
679 | property bool _ready: false |
680 | @@ -31,6 +32,7 @@ |
681 | signal contactRemoved() |
682 | |
683 | function fetchContact(contactId) { |
684 | + root.contactId = contactId |
685 | if (root._ready) { |
686 | root._fetchContact(contactId) |
687 | } else { |
688 | |
689 | === modified file 'tests/autopilot/address_book_app/emulators/main_window.py' |
690 | --- tests/autopilot/address_book_app/emulators/main_window.py 2013-12-12 15:55:57 +0000 |
691 | +++ tests/autopilot/address_book_app/emulators/main_window.py 2014-02-13 02:55:34 +0000 |
692 | @@ -16,11 +16,11 @@ |
693 | objectName="contactListPage") |
694 | |
695 | def get_contact_edit_page(self): |
696 | - return self.wait_select_single("ContactEditor", |
697 | + return self.wait_select_single("ContactEditPage", |
698 | objectName="contactEditorPage") |
699 | |
700 | def get_contact_view_page(self): |
701 | - return self.wait_select_single("ContactView", |
702 | + return self.wait_select_single("ContactViewPage", |
703 | objectName="contactViewPage") |
704 | |
705 | def get_contact_list_pick_page(self): |
706 | |
707 | === modified file 'tests/autopilot/address_book_app/tests/__init__.py' |
708 | --- tests/autopilot/address_book_app/tests/__init__.py 2014-01-14 20:44:25 +0000 |
709 | +++ tests/autopilot/address_book_app/tests/__init__.py 2014-02-13 02:55:34 +0000 |
710 | @@ -11,10 +11,13 @@ |
711 | import os |
712 | import time |
713 | import subprocess |
714 | +import signal |
715 | |
716 | from autopilot.testcase import AutopilotTestCase |
717 | from autopilot.matchers import Eventually |
718 | from autopilot.platform import model |
719 | +from autopilot.introspection import get_proxy_object_for_existing_process |
720 | + |
721 | from testtools.matchers import Equals |
722 | |
723 | from address_book_app.emulators.main_window import MainWindow |
724 | @@ -30,6 +33,11 @@ |
725 | ARGS = [] |
726 | PRELOAD_VCARD = False |
727 | |
728 | + def do_reset_config(self): |
729 | + config = os.path.expanduser(os.path.join("~", ".config", "AddressBookApp.conf")) |
730 | + if os.path.exists(config): |
731 | + os.remove(config) |
732 | + |
733 | def setUp(self): |
734 | self.pointing_device = toolkit_emulators.get_pointing_device() |
735 | super(AddressBookAppTestCase, self).setUp() |
736 | @@ -43,7 +51,13 @@ |
737 | else: |
738 | self.app_bin = AddressBookAppTestCase.DEFAULT_DEV_LOCATION |
739 | |
740 | + self.do_reset_config() |
741 | + self.start_app() |
742 | + |
743 | + |
744 | + def start_app(self): |
745 | print "Running from: %s" % (self.app_bin) |
746 | + |
747 | os.environ['QTCONTACTS_MANAGER_OVERRIDE'] = 'memory' |
748 | if AddressBookAppTestCase.PRELOAD_VCARD: |
749 | os.environ["ADDRESS_BOOK_TEST_DATA"] = "/usr/share/address-book-app/vcards/vcard.vcf" |
750 | @@ -57,13 +71,13 @@ |
751 | else: |
752 | self.launch_click_installed() |
753 | |
754 | + def tearDown(self): |
755 | + super(AddressBookAppTestCase, self).tearDown() |
756 | + |
757 | AddressBookAppTestCase.ARGS = [] |
758 | AddressBookAppTestCase.PRELOAD_VCARD = False |
759 | self.main_window.visible.wait_for(True) |
760 | |
761 | - def tearDown(self): |
762 | - super(AddressBookAppTestCase, self).tearDown() |
763 | - |
764 | # start the vkb |
765 | if model() != "Desktop": |
766 | subprocess.check_call(["/sbin/initctl", "start", "maliit-server"]) |
767 | @@ -290,3 +304,29 @@ |
768 | # wait for contact list to be visible again |
769 | list_page = self.main_window.get_contact_list_page() |
770 | self.assertThat(list_page.visible, Eventually(Equals(True))) |
771 | + |
772 | + def ensure_app_has_quit(self): |
773 | + """Terminate as gracefully as possible the application and ensure |
774 | + that it has fully quit before returning""" |
775 | + |
776 | + if model() == "Desktop": |
777 | + # make the app lost focus (this will save all info) |
778 | + self.keyboard.press_and_release("Alt+Tab") |
779 | + # kill the app |
780 | + self.app.process.send_signal(signal.SIGTERM) |
781 | + else: |
782 | + # On unity8 at the moment we have no clean way to close the app. |
783 | + # So we ask the shell first to show the home, unfocusing our app, which will |
784 | + # save its state. Then we simply send it a SIGTERM to force it to quit. |
785 | + # See bug https://bugs.launchpad.net/unity8/+bug/1261720 for more details. |
786 | + from unity8 import process_helpers |
787 | + pid = process_helpers._get_unity_pid() |
788 | + unity8 = get_proxy_object_for_existing_process(pid) |
789 | + shell = unity8.select_single("Shell") |
790 | + shell.slots.showHome() |
791 | + self.assertThat(shell.currentFocusedAppId, Eventually(NotEquals("address-book-app"))) |
792 | + self.app.process.send_signal(signal.SIGTERM) |
793 | + |
794 | + # Either way, we wait for the underlying process to be fully finished. |
795 | + self.app.process.wait() |
796 | + self.assertIsNotNone(self.app.process.returncode) |
797 | |
798 | === added file 'tests/autopilot/address_book_app/tests/test_save_state.py' |
799 | --- tests/autopilot/address_book_app/tests/test_save_state.py 1970-01-01 00:00:00 +0000 |
800 | +++ tests/autopilot/address_book_app/tests/test_save_state.py 2014-02-13 02:55:34 +0000 |
801 | @@ -0,0 +1,161 @@ |
802 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
803 | +# Copyright 2013 Canonical |
804 | +# |
805 | +# This program is free software: you can redistribute it and/or modify it |
806 | +# under the terms of the GNU General Public License version 3, as published |
807 | +# by the Free Software Foundation. |
808 | + |
809 | +"""Tests for the Addressbook App""" |
810 | + |
811 | +from __future__ import absolute_import |
812 | + |
813 | +from testtools.matchers import Equals |
814 | +from autopilot.matchers import Eventually |
815 | + |
816 | +from address_book_app.tests import AddressBookAppTestCase |
817 | + |
818 | + |
819 | +class TestStateSave(AddressBookAppTestCase): |
820 | + """ Tests the Save State functionality """ |
821 | + |
822 | + def test_create_contact_state(self): |
823 | + # execute add new contact |
824 | + self.main_window.open_toolbar().click_button("Add") |
825 | + |
826 | + # fill name |
827 | + firstNameField = self.main_window.wait_select_single( |
828 | + "TextInputDetail", |
829 | + objectName="firstName") |
830 | + lastNameField = self.main_window.wait_select_single( |
831 | + "TextInputDetail", |
832 | + objectName="lastName") |
833 | + self.type_on_field(firstNameField, "Sherlock") |
834 | + self.type_on_field(lastNameField, "Holmes") |
835 | + |
836 | + # fill phone number |
837 | + phone_number_0 = self.main_window.wait_select_single( |
838 | + "TextInputDetail", |
839 | + objectName="phoneNumber_0") |
840 | + self.type_on_field(phone_number_0, "81 8777 7755") |
841 | + |
842 | + # fill email |
843 | + email_0 = self.main_window.wait_select_single( |
844 | + "TextInputDetail", |
845 | + objectName="emailAddress_0") |
846 | + self.type_on_field(email_0, "holmes.sherlock.uk") |
847 | + |
848 | + # fill im |
849 | + im_0 = self.main_window.wait_select_single( |
850 | + "TextInputDetail", |
851 | + objectName="imUri_0") |
852 | + self.type_on_field(im_0, "sh.im.com.br") |
853 | + |
854 | + # Change Im type |
855 | + im_value_selector = self.main_window.select_single( |
856 | + "ValueSelector", |
857 | + objectName="type_onlineAccount_0") |
858 | + self.pointing_device.click_object(im_value_selector) |
859 | + self.assertThat(im_value_selector.expanded, Eventually(Equals(True))) |
860 | + |
861 | + im_address_0 = self.main_window.select_single( |
862 | + "TextInputDetail", |
863 | + objectName="imUri_0") |
864 | + |
865 | + # select the type with index = 0 |
866 | + self.select_a_value(im_address_0, im_value_selector, 0) |
867 | + |
868 | + # fill address |
869 | + street_0 = self.main_window.wait_select_single( |
870 | + "TextInputDetail", |
871 | + objectName="streetAddress_0") |
872 | + self.type_on_field(street_0, "221B Baker Street") |
873 | + locality_0 = self.main_window.wait_select_single( |
874 | + "TextInputDetail", |
875 | + objectName="localityAddress_0") |
876 | + self.type_on_field(locality_0, "West End") |
877 | + region_0 = self.main_window.wait_select_single( |
878 | + "TextInputDetail", |
879 | + objectName="regionAddress_0") |
880 | + self.type_on_field(region_0, "London") |
881 | + postcode_0 = self.main_window.wait_select_single( |
882 | + "TextInputDetail", |
883 | + objectName="postcodeAddress_0") |
884 | + self.type_on_field(postcode_0, "7777") |
885 | + country_0 = self.main_window.wait_select_single( |
886 | + "TextInputDetail", |
887 | + objectName="countryAddress_0") |
888 | + self.type_on_field(country_0, "united kingdom") |
889 | + |
890 | + # kill the app while in the create mode |
891 | + self.ensure_app_has_quit() |
892 | + |
893 | + # start app again |
894 | + self.start_app() |
895 | + |
896 | + # wait for edit page to appear |
897 | + edit_page = self.main_window.get_contact_edit_page() |
898 | + self.assertThat(edit_page.visible, Eventually(Equals(True))) |
899 | + |
900 | + # check for the contact data displayed correct |
901 | + |
902 | + # Name |
903 | + firstNameField = self.main_window.wait_select_single( |
904 | + "TextInputDetail", |
905 | + objectName="firstName") |
906 | + lastNameField = self.main_window.wait_select_single( |
907 | + "TextInputDetail", |
908 | + objectName="lastName") |
909 | + |
910 | + self.assertThat(firstNameField.text, Eventually(Equals("Sherlock"))) |
911 | + self.assertThat(lastNameField.text, Eventually(Equals("Holmes"))) |
912 | + |
913 | + # Phone number |
914 | + phone_number_0 = self.main_window.wait_select_single( |
915 | + "TextInputDetail", |
916 | + objectName="phoneNumber_0") |
917 | + self.assertThat(phone_number_0.text, Eventually(Equals("81 8777 7755"))) |
918 | + |
919 | |
920 | + email_0 = self.main_window.wait_select_single( |
921 | + "TextInputDetail", |
922 | + objectName="emailAddress_0") |
923 | + self.assertThat(email_0.text, Eventually(Equals("holmes.sherlock.uk"))) |
924 | + |
925 | + # Im |
926 | + im_0 = self.main_window.wait_select_single( |
927 | + "TextInputDetail", |
928 | + objectName="imUri_0") |
929 | + self.assertThat(im_0.text, Eventually(Equals("sh.im.com.br"))) |
930 | + |
931 | + # Im type |
932 | + im_value_selector = self.main_window.select_single( |
933 | + "ValueSelector", |
934 | + objectName="type_onlineAccount_0") |
935 | + self.assertThat(im_value_selector.currentIndex, Eventually(Equals(0))) |
936 | + |
937 | + # Address |
938 | + street_0 = self.main_window.wait_select_single( |
939 | + "TextInputDetail", |
940 | + objectName="streetAddress_0") |
941 | + self.assertThat(street_0.text, Eventually(Equals("221B Baker Street"))) |
942 | + |
943 | + locality_0 = self.main_window.wait_select_single( |
944 | + "TextInputDetail", |
945 | + objectName="localityAddress_0") |
946 | + self.assertThat(locality_0.text, Eventually(Equals("West End"))) |
947 | + |
948 | + region_0 = self.main_window.wait_select_single( |
949 | + "TextInputDetail", |
950 | + objectName="regionAddress_0") |
951 | + self.assertThat(region_0.text, Eventually(Equals("London"))) |
952 | + |
953 | + postcode_0 = self.main_window.wait_select_single( |
954 | + "TextInputDetail", |
955 | + objectName="postcodeAddress_0") |
956 | + self.assertThat(postcode_0.text, Eventually(Equals("7777"))) |
957 | + |
958 | + country_0 = self.main_window.wait_select_single( |
959 | + "TextInputDetail", |
960 | + objectName="countryAddress_0") |
961 | + self.assertThat(country_0.text, Eventually(Equals("united kingdom"))) |
962 | + |
FAILED: Continuous integration, rev:118 jenkins. qa.ubuntu. com/job/ address- book-app- ci/339/ jenkins. qa.ubuntu. com/job/ address- book-app- trusty- amd64-ci/ 49 jenkins. qa.ubuntu. com/job/ address- book-app- trusty- armhf-ci/ 49 jenkins. qa.ubuntu. com/job/ address- book-app- trusty- armhf-ci/ 49/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ address- book-app- trusty- i386-ci/ 49 jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty/ 2189 jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty- touch/2081/ console jenkins. qa.ubuntu. com/job/ autopilot- testrunner- otto-trusty/ 1912 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/2191 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/2191/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- armhf/2081 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- armhf/2081/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- mediumtests- runner- mako/4544/ console s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 2936
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/address- book-app- ci/339/ rebuild
http://