Merge lp:~renatofilho/address-book-app/multiple-addressbook into lp:address-book-app
- multiple-addressbook
- Merge into trunk
Proposed by
Renato Araujo Oliveira Filho
Status: | Superseded |
---|---|
Proposed branch: | lp:~renatofilho/address-book-app/multiple-addressbook |
Merge into: | lp:address-book-app |
Diff against target: |
1059 lines (+305/-187) 22 files modified
src/imports/Common/ContactDetailBase.qml (+6/-0) src/imports/Common/ContactDetailGroupBase.qml (+26/-5) src/imports/Common/ContactDetailGroupWithTypeBase.qml (+6/-2) src/imports/ContactEdit/CMakeLists.txt (+1/-0) src/imports/ContactEdit/ContactDetailAvatarEditor.qml (+4/-0) src/imports/ContactEdit/ContactDetailGroupWithTypeEditor.qml (+26/-13) src/imports/ContactEdit/ContactDetailNameEditor.qml (+4/-0) src/imports/ContactEdit/ContactDetailSyncTargetEditor.qml (+73/-0) src/imports/ContactEdit/ContactDetailWithTypeEditor.qml (+18/-19) src/imports/ContactEdit/ContactEditor.qml (+45/-84) src/imports/ContactList/ContactListPage.qml (+5/-21) src/imports/ContactView/ContactDetailAvatarView.qml (+5/-2) src/imports/ContactView/ContactDetailGroupWithTypeView.qml (+1/-1) src/imports/ContactView/ContactDetailPhoneNumberView.qml (+7/-3) src/imports/ContactView/ContactDetailPhoneNumbersView.qml (+1/-1) src/imports/ContactView/ContactDetailWithTypeView.qml (+7/-3) src/imports/ContactView/ContactView.qml (+23/-10) src/imports/MainWindow.qml (+26/-0) src/imports/Ubuntu/Contacts/ContactDetailOnlineAccountTypeModel.qml (+0/-3) src/imports/Ubuntu/Contacts/ContactDetailPhoneNumberTypeModel.qml (+0/-2) src/imports/Ubuntu/Contacts/ContactFetch.qml (+15/-17) tests/autopilot/address_book_app/tests/test_edit_contact.py (+6/-1) |
To merge this branch: | bzr merge lp:~renatofilho/address-book-app/multiple-addressbook |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Ubuntu Phablet Team | Pending | ||
Review via email: mp+209549@code.launchpad.net |
This proposal has been superseded by a proposal from 2014-03-19.
Commit message
Implemented support to create contacts in different sources.
Description of the change
To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : | # |
review:
Needs Fixing
(continuous-integration)
- 144. By Renato Araujo Oliveira Filho
- 145. By Renato Araujo Oliveira Filho
-
Merged parent branch.
- 146. By Renato Araujo Oliveira Filho
-
Merged parent branch.
- 147. By Renato Araujo Oliveira Filho
-
Added missing file.
- 148. By Renato Araujo Oliveira Filho
-
Fixed problem saving contact without sync target.
- 149. By Renato Araujo Oliveira Filho
-
Merged parent branch.
- 150. By Renato Araujo Oliveira Filho
-
Set the default icon for address-book.
- 151. By Renato Araujo Oliveira Filho
-
Merged parent branch: lp:~renatofilho/address-book-app/optimize-app
- 152. By Renato Araujo Oliveira Filho
-
Merged mainline.
- 153. By Renato Araujo Oliveira Filho
-
Fixed scroll to created contact.
- 154. By Renato Araujo Oliveira Filho
-
Scroll to contact based on the contact name if the contact id is empty
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/imports/Common/ContactDetailBase.qml' |
2 | --- src/imports/Common/ContactDetailBase.qml 2013-11-14 13:25:28 +0000 |
3 | +++ src/imports/Common/ContactDetailBase.qml 2014-03-19 18:42:11 +0000 |
4 | @@ -61,6 +61,10 @@ |
5 | imMap[QtContacts.OnlineAccount.Protocol] = "imProtocol" |
6 | imMap[QtContacts.OnlineAccount.Capabilities] = "imCaps" |
7 | |
8 | + // SyncTarget |
9 | + var syncTargetMap = {} |
10 | + syncTargetMap[QtContacts.SyncTarget.SyncTarget] = "syncTarget" |
11 | + |
12 | // all |
13 | var detailMap = {} |
14 | detailMap[QtContacts.ContactDetail.Name] = nameMap |
15 | @@ -68,6 +72,7 @@ |
16 | detailMap[QtContacts.ContactDetail.Email] = emailMap |
17 | detailMap[QtContacts.ContactDetail.Address] = addressMap |
18 | detailMap[QtContacts.ContactDetail.OnlineAccount] = imMap |
19 | + detailMap[QtContacts.ContactDetail.SyncTarget] = syncTargetMap |
20 | |
21 | // detail name |
22 | var detailNameMap = {} |
23 | @@ -76,6 +81,7 @@ |
24 | detailNameMap[QtContacts.ContactDetail.Email] = "email" |
25 | detailNameMap[QtContacts.ContactDetail.Address] = "address" |
26 | detailNameMap[QtContacts.ContactDetail.OnlineAccount] = "onlineAccount" |
27 | + detailNameMap[QtContacts.ContactDetail.SyncTarget] = "syncTarget" |
28 | |
29 | if ((detail in detailMap) && (field in detailMap[detail])) { |
30 | return detailMap[detail][field] |
31 | |
32 | === modified file 'src/imports/Common/ContactDetailGroupBase.qml' |
33 | --- src/imports/Common/ContactDetailGroupBase.qml 2014-01-14 20:43:59 +0000 |
34 | +++ src/imports/Common/ContactDetailGroupBase.qml 2014-03-19 18:42:11 +0000 |
35 | @@ -21,7 +21,7 @@ |
36 | FocusScope { |
37 | id: root |
38 | |
39 | - readonly property variant details: contact && contact.contactDetails && detailType ? contact.details(detailType) : [] |
40 | + property var details: [] |
41 | readonly property alias detailDelegates: contents.children |
42 | readonly property int detailsCount: detailsModel.count |
43 | |
44 | @@ -38,7 +38,27 @@ |
45 | |
46 | signal newFieldAdded(var index) |
47 | |
48 | - implicitHeight: detailsModel.values.length > 0 ? contents.implicitHeight : minimumHeight |
49 | + function reloadDetails(clearFields) |
50 | + { |
51 | + if (clearFields) { |
52 | + root.inputFields = [] |
53 | + } |
54 | + |
55 | + if (contact && detailType) { |
56 | + root.details = contact.details(detailType) |
57 | + } else { |
58 | + root.details = [] |
59 | + } |
60 | + } |
61 | + |
62 | + onContactChanged: reloadDetails(true) |
63 | + onDetailTypeChanged: reloadDetails(true) |
64 | + Connections { |
65 | + target: root.contact |
66 | + onContactChanged: reloadDetails(false) |
67 | + } |
68 | + |
69 | + implicitHeight: detailsCount > 0 ? contents.implicitHeight : minimumHeight |
70 | visible: implicitHeight > 0 |
71 | |
72 | // This model is used to avoid rebuild the repeater every time that the details change |
73 | @@ -46,7 +66,7 @@ |
74 | ListModel { |
75 | id: detailsModel |
76 | |
77 | - property var values: root.showEmpty ? root.details : filterDetails(root.details) |
78 | + property var values: root.showEmpty && root.details ? root.details : filterDetails(root.details) |
79 | |
80 | function filterDetails(details) { |
81 | var result = [] |
82 | @@ -68,6 +88,7 @@ |
83 | |
84 | onValuesChanged: { |
85 | if (!values) { |
86 | + root.inputFields = [] |
87 | clear() |
88 | return |
89 | } |
90 | @@ -78,7 +99,7 @@ |
91 | |
92 | var modelCount = count |
93 | for(var i=0; i < values.length; i++) { |
94 | - if (modelCount < i) { |
95 | + if (modelCount <= i) { |
96 | append({"detail": values[i]}) |
97 | } else if (get(i) != values[i]) { |
98 | set(i, {"detail": values[i]}) |
99 | @@ -112,7 +133,7 @@ |
100 | Binding { |
101 | target: detailItem.item |
102 | property: "detail" |
103 | - value: root.contact && root.details ? root.details[index] : null |
104 | + value: model.detail |
105 | } |
106 | |
107 | Binding { |
108 | |
109 | === modified file 'src/imports/Common/ContactDetailGroupWithTypeBase.qml' |
110 | --- src/imports/Common/ContactDetailGroupWithTypeBase.qml 2014-01-14 20:43:59 +0000 |
111 | +++ src/imports/Common/ContactDetailGroupWithTypeBase.qml 2014-03-19 18:42:11 +0000 |
112 | @@ -23,6 +23,7 @@ |
113 | |
114 | property string defaultIcon : "artwork:/protocol-other.png" |
115 | property ListModel typeModel |
116 | + property bool typeModelReady: false |
117 | |
118 | function getType(detail) { |
119 | if (typeModel) { |
120 | @@ -40,7 +41,6 @@ |
121 | } |
122 | |
123 | typeModel: ListModel { |
124 | - property bool ready: false |
125 | signal loaded() |
126 | |
127 | function getTypeIndex(detail) { |
128 | @@ -116,8 +116,12 @@ |
129 | append({"value": QtContacts.ContactDetail.ContextHome, "label": i18n.tr("Home"), "icon": null}) |
130 | append({"value": QtContacts.ContactDetail.ContextWork, "label": i18n.tr("Work"), "icon": null}) |
131 | append({"value": QtContacts.ContactDetail.ContextOther, "label": i18n.tr("Other"), "icon": null}) |
132 | - ready = true |
133 | loaded() |
134 | } |
135 | } |
136 | + onTypeModelChanged: root.typeModelReady = false |
137 | + Connections { |
138 | + target: root.typeModel |
139 | + onLoaded: root.typeModelReady = true |
140 | + } |
141 | } |
142 | |
143 | === modified file 'src/imports/ContactEdit/CMakeLists.txt' |
144 | --- src/imports/ContactEdit/CMakeLists.txt 2013-09-24 15:07:46 +0000 |
145 | +++ src/imports/ContactEdit/CMakeLists.txt 2014-03-19 18:42:11 +0000 |
146 | @@ -7,6 +7,7 @@ |
147 | ContactDetailOnlineAccountsEditor.qml |
148 | ContactDetailOrganizationsEditor.qml |
149 | ContactDetailPhoneNumbersEditor.qml |
150 | + ContactDetailSyncTargetEditor.qml |
151 | ContactDetailWithTypeEditor.qml |
152 | ContactEditor.qml |
153 | ContactFetchError.qml |
154 | |
155 | === modified file 'src/imports/ContactEdit/ContactDetailAvatarEditor.qml' |
156 | --- src/imports/ContactEdit/ContactDetailAvatarEditor.qml 2013-10-15 14:34:03 +0000 |
157 | +++ src/imports/ContactEdit/ContactDetailAvatarEditor.qml 2014-03-19 18:42:11 +0000 |
158 | @@ -27,6 +27,10 @@ |
159 | |
160 | readonly property string defaultAvatar: Qt.resolvedUrl("../../artwork/contact-default-profile.png") |
161 | |
162 | + function isEmpty() { |
163 | + return false; |
164 | + } |
165 | + |
166 | function save() { |
167 | if (avatarImage.source != root.defaultAvatar) { |
168 | if (root.detail && (root.detail === avatarImage.source)) { |
169 | |
170 | === modified file 'src/imports/ContactEdit/ContactDetailGroupWithTypeEditor.qml' |
171 | --- src/imports/ContactEdit/ContactDetailGroupWithTypeEditor.qml 2014-02-19 12:06:20 +0000 |
172 | +++ src/imports/ContactEdit/ContactDetailGroupWithTypeEditor.qml 2014-03-19 18:42:11 +0000 |
173 | @@ -40,6 +40,7 @@ |
174 | |
175 | function save() { |
176 | var changed = false |
177 | + var removedDetails = [] |
178 | for(var i=0; i < detailDelegates.length; i++) { |
179 | var delegate = detailDelegates[i] |
180 | |
181 | @@ -49,17 +50,30 @@ |
182 | } |
183 | |
184 | if (delegate.save) { |
185 | - // save type |
186 | - if (updateDetail(delegate.detail, delegate.selectedTypeIndex)) { |
187 | - changed = true |
188 | - } |
189 | - |
190 | - // save fields |
191 | - if (delegate.save()) { |
192 | - changed = true |
193 | - } |
194 | - } |
195 | - } |
196 | + // check if was removed |
197 | + if (delegate.isEmpty()) { |
198 | + removedDetails.push(delegate.detail) |
199 | + changed = true |
200 | + } else { |
201 | + if (updateDetail(delegate.detail, delegate.selectedTypeIndex)) { |
202 | + changed = true |
203 | + } |
204 | + |
205 | + // save field changes |
206 | + if (delegate.save()) { |
207 | + changed = true |
208 | + } |
209 | + } |
210 | + } |
211 | + } |
212 | + |
213 | + for(var i=0; i < removedDetails.length; i++) { |
214 | + if (contact.isPreferredDetail("TEL", removedDetails[i])) { |
215 | + contact.favorite.favorite = false |
216 | + } |
217 | + contact.removeDetail(removedDetails[i]) |
218 | + } |
219 | + |
220 | return changed |
221 | } |
222 | |
223 | @@ -146,7 +160,6 @@ |
224 | } |
225 | } |
226 | } |
227 | - |
228 | placeholderTexts: root.placeholderTexts |
229 | contact: root.contact |
230 | fields: root.fields |
231 | @@ -161,7 +174,7 @@ |
232 | Item { |
233 | Connections { |
234 | target: root.typeModel |
235 | - onReadyChanged: updateCombo(true) |
236 | + onLoaded: updateCombo(true) |
237 | } |
238 | } |
239 | } |
240 | |
241 | === modified file 'src/imports/ContactEdit/ContactDetailNameEditor.qml' |
242 | --- src/imports/ContactEdit/ContactDetailNameEditor.qml 2014-01-23 01:09:04 +0000 |
243 | +++ src/imports/ContactEdit/ContactDetailNameEditor.qml 2014-03-19 18:42:11 +0000 |
244 | @@ -26,6 +26,10 @@ |
245 | property bool isEmpty: (fields == -1) || (emptyFields.length === fields.length) |
246 | property variant emptyFields: [] |
247 | |
248 | + function isEmpty() { |
249 | + return false |
250 | + } |
251 | + |
252 | function save() { |
253 | var changed = false; |
254 | |
255 | |
256 | === added file 'src/imports/ContactEdit/ContactDetailSyncTargetEditor.qml' |
257 | --- src/imports/ContactEdit/ContactDetailSyncTargetEditor.qml 1970-01-01 00:00:00 +0000 |
258 | +++ src/imports/ContactEdit/ContactDetailSyncTargetEditor.qml 2014-03-19 18:42:11 +0000 |
259 | @@ -0,0 +1,73 @@ |
260 | +/* |
261 | + * Copyright (C) 2012-2013 Canonical, Ltd. |
262 | + * |
263 | + * This program is free software; you can redistribute it and/or modify |
264 | + * it under the terms of the GNU General Public License as published by |
265 | + * the Free Software Foundation; version 3. |
266 | + * |
267 | + * This program is distributed in the hope that it will be useful, |
268 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
269 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
270 | + * GNU General Public License for more details. |
271 | + * |
272 | + * You should have received a copy of the GNU General Public License |
273 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
274 | + */ |
275 | + |
276 | +import QtQuick 2.0 |
277 | +import Ubuntu.Components 0.1 |
278 | +import QtContacts 5.0 |
279 | + |
280 | +import "../Common" |
281 | + |
282 | +ContactDetailBase { |
283 | + id: root |
284 | + |
285 | + function save() { |
286 | + // only changes the target sync for new contacts |
287 | + if (!isNewContact) { |
288 | + return; |
289 | + } |
290 | + |
291 | + var activeSource = getSelectedSource() |
292 | + if (!root.detail) { |
293 | + root.detail = root.contact.syncTarget |
294 | + } |
295 | + root.detail.syncTarget = activeSource |
296 | + } |
297 | + |
298 | + function getSelectedSource() { |
299 | + return sourceModel.contacts[sources.selectedIndex].guid.guid |
300 | + } |
301 | + |
302 | + property bool isNewContact: contact && contact.contactId === "qtcontacts:::" |
303 | + |
304 | + detail: contact ? contact.detail(ContactDetail.SyncTarget) : null |
305 | + implicitHeight: isNewContact ? units.gu(5) : 0 |
306 | + |
307 | + ContactModel { |
308 | + id: sourceModel |
309 | + |
310 | + manager: QTCONTACTS_MANAGER_OVERRIDE && QTCONTACTS_MANAGER_OVERRIDE != "" ? QTCONTACTS_MANAGER_OVERRIDE : "galera" |
311 | + filter: DetailFilter { |
312 | + detail: ContactDetail.Type |
313 | + field: Type.TypeField |
314 | + value: Type.Group |
315 | + matchFlags: DetailFilter.MatchExactly |
316 | + } |
317 | + } |
318 | + |
319 | + OptionSelector { |
320 | + id: sources |
321 | + |
322 | + anchors.fill: parent |
323 | + model: sourceModel |
324 | + delegate: OptionSelectorDelegate { |
325 | + text: contact.displayLabel.label |
326 | + } |
327 | + |
328 | + containerHeight: itemHeight * 4 |
329 | + } |
330 | + z: 1000 |
331 | +} |
332 | + |
333 | |
334 | === modified file 'src/imports/ContactEdit/ContactDetailWithTypeEditor.qml' |
335 | --- src/imports/ContactEdit/ContactDetailWithTypeEditor.qml 2014-01-28 14:28:58 +0000 |
336 | +++ src/imports/ContactEdit/ContactDetailWithTypeEditor.qml 2014-03-19 18:42:11 +0000 |
337 | @@ -36,33 +36,32 @@ |
338 | detailTypeSelector.selectItem(type) |
339 | } |
340 | |
341 | + function isEmpty() { |
342 | + for (var i=0; i < fieldValues.children.length; i++) { |
343 | + var input = fieldValues.children[i] |
344 | + if (input.text && (input.text !== "")) { |
345 | + return false |
346 | + } |
347 | + } |
348 | + return true |
349 | + } |
350 | + |
351 | function save() { |
352 | var detailchanged = false |
353 | |
354 | // save field values |
355 | - var isEmpty = true |
356 | for (var i=0; i < fieldValues.children.length; i++) { |
357 | var input = fieldValues.children[i] |
358 | if (input.detail && (input.field >= 0)) { |
359 | - var originalValue = input.detail.value(input.field) |
360 | - originalValue = originalValue ? String(originalValue) : "" |
361 | if (input.text !== "") { |
362 | - isEmpty = false |
363 | - } |
364 | - |
365 | - if (originalValue !== input.text) { |
366 | - input.detail.setValue(input.field, input.text) |
367 | - detailchanged = true |
368 | - } |
369 | - } |
370 | - } |
371 | - |
372 | - if (isEmpty) { |
373 | - // unfavorite the contact if the favorite number was removed |
374 | - if (contact.isPreferredDetail("TEL", detail)) { |
375 | - contact.favorite.favorite = false |
376 | - } |
377 | - contact.removeDetail(input.detail) |
378 | + var originalValue = input.detail.value(input.field) |
379 | + originalValue = originalValue ? String(originalValue) : "" |
380 | + if (originalValue !== input.text) { |
381 | + root.detail.setValue(input.field, input.text) |
382 | + detailchanged = true |
383 | + } |
384 | + } |
385 | + } |
386 | } |
387 | |
388 | return detailchanged |
389 | |
390 | === modified file 'src/imports/ContactEdit/ContactEditor.qml' |
391 | --- src/imports/ContactEdit/ContactEditor.qml 2013-11-21 12:14:04 +0000 |
392 | +++ src/imports/ContactEdit/ContactEditor.qml 2014-03-19 18:42:11 +0000 |
393 | @@ -19,16 +19,16 @@ |
394 | import Ubuntu.Components 0.1 |
395 | import Ubuntu.Components.ListItems 0.1 as ListItem |
396 | import Ubuntu.Components.Popups 0.1 |
397 | +import Ubuntu.Contacts 0.1 as ContactsUI |
398 | |
399 | Page { |
400 | id: contactEditor |
401 | objectName: "contactEditorPage" |
402 | |
403 | property QtObject contact: null |
404 | - property QtObject model: null |
405 | + property alias model: contactFetch.model |
406 | |
407 | // this is used to add a phone number to a existing contact |
408 | - property int currentFetchOperation: -1 |
409 | property string contactId: "" |
410 | property string newPhoneNumber: "" |
411 | |
412 | @@ -78,10 +78,10 @@ |
413 | } |
414 | |
415 | if (changed) { |
416 | - model.saveContact(contact) |
417 | - } else { |
418 | - pageStack.pop() |
419 | + // backend error will be handled by the root page (contact list) |
420 | + contactEditor.model.saveContact(contact) |
421 | } |
422 | + pageStack.pop() |
423 | } |
424 | |
425 | function makeMeVisible(item) { |
426 | @@ -114,39 +114,10 @@ |
427 | id: fetchErrorDialog |
428 | } |
429 | |
430 | - Connections { |
431 | - target: model |
432 | - onContactsFetched: { |
433 | - if (requestId == currentFetchOperation) { |
434 | - currentFetchOperation = -1 |
435 | - // this fetch request can only return one contact |
436 | - if(fetchedContacts.length !== 1) { |
437 | - PopupUtils.open(fetchErrorDialog, null) |
438 | - } |
439 | - contact = fetchedContacts[0] |
440 | - } |
441 | - } |
442 | - } |
443 | - |
444 | - onContactIdChanged: { |
445 | - if (contactId) { |
446 | - currentFetchOperation = model.fetchContacts(contactId) |
447 | - } |
448 | - } |
449 | - |
450 | - onContactChanged: { |
451 | - if (contact && (newPhoneNumber.length > 0)) { |
452 | - var detailSourceTemplate = "import QtContacts 5.0; PhoneNumber{ number: \"" + newPhoneNumber + "\" }" |
453 | - var newDetail = Qt.createQmlObject(detailSourceTemplate, contactEditor) |
454 | - if (newDetail) { |
455 | - contact.addDetail(newDetail) |
456 | - // we need to wait for the field be created |
457 | - focusTimer.restart() |
458 | - |
459 | - } |
460 | - newPhoneNumber = "" |
461 | - |
462 | - } |
463 | + ContactsUI.ContactFetch { |
464 | + id: contactFetch |
465 | + |
466 | + onContactNotFound: PopupUtils.open(fetchErrorDialog, null) |
467 | } |
468 | |
469 | Timer { |
470 | @@ -176,7 +147,6 @@ |
471 | } |
472 | contentHeight: contents.height |
473 | contentWidth: parent.width |
474 | - visible: !busyIndicator.visible |
475 | |
476 | // after add a new field we need to wait for the contentHeight to change to scroll to the correct position |
477 | onContentHeightChanged: contactEditor.makeMeVisible(contactEditor.activeItem) |
478 | @@ -192,6 +162,18 @@ |
479 | } |
480 | height: childrenRect.height |
481 | |
482 | + ContactDetailSyncTargetEditor { |
483 | + id: syncTargetEditor |
484 | + |
485 | + contact: contactEditor.contact |
486 | + anchors { |
487 | + left: parent.left |
488 | + right: parent.right |
489 | + } |
490 | + height: implicitHeight |
491 | + KeyNavigation.tab: nameEditor |
492 | + } |
493 | + |
494 | ContactDetailNameEditor { |
495 | id: nameEditor |
496 | |
497 | @@ -202,6 +184,7 @@ |
498 | } |
499 | height: implicitHeight + units.gu(3) |
500 | KeyNavigation.tab: avatarEditor |
501 | + KeyNavigation.backtab : syncTargetEditor |
502 | } |
503 | |
504 | ContactDetailAvatarEditor { |
505 | @@ -286,46 +269,6 @@ |
506 | } |
507 | } |
508 | |
509 | - Component.onCompleted: nameEditor.forceActiveFocus() |
510 | - |
511 | - ActivityIndicator { |
512 | - id: busyIndicator |
513 | - |
514 | - running: contactSaveLock.saving |
515 | - visible: running |
516 | - anchors.centerIn: parent |
517 | - } |
518 | - |
519 | - Connections { |
520 | - id: contactSaveLock |
521 | - |
522 | - property bool saving: false |
523 | - |
524 | - target: contactEditor.model |
525 | - |
526 | - onContactsChanged: { |
527 | - if (saving) { |
528 | - saving = false |
529 | - pageStack.contactCreated(contactEditor.contact) |
530 | - pageStack.pop() |
531 | - } else if (contactEditor.contact && |
532 | - (contactEditor.contact.contactId != "qtcontacts:::")) { |
533 | - for (var i=0; i < contactEditor.model.contacts.length; i++) { |
534 | - if (contactEditor.model.contacts[i].contactId == contactEditor.contact.contactId) { |
535 | - return |
536 | - } |
537 | - } |
538 | - contactEditor.contact = null |
539 | - pageStack.pop() |
540 | - } |
541 | - } |
542 | - |
543 | - onErrorChanged: { |
544 | - //TODO: show a dialog |
545 | - console.debug("Save error:" + contactEditor.model.error) |
546 | - } |
547 | - } |
548 | - |
549 | EditToolbar { |
550 | id: toolbar |
551 | anchors { |
552 | @@ -337,11 +280,7 @@ |
553 | acceptAction: Action { |
554 | text: i18n.tr("Save") |
555 | enabled: !nameEditor.isEmpty |
556 | - onTriggered: { |
557 | - // wait for contact to be saved or cause a error |
558 | - contactSaveLock.saving = true |
559 | - contactEditor.save() |
560 | - } |
561 | + onTriggered: contactEditor.save() |
562 | } |
563 | rejectAction: Action { |
564 | text: i18n.tr("Cancel") |
565 | @@ -358,4 +297,26 @@ |
566 | } |
567 | } |
568 | } |
569 | + |
570 | + // This will load the contact information and add the new phone number |
571 | + // when the app was launched with the URI: addressbook:///addphone?id=<contact-id>&phone=<phone-number> |
572 | + onContactChanged: { |
573 | + if (contact && (newPhoneNumber.length > 0)) { |
574 | + var detailSourceTemplate = "import QtContacts 5.0; PhoneNumber{ number: \"" + newPhoneNumber + "\" }" |
575 | + var newDetail = Qt.createQmlObject(detailSourceTemplate, contactEditor) |
576 | + if (newDetail) { |
577 | + contact.addDetail(newDetail) |
578 | + // we need to wait for the field be created |
579 | + focusTimer.restart() |
580 | + } |
581 | + newPhoneNumber = "" |
582 | + } |
583 | + } |
584 | + |
585 | + Component.onCompleted: { |
586 | + if (contactId !== "") { |
587 | + contactFetch.fetchContact(contactId) |
588 | + } |
589 | + nameEditor.forceActiveFocus() |
590 | + } |
591 | } |
592 | |
593 | === modified file 'src/imports/ContactList/ContactListPage.qml' |
594 | --- src/imports/ContactList/ContactListPage.qml 2014-02-25 20:20:21 +0000 |
595 | +++ src/imports/ContactList/ContactListPage.qml 2014-03-19 18:42:11 +0000 |
596 | @@ -17,7 +17,6 @@ |
597 | import QtQuick 2.0 |
598 | import Ubuntu.Components 0.1 |
599 | import Ubuntu.Components.ListItems 0.1 as ListItem |
600 | -import Ubuntu.Components.Popups 0.1 as Popups |
601 | import Ubuntu.Contacts 0.1 as ContactsUI |
602 | import QtContacts 5.0 |
603 | |
604 | @@ -48,25 +47,7 @@ |
605 | return newContact |
606 | } |
607 | |
608 | - |
609 | - |
610 | title: i18n.tr("Contacts") |
611 | - Component { |
612 | - id: dialog |
613 | - |
614 | - Popups.Dialog { |
615 | - id: dialogue |
616 | - |
617 | - title: i18n.tr("Error") |
618 | - text: i18n.tr("Fail to Load contacts") |
619 | - |
620 | - Button { |
621 | - text: "Cancel" |
622 | - gradient: UbuntuColors.greyGradient |
623 | - onClicked: PopupUtils.close(dialogue) |
624 | - } |
625 | - } |
626 | - } |
627 | |
628 | ContactsUI.ContactListView { |
629 | id: contactList |
630 | @@ -83,7 +64,6 @@ |
631 | bottomMargin: contactList.isInSelectionMode ? 0 : units.gu(2) |
632 | fill: parent |
633 | } |
634 | - onError: PopupUtils.open(dialog, null) |
635 | swipeToDelete: !pickMode |
636 | |
637 | ActivityIndicator { |
638 | @@ -96,7 +76,8 @@ |
639 | |
640 | onContactClicked: { |
641 | pageStack.push(Qt.resolvedUrl("../ContactView/ContactView.qml"), |
642 | - {model: contactList.listModel, contactId: contact.contactId}) |
643 | + {model: contactList.listModel, |
644 | + contact: contact}) |
645 | } |
646 | |
647 | onSelectionDone: { |
648 | @@ -116,6 +97,7 @@ |
649 | contactList.listModel.removeContacts(ids) |
650 | } |
651 | } |
652 | + |
653 | onSelectionCanceled: { |
654 | if (pickMode) { |
655 | if (contactContentHub) { |
656 | @@ -131,6 +113,8 @@ |
657 | toolbar.opened = false |
658 | } |
659 | } |
660 | + |
661 | + onError: pageStack.contactModelError(error) |
662 | } |
663 | |
664 | tools: ToolbarItems { |
665 | |
666 | === modified file 'src/imports/ContactView/ContactDetailAvatarView.qml' |
667 | --- src/imports/ContactView/ContactDetailAvatarView.qml 2013-10-08 18:42:21 +0000 |
668 | +++ src/imports/ContactView/ContactDetailAvatarView.qml 2014-03-19 18:42:11 +0000 |
669 | @@ -23,10 +23,12 @@ |
670 | ContactDetailBase { |
671 | id: root |
672 | |
673 | + readonly property string defaultAvatar: Qt.resolvedUrl("../../artwork/contact-default-profile.png") |
674 | + |
675 | function getAvatar(avatarDetail) |
676 | { |
677 | // use this verbose mode to avoid problems with binding loops |
678 | - var avatarUrl = Qt.resolvedUrl("../../artwork/contact-default-profile.png") |
679 | + var avatarUrl = defaultAvatar |
680 | if (avatarDetail) { |
681 | var avatarValue = avatarDetail.value(Avatar.ImageUrl) |
682 | if (avatarValue != "") { |
683 | @@ -41,7 +43,8 @@ |
684 | |
685 | Image { |
686 | anchors.fill: parent |
687 | - source: root.getAvatar(root.detail) |
688 | + // make sure that the avatar changes if the contact changes |
689 | + source: root.contact ? root.getAvatar(root.detail) : root.defaultAvatar |
690 | asynchronous: true |
691 | smooth: true |
692 | fillMode: Image.PreserveAspectCrop |
693 | |
694 | === modified file 'src/imports/ContactView/ContactDetailGroupWithTypeView.qml' |
695 | --- src/imports/ContactView/ContactDetailGroupWithTypeView.qml 2014-01-14 20:43:59 +0000 |
696 | +++ src/imports/ContactView/ContactDetailGroupWithTypeView.qml 2014-03-19 18:42:11 +0000 |
697 | @@ -51,7 +51,7 @@ |
698 | } |
699 | |
700 | detailDelegate: ContactDetailWithTypeView { |
701 | - property variant detailType: detail && root.contact && root.typeModel.ready ? root.getType(detail) : null |
702 | + property variant detailType: detail && root.contact && root.typeModelReady ? root.getType(detail) : "" |
703 | |
704 | action: root.defaultAction |
705 | contact: root.contact |
706 | |
707 | === modified file 'src/imports/ContactView/ContactDetailPhoneNumberView.qml' |
708 | --- src/imports/ContactView/ContactDetailPhoneNumberView.qml 2013-11-14 13:25:28 +0000 |
709 | +++ src/imports/ContactView/ContactDetailPhoneNumberView.qml 2014-03-19 18:42:11 +0000 |
710 | @@ -25,10 +25,11 @@ |
711 | |
712 | property alias typeLabel: view.typeLabel |
713 | property alias lineHeight: view.lineHeight |
714 | + readonly property bool isReady: (fields != null) && (detail != null) |
715 | |
716 | function populateValues() |
717 | { |
718 | - if (fields && detail) { |
719 | + if (isReady) { |
720 | var values = [] |
721 | for(var i=0; i < fields.length; i++) { |
722 | values.push(detail.value(fields[i])) |
723 | @@ -38,8 +39,11 @@ |
724 | } |
725 | |
726 | implicitHeight: view.implicitHeight |
727 | - onFieldsChanged: populateValues() |
728 | - onDetailChanged: populateValues() |
729 | + onIsReadyChanged: populateValues() |
730 | + Connections { |
731 | + target: root.detail |
732 | + onDetailChanged: populateValues() |
733 | + } |
734 | |
735 | BasicFieldView { |
736 | id: view |
737 | |
738 | === modified file 'src/imports/ContactView/ContactDetailPhoneNumbersView.qml' |
739 | --- src/imports/ContactView/ContactDetailPhoneNumbersView.qml 2014-02-25 21:52:58 +0000 |
740 | +++ src/imports/ContactView/ContactDetailPhoneNumbersView.qml 2014-03-19 18:42:11 +0000 |
741 | @@ -35,7 +35,7 @@ |
742 | } |
743 | |
744 | detailDelegate: ContactDetailPhoneNumberView { |
745 | - property variant detailType: detail && root.contact && root.typeModel.ready ? root.getType(detail) : null |
746 | + property variant detailType: detail && root.contact && root.typeModelReady ? root.getType(detail) : null |
747 | |
748 | contact: root.contact |
749 | fields: root.fields |
750 | |
751 | === modified file 'src/imports/ContactView/ContactDetailWithTypeView.qml' |
752 | --- src/imports/ContactView/ContactDetailWithTypeView.qml 2013-11-14 17:48:48 +0000 |
753 | +++ src/imports/ContactView/ContactDetailWithTypeView.qml 2014-03-19 18:42:11 +0000 |
754 | @@ -27,10 +27,11 @@ |
755 | property alias typeLabel: view.typeLabel |
756 | property string typeIcon: null |
757 | property alias lineHeight: view.lineHeight |
758 | + readonly property bool isReady: (fields != null) && (detail != null) |
759 | |
760 | function populateValues() |
761 | { |
762 | - if (fields && detail) { |
763 | + if (isReady) { |
764 | var values = [] |
765 | for(var i=0; i < fields.length; i++) { |
766 | values.push(detail.value(fields[i])) |
767 | @@ -39,9 +40,12 @@ |
768 | } |
769 | } |
770 | |
771 | - onFieldsChanged: populateValues() |
772 | - onDetailChanged: populateValues() |
773 | implicitHeight: view.implicitHeight |
774 | + onIsReadyChanged: populateValues() |
775 | + Connections { |
776 | + target: root.detail |
777 | + onDetailChanged: populateValues() |
778 | + } |
779 | |
780 | BasicFieldView { |
781 | id: view |
782 | |
783 | === modified file 'src/imports/ContactView/ContactView.qml' |
784 | --- src/imports/ContactView/ContactView.qml 2014-02-25 21:52:58 +0000 |
785 | +++ src/imports/ContactView/ContactView.qml 2014-03-19 18:42:11 +0000 |
786 | @@ -24,15 +24,15 @@ |
787 | id: root |
788 | objectName: "contactViewPage" |
789 | |
790 | - readonly property alias contact: contactFetch.contact |
791 | - property variant contactId: null |
792 | + property QtObject contact: null |
793 | property alias model: contactFetch.model |
794 | + // used by main page to open the contact view on app startup |
795 | + property string contactId: "" |
796 | |
797 | function formatNameToDisplay(contact) { |
798 | if (!contact) { |
799 | return "" |
800 | } |
801 | - |
802 | if (contact.name) { |
803 | var detail = contact.name |
804 | return detail.firstName +" " + detail.lastName |
805 | @@ -43,14 +43,10 @@ |
806 | } |
807 | } |
808 | |
809 | - |
810 | title: formatNameToDisplay(contact) |
811 | onActiveChanged: { |
812 | if (active) { |
813 | - contactFetch.fetchContact(root.contactId) |
814 | - |
815 | //WORKAROUND: to correct scroll back the page |
816 | - flickable.contentY = -100 |
817 | flickable.returnToBounds() |
818 | } |
819 | } |
820 | @@ -141,14 +137,13 @@ |
821 | } |
822 | height: implicitHeight |
823 | } |
824 | - |
825 | } |
826 | } |
827 | |
828 | ActivityIndicator { |
829 | id: busyIndicator |
830 | |
831 | - running: contactFetch.running |
832 | + running: (root.contact === null) && contactFetch.running |
833 | visible: running |
834 | anchors.centerIn: parent |
835 | } |
836 | @@ -156,7 +151,17 @@ |
837 | ContactsUI.ContactFetch { |
838 | id: contactFetch |
839 | |
840 | - onContactRemoved: pageStack.pop() |
841 | + onContactRemoved: { |
842 | + pageStack.pop() |
843 | + } |
844 | + |
845 | + onContactNotFound: { |
846 | + pageStack.pop() |
847 | + } |
848 | + |
849 | + onContactFetched: { |
850 | + root.contact = contact |
851 | + } |
852 | } |
853 | |
854 | tools: ToolbarItems { |
855 | @@ -185,4 +190,12 @@ |
856 | } |
857 | } |
858 | } |
859 | + |
860 | + // This will load the contact information when the app was launched with |
861 | + // the URI: addressbook:///contact?id=<id> |
862 | + Component.onCompleted: { |
863 | + if (contactId !== "") { |
864 | + contactFetch.fetchContact(contactId) |
865 | + } |
866 | + } |
867 | } |
868 | |
869 | === modified file 'src/imports/MainWindow.qml' |
870 | --- src/imports/MainWindow.qml 2014-02-25 20:06:47 +0000 |
871 | +++ src/imports/MainWindow.qml 2014-03-19 18:42:11 +0000 |
872 | @@ -17,10 +17,13 @@ |
873 | import QtQuick 2.0 |
874 | import QtContacts 5.0 |
875 | import Ubuntu.Components 0.1 |
876 | +import Ubuntu.Components.Popups 0.1 as Popups |
877 | |
878 | MainView { |
879 | id: mainWindow |
880 | |
881 | + property string modelErrorMessage: "" |
882 | + |
883 | width: units.gu(40) |
884 | height: units.gu(71) |
885 | anchorToKeyboard: false |
886 | @@ -59,6 +62,7 @@ |
887 | signal createContactRequested(string phoneNumber) |
888 | signal editContatRequested(string contactId, string phoneNumber) |
889 | signal contactCreated(QtObject contact) |
890 | + signal contactModelError(string errorMessage) |
891 | |
892 | anchors { |
893 | fill: parent |
894 | @@ -69,6 +73,11 @@ |
895 | } |
896 | } |
897 | } |
898 | + |
899 | + onContactModelError: { |
900 | + modelErrorMessage = errorMessage |
901 | + PopupUtils.open(errorDialog, null) |
902 | + } |
903 | } |
904 | |
905 | Component.onCompleted: { |
906 | @@ -76,6 +85,23 @@ |
907 | mainWindow.applicationReady() |
908 | } |
909 | |
910 | + Component { |
911 | + id: errorDialog |
912 | + |
913 | + Popups.Dialog { |
914 | + id: dialogue |
915 | + |
916 | + title: i18n.tr("Error") |
917 | + text: mainWindow.modelErrorMessage |
918 | + |
919 | + Button { |
920 | + text: "Cancel" |
921 | + gradient: UbuntuColors.greyGradient |
922 | + onClicked: PopupUtils.close(dialogue) |
923 | + } |
924 | + } |
925 | + } |
926 | + |
927 | Connections { |
928 | target: UriHandler |
929 | onOpened: { |
930 | |
931 | === modified file 'src/imports/Ubuntu/Contacts/ContactDetailOnlineAccountTypeModel.qml' |
932 | --- src/imports/Ubuntu/Contacts/ContactDetailOnlineAccountTypeModel.qml 2014-01-14 20:43:59 +0000 |
933 | +++ src/imports/Ubuntu/Contacts/ContactDetailOnlineAccountTypeModel.qml 2014-03-19 18:42:11 +0000 |
934 | @@ -20,8 +20,6 @@ |
935 | ListModel { |
936 | id: typeModel |
937 | |
938 | - property bool ready: false |
939 | - |
940 | signal loaded() |
941 | |
942 | function getTypeIndex(detail) { |
943 | @@ -69,6 +67,5 @@ |
944 | /*4*/ append({"value": 7, "label": i18n.tr("Skype"), "icon": "artwork:/protocol-skype.svg"}) |
945 | /*5*/ append({"value": 8, "label": i18n.tr("Yahoo"), "icon": "artwork:/protocol-yahoo.svg"}) |
946 | loaded() |
947 | - ready = true |
948 | } |
949 | } |
950 | |
951 | === modified file 'src/imports/Ubuntu/Contacts/ContactDetailPhoneNumberTypeModel.qml' |
952 | --- src/imports/Ubuntu/Contacts/ContactDetailPhoneNumberTypeModel.qml 2014-01-14 20:43:59 +0000 |
953 | +++ src/imports/Ubuntu/Contacts/ContactDetailPhoneNumberTypeModel.qml 2014-03-19 18:42:11 +0000 |
954 | @@ -20,7 +20,6 @@ |
955 | ListModel { |
956 | id: typeModel |
957 | |
958 | - property bool ready: false |
959 | signal loaded() |
960 | |
961 | function getTypeIndex(detail) { |
962 | @@ -103,6 +102,5 @@ |
963 | append({"value": "Other", "label": i18n.tr("Other"), "icon": null, |
964 | "context": QtContacts.ContactDetail.ContextOther, "subType": QtContacts.PhoneNumber.Landline }) |
965 | loaded() |
966 | - ready = true |
967 | } |
968 | } |
969 | |
970 | === modified file 'src/imports/Ubuntu/Contacts/ContactFetch.qml' |
971 | --- src/imports/Ubuntu/Contacts/ContactFetch.qml 2013-11-04 14:56:40 +0000 |
972 | +++ src/imports/Ubuntu/Contacts/ContactFetch.qml 2014-03-19 18:42:11 +0000 |
973 | @@ -19,9 +19,11 @@ |
974 | Item { |
975 | id: root |
976 | |
977 | + readonly property alias contact: connections.contact |
978 | + readonly property alias contactId: connections.contactId |
979 | property alias model: connections.target |
980 | + |
981 | property bool running: false |
982 | - property QtObject contact: null |
983 | property bool contactIsDirty: false |
984 | |
985 | property string _pendingId: "" |
986 | @@ -29,6 +31,7 @@ |
987 | |
988 | signal contactFetched(QtObject contact) |
989 | signal contactRemoved() |
990 | + signal contactNotFound() |
991 | |
992 | function fetchContact(contactId) { |
993 | if (root._ready) { |
994 | @@ -58,20 +61,9 @@ |
995 | } |
996 | } |
997 | |
998 | - Connections { |
999 | - target: root.model |
1000 | - |
1001 | - onContactsChanged: { |
1002 | - if (root.contact) { |
1003 | - root.contactIsDirty = true |
1004 | - |
1005 | - for (var i=0; i < root.model.contacts.length; i++) { |
1006 | - if (root.model.contacts[i].contactId == root.contact.contactId) { |
1007 | - return |
1008 | - } |
1009 | - } |
1010 | - contactRemoved() |
1011 | - } |
1012 | + onContactChanged: { |
1013 | + if (contact == null) { |
1014 | + contactRemoved() |
1015 | } |
1016 | } |
1017 | |
1018 | @@ -79,6 +71,8 @@ |
1019 | id: connections |
1020 | |
1021 | property int currentQueryId: -1 |
1022 | + property QtObject contact: null |
1023 | + property string contactId: contact ? contact.contactId : "" |
1024 | |
1025 | onContactsFetched: { |
1026 | // currentQueryId == -2 is used during a fetch using "memory" manager |
1027 | @@ -86,8 +80,12 @@ |
1028 | root.contactIsDirty = false |
1029 | root.running = false |
1030 | currentQueryId = -1 |
1031 | - root.contact = fetchedContacts[0] |
1032 | - root.contactFetched(fetchedContacts[0]) |
1033 | + if (fetchedContacts.length > 0) { |
1034 | + contact = fetchedContacts[0] |
1035 | + root.contactFetched(fetchedContacts[0]) |
1036 | + } else { |
1037 | + contactNotFound() |
1038 | + } |
1039 | } |
1040 | } |
1041 | } |
1042 | |
1043 | === modified file 'tests/autopilot/address_book_app/tests/test_edit_contact.py' |
1044 | --- tests/autopilot/address_book_app/tests/test_edit_contact.py 2014-01-28 10:20:07 +0000 |
1045 | +++ tests/autopilot/address_book_app/tests/test_edit_contact.py 2014-03-19 18:42:11 +0000 |
1046 | @@ -79,7 +79,12 @@ |
1047 | |
1048 | def test_add_email(self): |
1049 | self.add_contact("Fulano", "") |
1050 | - self.edit_contact(0) |
1051 | + edit_page = self.edit_contact(0) |
1052 | + |
1053 | + emailGroup = edit_page.select_single( |
1054 | + "ContactDetailGroupWithTypeEditor", |
1055 | + objectName="emails") |
1056 | + self.create_new_detail(emailGroup) |
1057 | |
1058 | # fill email address |
1059 | email_field = self.main_window.select_single( |
FAILED: Continuous integration, rev:143 jenkins. qa.ubuntu. com/job/ address- book-app- ci/446/ jenkins. qa.ubuntu. com/job/ address- book-app- trusty- amd64-ci/ 156 jenkins. qa.ubuntu. com/job/ address- book-app- trusty- armhf-ci/ 156 jenkins. qa.ubuntu. com/job/ address- book-app- trusty- armhf-ci/ 156/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ address- book-app- trusty- i386-ci/ 156 jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty/ 3691 jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty- touch/3285/ console jenkins. qa.ubuntu. com/job/ autopilot- testrunner- otto-trusty/ 3242 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/3696 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/3696/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- armhf/3287 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- armhf/3287/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- mediumtests- runner- mako/5657/ console s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 4503
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: s-jenkins. ubuntu- ci:8080/ job/address- book-app- ci/446/ rebuild
http://