Merge lp:~renatofilho/address-book-app/release-2014-07-02 into lp:address-book-app
- release-2014-07-02
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Bill Filler | ||||
Approved revision: | 205 | ||||
Merged at revision: | 224 | ||||
Proposed branch: | lp:~renatofilho/address-book-app/release-2014-07-02 | ||||
Merge into: | lp:address-book-app | ||||
Diff against target: |
1768 lines (+1109/-260) 23 files modified
README (+26/-0) debian/control (+10/-2) po/address-book-app.pot (+17/-13) src/app/addressbookapp.cpp (+1/-1) src/imports/CMakeLists.txt (+2/-0) src/imports/Common/ContactDetailBase.qml (+7/-0) src/imports/ContactList/ContactListPage.qml (+1/-15) src/imports/ContactView/ContactView.qml (+11/-13) src/imports/Ubuntu/Contacts/CMakeLists.txt (+31/-0) src/imports/Ubuntu/Contacts/ContactDelegate.qml (+7/-7) src/imports/Ubuntu/Contacts/ContactListView.qml (+4/-3) src/imports/Ubuntu/Contacts/ContactSimpleListView.qml (+39/-6) src/imports/Ubuntu/Contacts/ListItemWithActions.qml (+292/-0) src/imports/Ubuntu/Contacts/MostCalledModel.qml (+27/-110) src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp (+302/-0) src/imports/Ubuntu/Contacts/mostcalledproxymodel.h (+95/-0) src/imports/Ubuntu/Contacts/plugin.cpp (+33/-0) src/imports/Ubuntu/Contacts/plugin.h (+34/-0) src/imports/Ubuntu/Contacts/qmldir (+3/-0) tests/CMakeLists.txt (+2/-0) tests/autopilot/address_book_app/tests/test_add_contact.py (+0/-90) tests/qml/CMakeLists.txt (+22/-0) tests/qml/tst_ContactEditor.qml (+143/-0) |
||||
To merge this branch: | bzr merge lp:~renatofilho/address-book-app/release-2014-07-02 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Ubuntu Phablet Team | Pending | ||
Review via email: mp+225418@code.launchpad.net |
Commit message
* Implemented MostCalledConta
* Enabled share button again.
* Fixed build warning: comparison with string literal results in unspecified behavior;
* Implemented swipe to delete.
Description of the change
Renato Araujo Oliveira Filho (renatofilho) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:204
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 205. By Renato Araujo Oliveira Filho
-
Staging merged.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:205
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 206. By Renato Araujo Oliveira Filho
-
Does not install unit tests dependency for [arm64, powerpc and ppc64el]
Some packages necessary to run the unit test is not available for all architectures.
- 207. By Renato Araujo Oliveira Filho
-
Check for "qmltestrunner" and "xvfb-run" installation before run qml tests.
- 208. By Renato Araujo Oliveira Filho
-
Used cmake variable with full path for XVFB_RUN_BIN
Preview Diff
1 | === renamed file 'README.click' => 'README' |
2 | --- README.click 2014-06-07 13:07:42 +0000 |
3 | +++ README 2014-07-04 14:53:15 +0000 |
4 | @@ -1,3 +1,29 @@ |
5 | +Building for desktop |
6 | +==================== |
7 | + |
8 | +mkdir build |
9 | +cd build |
10 | +cmake .. |
11 | +make |
12 | + |
13 | +Run on desktop |
14 | +============== |
15 | + |
16 | +cd build |
17 | +./src/app/address-book-app |
18 | + |
19 | +Run the QML tests |
20 | +================= |
21 | + |
22 | +cd build |
23 | +make test or ctest |
24 | + |
25 | +Run the Autopilot tests |
26 | +======================= |
27 | + |
28 | +cd build |
29 | +make autopilot |
30 | + |
31 | Building for click |
32 | ================== |
33 | |
34 | |
35 | === modified file 'debian/control' |
36 | --- debian/control 2014-06-24 21:57:09 +0000 |
37 | +++ debian/control 2014-07-04 14:53:15 +0000 |
38 | @@ -9,9 +9,18 @@ |
39 | libgles2-mesa-dev, |
40 | python3, |
41 | pkg-config, |
42 | + qml-module-qttest [i386 amd64 armhf], |
43 | + qtdeclarative5-dev-tools [i386 amd64 armhf], |
44 | + qtdeclarative5-qtcontacts-plugin [i386 amd64 armhf], |
45 | + qtdeclarative5-ubuntu-content0.1 [i386 amd64 armhf], |
46 | + qtdeclarative5-ubuntu-keyboard-extensions0.1 [i386 amd64 armhf], |
47 | + qtdeclarative5-ubuntu-telephony-phonenumber0.1 [i386 amd64 armhf], |
48 | + qtdeclarative5-ubuntu-ui-toolkit-plugin [i386 amd64 armhf], |
49 | qt5-default, |
50 | qtbase5-dev, |
51 | qtdeclarative5-dev, |
52 | + qtpim5-dev, |
53 | + xvfb [i386 amd64 armhf], |
54 | Standards-Version: 3.9.5 |
55 | Homepage: https://launchpad.net/address-book-app |
56 | # If you aren't a member of ~phablet-team but need to upload packaging changes, |
57 | @@ -32,7 +41,6 @@ |
58 | qtdeclarative5-ubuntu-history0.1, |
59 | qtdeclarative5-ubuntu-keyboard-extensions0.1, |
60 | qtdeclarative5-ubuntu-telephony-phonenumber0.1, |
61 | - qtdeclarative5-ubuntu-telephony0.1, |
62 | ${misc:Depends}, |
63 | ${shlibs:Depends}, |
64 | Description: Address Book application |
65 | @@ -76,4 +84,4 @@ |
66 | address-book-app (>= ${binary:Version}), |
67 | ubuntu-mobile-icons, |
68 | Description: Test package for address-book-app |
69 | - Autopilot tests for the address-book-app package |
70 | + Autopilot tests for the address-book-app package |
71 | |
72 | === modified file 'po/address-book-app.pot' |
73 | --- po/address-book-app.pot 2014-06-27 12:54:55 +0000 |
74 | +++ po/address-book-app.pot 2014-07-04 14:53:15 +0000 |
75 | @@ -8,7 +8,7 @@ |
76 | msgstr "" |
77 | "Project-Id-Version: address-book-app\n" |
78 | "Report-Msgid-Bugs-To: \n" |
79 | -"POT-Creation-Date: 2014-06-27 09:54-0300\n" |
80 | +"POT-Creation-Date: 2014-06-30 15:01-0300\n" |
81 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
82 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
83 | "Language-Team: LANGUAGE <LL@li.org>\n" |
84 | @@ -55,12 +55,12 @@ |
85 | |
86 | #: src/imports/ContactEdit/AddFieldDialog.qml:133 |
87 | #: src/imports/ContactEdit/ContactEditor.qml:372 |
88 | -#: src/imports/ContactList/ContactListPage.qml:324 |
89 | +#: src/imports/ContactList/ContactListPage.qml:314 |
90 | #: src/imports/Ubuntu/Contacts/DialogButtons.qml:37 |
91 | msgid "Cancel" |
92 | msgstr "" |
93 | |
94 | -#: src/imports/ContactList/ContactListPage.qml:252 |
95 | +#: src/imports/ContactList/ContactListPage.qml:242 |
96 | msgid "Cancel selection" |
97 | msgstr "" |
98 | |
99 | @@ -86,8 +86,8 @@ |
100 | msgstr "" |
101 | |
102 | #: src/imports/ContactEdit/ContactEditor.qml:337 |
103 | -#: src/imports/ContactList/ContactListPage.qml:148 |
104 | -#: src/imports/ContactList/ContactListPage.qml:277 |
105 | +#: src/imports/ContactList/ContactListPage.qml:146 |
106 | +#: src/imports/ContactList/ContactListPage.qml:267 |
107 | msgid "Delete" |
108 | msgstr "" |
109 | |
110 | @@ -96,7 +96,7 @@ |
111 | msgstr "" |
112 | |
113 | #: src/imports/ContactEdit/ContactEditor.qml:141 |
114 | -#: src/imports/ContactView/ContactView.qml:219 |
115 | +#: src/imports/ContactView/ContactView.qml:217 |
116 | msgid "Edit" |
117 | msgstr "" |
118 | |
119 | @@ -173,7 +173,7 @@ |
120 | msgid "Loading" |
121 | msgstr "" |
122 | |
123 | -#: src/imports/ContactList/ContactListPage.qml:242 |
124 | +#: src/imports/ContactList/ContactListPage.qml:232 |
125 | msgid "Loading..." |
126 | msgstr "" |
127 | |
128 | @@ -246,15 +246,15 @@ |
129 | msgid "Save" |
130 | msgstr "" |
131 | |
132 | -#: src/imports/ContactList/ContactListPage.qml:302 |
133 | +#: src/imports/ContactList/ContactListPage.qml:292 |
134 | msgid "Search" |
135 | msgstr "" |
136 | |
137 | -#: src/imports/ContactList/ContactListPage.qml:277 |
138 | +#: src/imports/ContactList/ContactListPage.qml:267 |
139 | msgid "Select" |
140 | msgstr "" |
141 | |
142 | -#: src/imports/ContactList/ContactListPage.qml:261 |
143 | +#: src/imports/ContactList/ContactListPage.qml:251 |
144 | msgid "Select All" |
145 | msgstr "" |
146 | |
147 | @@ -266,6 +266,10 @@ |
148 | msgid "Select a field" |
149 | msgstr "" |
150 | |
151 | +#: src/imports/ContactView/ContactView.qml:206 |
152 | +msgid "Share" |
153 | +msgstr "" |
154 | + |
155 | #: src/imports/Ubuntu/Contacts/ContactDetailOnlineAccountTypeModel.qml:67 |
156 | msgid "Skype" |
157 | msgstr "" |
158 | @@ -279,15 +283,15 @@ |
159 | msgid "Street" |
160 | msgstr "" |
161 | |
162 | -#: src/imports/ContactList/ContactListPage.qml:293 |
163 | +#: src/imports/ContactList/ContactListPage.qml:283 |
164 | msgid "Sync" |
165 | msgstr "" |
166 | |
167 | -#: src/imports/ContactList/ContactListPage.qml:293 |
168 | +#: src/imports/ContactList/ContactListPage.qml:283 |
169 | msgid "Syncing" |
170 | msgstr "" |
171 | |
172 | -#: src/imports/ContactList/ContactListPage.qml:242 |
173 | +#: src/imports/ContactList/ContactListPage.qml:232 |
174 | msgid "Syncing..." |
175 | msgstr "" |
176 | |
177 | |
178 | === modified file 'src/app/addressbookapp.cpp' |
179 | --- src/app/addressbookapp.cpp 2014-06-26 15:46:45 +0000 |
180 | +++ src/app/addressbookapp.cpp 2014-07-04 14:53:15 +0000 |
181 | @@ -74,7 +74,7 @@ |
182 | QString appPath = QCoreApplication::applicationDirPath(); |
183 | if (appPath.startsWith(ADDRESS_BOOK_DEV_BINDIR)) { |
184 | return QString(ADDRESS_BOOK_APP_DEV_DATADIR) + suffix; |
185 | - } else if (QT_EXTRA_IMPORTS_DIR != ""){ |
186 | + } else if (!QStringLiteral(QT_EXTRA_IMPORTS_DIR).isEmpty()) { |
187 | return QString(QT_EXTRA_IMPORTS_DIR) + suffix; |
188 | } else { |
189 | return ""; |
190 | |
191 | === modified file 'src/imports/CMakeLists.txt' |
192 | --- src/imports/CMakeLists.txt 2014-06-11 21:25:08 +0000 |
193 | +++ src/imports/CMakeLists.txt 2014-07-04 14:53:15 +0000 |
194 | @@ -1,3 +1,5 @@ |
195 | +project(imports) |
196 | + |
197 | set(ADDRESS_BOOK_APP_QMLS |
198 | MainWindow.qml |
199 | ) |
200 | |
201 | === modified file 'src/imports/Common/ContactDetailBase.qml' |
202 | --- src/imports/Common/ContactDetailBase.qml 2014-06-12 18:54:18 +0000 |
203 | +++ src/imports/Common/ContactDetailBase.qml 2014-07-04 14:53:15 +0000 |
204 | @@ -60,6 +60,12 @@ |
205 | imMap[QtContacts.OnlineAccount.Protocol] = "imProtocol" |
206 | imMap[QtContacts.OnlineAccount.Capabilities] = "imCaps" |
207 | |
208 | + // organization |
209 | + var organizationMap = {} |
210 | + organizationMap[QtContacts.Organization.Name] = 'orgName' |
211 | + organizationMap[QtContacts.Organization.Role] = 'orgRole' |
212 | + organizationMap[QtContacts.Organization.Title] = 'orgTitle' |
213 | + |
214 | // SyncTarget |
215 | var syncTargetMap = {} |
216 | syncTargetMap[QtContacts.SyncTarget.SyncTarget] = "syncTarget" |
217 | @@ -71,6 +77,7 @@ |
218 | detailMap[QtContacts.ContactDetail.Email] = emailMap |
219 | detailMap[QtContacts.ContactDetail.Address] = addressMap |
220 | detailMap[QtContacts.ContactDetail.OnlineAccount] = imMap |
221 | + detailMap[QtContacts.ContactDetail.Organization] = organizationMap |
222 | detailMap[QtContacts.ContactDetail.SyncTarget] = syncTargetMap |
223 | |
224 | // detail name |
225 | |
226 | === modified file 'src/imports/ContactList/ContactListPage.qml' |
227 | --- src/imports/ContactList/ContactListPage.qml 2014-06-27 12:54:15 +0000 |
228 | +++ src/imports/ContactList/ContactListPage.qml 2014-07-04 14:53:15 +0000 |
229 | @@ -141,20 +141,10 @@ |
230 | multipleSelection: !pickMode || |
231 | mainPage.pickMultipleContacts || (contactExporter.active && contactExporter.isMultiple) |
232 | |
233 | - anchors.fill: parent |
234 | - |
235 | leftSideAction: Action { |
236 | iconName: "delete" |
237 | text: i18n.tr("Delete") |
238 | - onTriggered: { |
239 | - value.makeDisappear() |
240 | - } |
241 | - } |
242 | - |
243 | - onContactDisappeared: { |
244 | - if (contact) { |
245 | - contactModel.removeContact(contact.contactId) |
246 | - } |
247 | + onTriggered: value.remove() |
248 | } |
249 | |
250 | onCountChanged: { |
251 | @@ -168,10 +158,6 @@ |
252 | mainPage.onlineAccountsMessageDialog = null |
253 | application.unsetFirstRun() |
254 | } |
255 | - |
256 | - if (mainPage.searching) { |
257 | - contactList.positionViewAtBeginning() |
258 | - } |
259 | } |
260 | |
261 | onAddContactClicked: mainPage.createContactWithPhoneNumber(label) |
262 | |
263 | === modified file 'src/imports/ContactView/ContactView.qml' |
264 | --- src/imports/ContactView/ContactView.qml 2014-06-13 19:54:01 +0000 |
265 | +++ src/imports/ContactView/ContactView.qml 2014-07-04 14:53:15 +0000 |
266 | @@ -200,19 +200,17 @@ |
267 | } |
268 | } |
269 | } |
270 | - // FIXME: Having more than 3 options in the header causes a bug that make difficult to reach the component behind it. |
271 | - // Enable it again when the bug #1329557 get fix |
272 | -// ToolbarButton { |
273 | -// action: Action { |
274 | -// objectName: "share" |
275 | -// text: i18n.tr("Share") |
276 | -// iconName: "share" |
277 | -// onTriggered: { |
278 | -// pageStack.push(Qt.resolvedUrl("../ContactShare/ContactSharePage.qml"), |
279 | -// { contactModel: root.model, contact: root.contact}) |
280 | -// } |
281 | -// } |
282 | -// } |
283 | + ToolbarButton { |
284 | + action: Action { |
285 | + objectName: "share" |
286 | + text: i18n.tr("Share") |
287 | + iconName: "share" |
288 | + onTriggered: { |
289 | + pageStack.push(Qt.resolvedUrl("../ContactShare/ContactSharePage.qml"), |
290 | + { contactModel: root.model, contact: root.contact}) |
291 | + } |
292 | + } |
293 | + } |
294 | ToolbarButton { |
295 | action: Action { |
296 | objectName: "edit" |
297 | |
298 | === modified file 'src/imports/Ubuntu/Contacts/CMakeLists.txt' |
299 | --- src/imports/Ubuntu/Contacts/CMakeLists.txt 2014-06-18 18:19:09 +0000 |
300 | +++ src/imports/Ubuntu/Contacts/CMakeLists.txt 2014-07-04 14:53:15 +0000 |
301 | @@ -1,3 +1,5 @@ |
302 | +set(CONTACT_COMPONENTS_PLUGIN "ubuntu-contacts-qml") |
303 | + |
304 | set(CONTACT_COMPONENTS_QMLS |
305 | ContactList.js |
306 | ContactAvatar.qml |
307 | @@ -12,15 +14,44 @@ |
308 | DialogButtons.qml |
309 | FastScroll.qml |
310 | FastScroll.js |
311 | + ListItemWithActions.qml |
312 | MostCalledModel.qml |
313 | MultipleSelectionListView.qml |
314 | MultipleSelectionVisualModel.qml |
315 | qmldir |
316 | ) |
317 | |
318 | +set(CONTACT_COMPONENTS_SRC |
319 | + mostcalledproxymodel.h |
320 | + mostcalledproxymodel.cpp |
321 | + plugin.h |
322 | + plugin.cpp |
323 | +) |
324 | + |
325 | +add_library(${CONTACT_COMPONENTS_PLUGIN} MODULE |
326 | + ${CONTACT_COMPONENTS_SRC} |
327 | +) |
328 | + |
329 | +qt5_use_modules(${CONTACT_COMPONENTS_PLUGIN} Core Contacts Qml Quick) |
330 | + |
331 | # make the files visible on qtcreator |
332 | add_custom_target(contact_components_QmlFiles ALL SOURCES ${CONTACT_COMPONENTS_QMLS}) |
333 | |
334 | if(INSTALL_COMPONENTS) |
335 | install(FILES ${CONTACT_COMPONENTS_QMLS} DESTINATION ${QMLPLUGIN_INSTALL_PREFIX}) |
336 | + install(TARGETS ${CONTACT_COMPONENTS_PLUGIN} DESTINATION ${QMLPLUGIN_INSTALL_PREFIX}) |
337 | +endif() |
338 | + |
339 | + |
340 | +#copy qml files to build dir to make it possible to run without install |
341 | + |
342 | +add_custom_target(copy_qml) |
343 | +foreach(QML_FILE ${CONTACT_COMPONENTS_QMLS}) |
344 | + add_custom_command(TARGET copy_qml PRE_BUILD |
345 | + COMMAND ${CMAKE_COMMAND} -E |
346 | + copy ${CMAKE_CURRENT_SOURCE_DIR}/${QML_FILE} ${CMAKE_CURRENT_BINARY_DIR}/) |
347 | +endforeach() |
348 | + |
349 | +if (NOT ${CMAKE_CURRENT_BINARY_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) |
350 | + add_dependencies(${CONTACT_COMPONENTS_PLUGIN} copy_qml) |
351 | endif() |
352 | |
353 | === modified file 'src/imports/Ubuntu/Contacts/ContactDelegate.qml' |
354 | --- src/imports/Ubuntu/Contacts/ContactDelegate.qml 2014-06-23 18:31:56 +0000 |
355 | +++ src/imports/Ubuntu/Contacts/ContactDelegate.qml 2014-07-04 14:53:15 +0000 |
356 | @@ -20,7 +20,7 @@ |
357 | import Ubuntu.Components.ListItems 0.1 as ListItem |
358 | import "Contacts.js" as ContactsJS |
359 | |
360 | -Item { |
361 | +ListItemWithActions { |
362 | id: root |
363 | |
364 | property bool showAvatar: true |
365 | @@ -32,6 +32,7 @@ |
366 | property variant titleFields: [ Name.FirstName, Name.LastName ] |
367 | property bool detailsShown: false |
368 | property int loaderOpacity: 0.0 |
369 | + property var _contact: contact |
370 | |
371 | signal clicked(int index, QtObject contact) |
372 | signal pressAndHold(int index, QtObject contact) |
373 | @@ -47,6 +48,10 @@ |
374 | height: delegate.height |
375 | implicitHeight: delegate.height + (pickerLoader.item ? pickerLoader.item.height : 0) |
376 | width: parent ? parent.width : 0 |
377 | + color: Theme.palette.normal.background |
378 | + |
379 | + onItemClicked: root.clicked(index, contact) |
380 | + onItemPressAndHold: root.pressAndHold(index, contact) |
381 | |
382 | Item { |
383 | id: delegate |
384 | @@ -67,12 +72,6 @@ |
385 | visible: root.selected || root.detailsShown |
386 | } |
387 | |
388 | - MouseArea { |
389 | - anchors.fill: parent |
390 | - onClicked: root.clicked(index, contact) |
391 | - onPressAndHold: root.pressAndHold(index, contact) |
392 | - } |
393 | - |
394 | ContactAvatar { |
395 | id: avatar |
396 | |
397 | @@ -181,6 +180,7 @@ |
398 | clip: true |
399 | height: root.implicitHeight |
400 | loaderOpacity: 1.0 |
401 | + locked: true |
402 | // FIXME: Setting detailsShown to true on expanded state cause the property to change to false and true during the state transition, and that |
403 | // causes the loader to load twice |
404 | //detailsShown: true |
405 | |
406 | === modified file 'src/imports/Ubuntu/Contacts/ContactListView.qml' |
407 | --- src/imports/Ubuntu/Contacts/ContactListView.qml 2014-07-01 18:48:39 +0000 |
408 | +++ src/imports/Ubuntu/Contacts/ContactListView.qml 2014-07-04 14:53:15 +0000 |
409 | @@ -441,14 +441,14 @@ |
410 | model: MostCalledModel { |
411 | id: calledModel |
412 | |
413 | - readonly property bool visible: view.favouritesIsSelected |
414 | + readonly property bool visible: view.favouritesIsSelected |
415 | |
416 | onVisibleChanged: { |
417 | + // update the model every time that it became visible |
418 | if (visible) { |
419 | - filterEntries() |
420 | + model.update() |
421 | } |
422 | } |
423 | - maxCount: 20 |
424 | onInfoRequested: root.infoRequested(contact) |
425 | onDetailClicked: root.detailClicked(contact, detail, action) |
426 | onAddContactClicked: root.addContactClicked(label) |
427 | @@ -549,6 +549,7 @@ |
428 | repeat: false |
429 | interval: 300 |
430 | onTriggered: { |
431 | + view.positionViewAtBeginning() |
432 | var needUpdate = false |
433 | if (root.filterTerm === "") { // if the search criteria is empty clear the list before show all contacts |
434 | if (contactTermFilter.value !== "") { |
435 | |
436 | === modified file 'src/imports/Ubuntu/Contacts/ContactSimpleListView.qml' |
437 | --- src/imports/Ubuntu/Contacts/ContactSimpleListView.qml 2014-06-19 15:38:16 +0000 |
438 | +++ src/imports/Ubuntu/Contacts/ContactSimpleListView.qml 2014-07-04 14:53:15 +0000 |
439 | @@ -268,6 +268,13 @@ |
440 | listDelegate: ContactDelegate { |
441 | id: contactDelegate |
442 | |
443 | + property var removalAnimation |
444 | + |
445 | + function remove() |
446 | + { |
447 | + removalAnimation.start() |
448 | + } |
449 | + |
450 | width: parent.width |
451 | selected: contactListView.multiSelectionEnabled && contactListView.isSelected(contactDelegate) |
452 | defaultAvatarUrl: contactListView.defaultAvatarImageUrl |
453 | @@ -275,17 +282,43 @@ |
454 | titleFields: contactListView.titleFields |
455 | isCurrentItem: ListView.isCurrentItem |
456 | |
457 | + // actions |
458 | + leftSideAction: contactListView.leftSideAction |
459 | + rightSideActions: contactListView.rightSideActions |
460 | + |
461 | onDetailClicked: contactListView.detailClicked(contact, detail, action) |
462 | onInfoRequested: contactListView._fetchContact(index, contact) |
463 | |
464 | // collapse the item before remove it, to avoid crash |
465 | - ListView.onRemove: SequentialAnimation { |
466 | + ListView.onRemove: ScriptAction { |
467 | + script: { |
468 | + if (contactDelegate.state !== "") { |
469 | + contactListView.currentIndex = -1 |
470 | + } |
471 | + } |
472 | + } |
473 | + |
474 | + // used by swipe to delete |
475 | + removalAnimation: SequentialAnimation { |
476 | + alwaysRunToEnd: true |
477 | + |
478 | + PropertyAction { |
479 | + target: contactDelegate |
480 | + property: "ListView.delayRemove" |
481 | + value: true |
482 | + } |
483 | + UbuntuNumberAnimation { |
484 | + target: contactDelegate |
485 | + property: "height" |
486 | + to: 1 |
487 | + } |
488 | + PropertyAction { |
489 | + target: contactDelegate |
490 | + property: "ListView.delayRemove" |
491 | + value: false |
492 | + } |
493 | ScriptAction { |
494 | - script: { |
495 | - if (contactDelegate.state !== "") { |
496 | - contactListView.currentIndex = -1 |
497 | - } |
498 | - } |
499 | + script: contactListView.listModel.removeContact(contact.contactId) |
500 | } |
501 | } |
502 | |
503 | |
504 | === added file 'src/imports/Ubuntu/Contacts/ListItemWithActions.qml' |
505 | --- src/imports/Ubuntu/Contacts/ListItemWithActions.qml 1970-01-01 00:00:00 +0000 |
506 | +++ src/imports/Ubuntu/Contacts/ListItemWithActions.qml 2014-07-04 14:53:15 +0000 |
507 | @@ -0,0 +1,292 @@ |
508 | +/* |
509 | + * Copyright (C) 2012-2014 Canonical, Ltd. |
510 | + * |
511 | + * This program is free software; you can redistribute it and/or modify |
512 | + * it under the terms of the GNU General Public License as published by |
513 | + * the Free Software Foundation; version 3. |
514 | + * |
515 | + * This program is distributed in the hope that it will be useful, |
516 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
517 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
518 | + * GNU General Public License for more details. |
519 | + * |
520 | + * You should have received a copy of the GNU General Public License |
521 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
522 | + */ |
523 | + |
524 | +import QtQuick 2.2 |
525 | +import Ubuntu.Components 0.1 |
526 | + |
527 | +Item { |
528 | + id: root |
529 | + |
530 | + property Action leftSideAction: null |
531 | + property list<Action> rightSideActions |
532 | + property double defaultHeight: units.gu(8) |
533 | + property bool locked: false |
534 | + property Action activeAction: null |
535 | + property var activeItem: null |
536 | + property bool triggerActionOnMouseRelease: false |
537 | + property alias color: main.color |
538 | + default property alias contents: main.children |
539 | + |
540 | + readonly property double actionWidth: units.gu(5) |
541 | + readonly property double threshold: 0.4 |
542 | + readonly property string swipeState: main.x == 0 ? "Normal" : main.x > 0 ? "LeftToRight" : "RightToLeft" |
543 | + |
544 | + signal itemClicked(var mouse) |
545 | + signal itemPressAndHold(var mouse) |
546 | + |
547 | + function returnToBoundsRTL() |
548 | + { |
549 | + var xOffset = Math.abs(main.x) |
550 | + var actionFullWidth = actionWidth + units.gu(1) |
551 | + |
552 | + if (xOffset < actionFullWidth) { |
553 | + main.x = 0 |
554 | + } else if (xOffset > (actionFullWidth * rightActionsRepeater.count)) { |
555 | + main.x = - (actionFullWidth * rightActionsRepeater.count) |
556 | + } else { |
557 | + for (var i = rightActionsRepeater.count; i >= 2; i--) { |
558 | + if (xOffset >= (actionFullWidth * i)) { |
559 | + main.x = -(actionWidth * i) |
560 | + return |
561 | + } |
562 | + } |
563 | + main.x = -actionWidth |
564 | + } |
565 | + } |
566 | + |
567 | + function returnToBoundsLTR() |
568 | + { |
569 | + var finalX = leftActionView.width |
570 | + if (main.x > (finalX * root.threshold)) |
571 | + main.x = finalX |
572 | + else |
573 | + main.x = 0 |
574 | + } |
575 | + |
576 | + function returnToBounds() |
577 | + { |
578 | + if (main.x < 0) { |
579 | + returnToBoundsRTL() |
580 | + } else if (main.x > 0) { |
581 | + returnToBoundsLTR() |
582 | + } |
583 | + } |
584 | + |
585 | + function contains(item, point) |
586 | + { |
587 | + return (point.x >= item.x) && (point.x <= (item.x + item.width)) && (point.y >= item.y) && (point.y <= (item.y + item.height)); |
588 | + } |
589 | + |
590 | + function getActionAt(point) |
591 | + { |
592 | + if (contains(leftActionView, point)) { |
593 | + return leftSideAction |
594 | + } else if (contains(rightActionsView, point)) { |
595 | + var newPoint = root.mapToItem(rightActionsView, point.x, point.y) |
596 | + for (var i = 0; i < rightActionsRepeater.count; i++) { |
597 | + var child = rightActionsRepeater.itemAt(i) |
598 | + if (contains(child, newPoint)) { |
599 | + return rightSideActions[i] |
600 | + } |
601 | + } |
602 | + } |
603 | + return null |
604 | + } |
605 | + |
606 | + function updateActiveAction() |
607 | + { |
608 | + var xOffset = Math.abs(main.x) |
609 | + if (main.x < 0) { |
610 | + for (var i = rightActionsRepeater.count - 1; i >= 0; i--) { |
611 | + var child = rightActionsRepeater.itemAt(i) |
612 | + var childOffset = rightActionsView.width - child.x |
613 | + if (xOffset <= childOffset) { |
614 | + root.activeItem = child |
615 | + root.activeAction = root.rightSideActions[i] |
616 | + return |
617 | + } |
618 | + } |
619 | + } else { |
620 | + root.activeAction = null |
621 | + } |
622 | + } |
623 | + |
624 | + function resetSwipe() |
625 | + { |
626 | + main.x = 0 |
627 | + } |
628 | + |
629 | + height: defaultHeight |
630 | + clip: height !== defaultHeight |
631 | + |
632 | + Rectangle { |
633 | + id: leftActionView |
634 | + |
635 | + anchors { |
636 | + top: parent.top |
637 | + bottom: parent.bottom |
638 | + left: parent.left |
639 | + } |
640 | + width: height |
641 | + visible: leftSideAction |
642 | + color: "red" |
643 | + |
644 | + Icon { |
645 | + anchors.centerIn: parent |
646 | + name: leftSideAction ? leftSideAction.iconName : "" |
647 | + color: Theme.palette.selected.field |
648 | + height: units.gu(3) |
649 | + width: units.gu(3) |
650 | + } |
651 | + } |
652 | + |
653 | + Item { |
654 | + id: rightActionsView |
655 | + |
656 | + anchors { |
657 | + top: main.top |
658 | + right: parent.right |
659 | + bottom: main.bottom |
660 | + } |
661 | + width: rightActionsRepeater.count * (root.actionWidth + units.gu(1)) |
662 | + Row { |
663 | + anchors.fill: parent |
664 | + spacing: units.gu(1) |
665 | + Repeater { |
666 | + id: rightActionsRepeater |
667 | + |
668 | + model: rightSideActions |
669 | + Item { |
670 | + property alias image: img |
671 | + |
672 | + anchors { |
673 | + top: parent.top |
674 | + bottom: parent.bottom |
675 | + } |
676 | + width: root.actionWidth |
677 | + |
678 | + Icon { |
679 | + id: img |
680 | + |
681 | + anchors.centerIn: parent |
682 | + width: units.gu(3) |
683 | + height: units.gu(3) |
684 | + name: iconName |
685 | + color: root.activeAction === modelData || !root.triggerActionOnMouseRelease ? UbuntuColors.lightAubergine : Theme.palette.selected.background |
686 | + } |
687 | + } |
688 | + } |
689 | + } |
690 | + } |
691 | + |
692 | + |
693 | + Rectangle { |
694 | + id: main |
695 | + |
696 | + anchors { |
697 | + top: parent.top |
698 | + bottom: parent.bottom |
699 | + } |
700 | + |
701 | + width: parent.width |
702 | + Behavior on x { |
703 | + UbuntuNumberAnimation { |
704 | + easing.type: Easing.OutElastic |
705 | + duration: UbuntuAnimation.SlowDuration |
706 | + } |
707 | + } |
708 | + } |
709 | + |
710 | + SequentialAnimation { |
711 | + id: triggerAction |
712 | + |
713 | + property var currentItem: root.activeItem ? root.activeItem.image : null |
714 | + |
715 | + running: false |
716 | + ParallelAnimation { |
717 | + UbuntuNumberAnimation { |
718 | + target: triggerAction.currentItem |
719 | + property: "opacity" |
720 | + from: 1.0 |
721 | + to: 0.0 |
722 | + duration: UbuntuAnimation.SlowDuration |
723 | + easing {type: Easing.InOutBack; } |
724 | + } |
725 | + UbuntuNumberAnimation { |
726 | + target: triggerAction.currentItem |
727 | + properties: "width, height" |
728 | + from: units.gu(3) |
729 | + to: root.actionWidth |
730 | + duration: UbuntuAnimation.SlowDuration |
731 | + easing {type: Easing.InOutBack; } |
732 | + } |
733 | + } |
734 | + ScriptAction { |
735 | + script: { |
736 | + root.activeAction.triggered(root) |
737 | + } |
738 | + } |
739 | + PropertyAction { |
740 | + target: triggerAction.currentItem |
741 | + properties: "width, height" |
742 | + value: units.gu(3) |
743 | + } |
744 | + PropertyAction { |
745 | + target: triggerAction.currentItem |
746 | + properties: "opacity" |
747 | + value: 1.0 |
748 | + } |
749 | + UbuntuNumberAnimation { |
750 | + target: main |
751 | + property: "x" |
752 | + to: 0 |
753 | + easing.type: Easing.OutElastic |
754 | + duration: UbuntuAnimation.SlowDuration |
755 | + } |
756 | + } |
757 | + |
758 | + MouseArea { |
759 | + id: mouseArea |
760 | + |
761 | + property bool locked: root.locked || ((root.leftSideAction === null) && (root.rightSideActions.count === 0)) |
762 | + |
763 | + anchors.fill: parent |
764 | + drag { |
765 | + target: locked ? null : main |
766 | + axis: Drag.XAxis |
767 | + minimumX: -rightActionsView.width |
768 | + maximumX: leftActionView.visible ? leftActionView.width : 0 |
769 | + } |
770 | + |
771 | + onReleased: { |
772 | + if (root.triggerActionOnMouseRelease && root.activeAction) { |
773 | + triggerAction.start() |
774 | + } else { |
775 | + root.returnToBounds() |
776 | + } |
777 | + } |
778 | + onClicked: { |
779 | + if (main.x === 0) { |
780 | + root.itemClicked(mouse) |
781 | + return |
782 | + } |
783 | + |
784 | + var action = getActionAt(Qt.point(mouse.x, mouse.y)) |
785 | + if (action) { |
786 | + action.triggered(root) |
787 | + } |
788 | + root.resetSwipe() |
789 | + } |
790 | + |
791 | + onPositionChanged: updateActiveAction() |
792 | + onPressAndHold: { |
793 | + if (main.x === 0) { |
794 | + root.itemPressAndHold(mouse) |
795 | + } |
796 | + } |
797 | + z: -1 |
798 | + } |
799 | +} |
800 | |
801 | === modified file 'src/imports/Ubuntu/Contacts/MostCalledModel.qml' |
802 | --- src/imports/Ubuntu/Contacts/MostCalledModel.qml 2014-07-01 18:48:39 +0000 |
803 | +++ src/imports/Ubuntu/Contacts/MostCalledModel.qml 2014-07-04 14:53:15 +0000 |
804 | @@ -18,14 +18,12 @@ |
805 | import QtQuick 2.2 |
806 | import QtContacts 5.0 |
807 | import Ubuntu.History 0.1 |
808 | -import Ubuntu.Telephony 0.1 |
809 | +import Ubuntu.Contacts 0.1 |
810 | |
811 | VisualDataModel { |
812 | id: root |
813 | |
814 | - property int maxCount: 10 |
815 | property var contactModel: null |
816 | - property var historyModel |
817 | property int currentIndex: -1 |
818 | |
819 | signal clicked(int index, QtObject contact) |
820 | @@ -33,115 +31,39 @@ |
821 | signal infoRequested(int index, QtObject contact) |
822 | signal addContactClicked(string label) |
823 | |
824 | - function filterEntries() |
825 | - { |
826 | - var contacts = {} |
827 | - var interval = new Date() |
828 | - var secs = (interval.getTime() - 2592000000) // one month ago |
829 | - interval.setTime(secs) |
830 | - |
831 | - var totalCount = 0 |
832 | - var i = 0; |
833 | - while(true) { |
834 | - var event = historyModel.getItem(i) |
835 | - if (!event) { |
836 | - break |
837 | - } |
838 | - |
839 | - if (event.timestamp < interval) { |
840 | - break |
841 | - } |
842 | - |
843 | - var participants = event.participants |
844 | - for (var p=0; p < participants.length; p++) { |
845 | - var phoneNumber = participants[p] |
846 | - if (phoneNumber) { |
847 | - if (contacts[phoneNumber] === undefined) { |
848 | - contacts[phoneNumber] = 1 |
849 | - } else { |
850 | - var count = contacts[phoneNumber] |
851 | - contacts[phoneNumber] = count + 1 |
852 | - } |
853 | - totalCount += 1 |
854 | - } |
855 | - } |
856 | - i++ |
857 | - } |
858 | - |
859 | - listModel.clear() |
860 | - if (totalCount == 0) { |
861 | - return |
862 | - } |
863 | - |
864 | - // sort phones most called first |
865 | - var mostCalledFirst = [] |
866 | - for (var key in contacts) { |
867 | - mostCalledFirst.push([key, contacts[key]]); |
868 | - } |
869 | - |
870 | - mostCalledFirst.sort(function(a, b) { |
871 | - a = a[1]; |
872 | - b = b[1]; |
873 | - |
874 | - return a < b ? -1 : (a > b ? 1 : 0); |
875 | - }); |
876 | - |
877 | - contacts = {} |
878 | - for (var i = 0; i < mostCalledFirst.length; i++) { |
879 | - var key = mostCalledFirst[i][0]; |
880 | - var value = mostCalledFirst[i][1]; |
881 | - contacts[key] = value |
882 | - } |
883 | - |
884 | - // get the avarage frequency |
885 | - var average = totalCount / mostCalledFirst.length |
886 | - |
887 | - for (var phone in contacts) { |
888 | - if (contacts[phone] >= average) { |
889 | - listModel.insert(0, {"participant": phone}) |
890 | - if (listModel.count >= root.maxCount) { |
891 | - return; |
892 | - } |
893 | - } |
894 | - } |
895 | - } |
896 | - |
897 | - model: ListModel { |
898 | - id: listModel |
899 | - } |
900 | - |
901 | - historyModel: HistoryEventModel { |
902 | - |
903 | - function getItem(row) { |
904 | - while ((row >= count) && (canFetchMore())) { |
905 | - fetchMore() |
906 | - } |
907 | - return get(row) |
908 | - } |
909 | - |
910 | - type: HistoryThreadModel.EventTypeVoice |
911 | - sort: HistorySort { |
912 | - sortField: "timestamp" |
913 | - sortOrder: HistorySort.DescendingOrder |
914 | - } |
915 | - } |
916 | - |
917 | + |
918 | + model: MostCalledContactsModel { |
919 | + startInterval: new Date((new Date().getTime() - 2592000000)) // one month ago |
920 | + sourceModel: HistoryEventModel { |
921 | + type: HistoryThreadModel.EventTypeVoice |
922 | + sort: HistorySort { |
923 | + sortField: "timestamp" |
924 | + sortOrder: HistorySort.DescendingOrder |
925 | + } |
926 | + filter: HistoryFilter { |
927 | + filterProperty: "senderId" |
928 | + filterValue: "self" |
929 | + matchFlags: HistoryFilter.MatchCaseSensitive |
930 | + } |
931 | + } |
932 | + } |
933 | |
934 | delegate: ContactDelegate { |
935 | id: contactDelegate |
936 | |
937 | readonly property alias contact: contactFetch.contact |
938 | + property var contents |
939 | |
940 | onDetailClicked: root.detailClicked(contact, detail, action) |
941 | onInfoRequested: root.infoRequested(index, contact) |
942 | onAddContactClicked: root.addContactClicked(label) |
943 | |
944 | defaultAvatarUrl: "image://theme/contacts" |
945 | - defaultTitle: participant |
946 | - width: parent.width |
947 | + width: parent ? parent.width : 0 |
948 | titleDetail: ContactDetail.DisplayLabel |
949 | titleFields: [ DisplayLabel.Label ] |
950 | isCurrentItem: root.currentIndex === index |
951 | + locked: true |
952 | |
953 | // collapse the item before remove it, to avoid crash |
954 | ListView.onRemove: SequentialAnimation { |
955 | @@ -166,17 +88,12 @@ |
956 | } |
957 | } |
958 | |
959 | - ContactWatcher { |
960 | - id: contactWatcher |
961 | - |
962 | - phoneNumber: participant |
963 | - onContactIdChanged: contactFetch.fetchContact(contactId) |
964 | - } |
965 | - |
966 | - ContactFetch { |
967 | - id: contactFetch |
968 | - |
969 | - model: contactsModel |
970 | - } |
971 | + // delegate does not support more than one child |
972 | + contents: ContactFetch { |
973 | + id: contactFetch |
974 | + model: contactsModel |
975 | + } |
976 | + |
977 | + Component.onCompleted: contactFetch.fetchContact(contactId) |
978 | } |
979 | } |
980 | |
981 | === added file 'src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp' |
982 | --- src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp 1970-01-01 00:00:00 +0000 |
983 | +++ src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp 2014-07-04 14:53:15 +0000 |
984 | @@ -0,0 +1,302 @@ |
985 | +/* |
986 | + * Copyright (C) 2012-2013 Canonical, Ltd. |
987 | + * |
988 | + * This program is free software; you can redistribute it and/or modify |
989 | + * it under the terms of the GNU General Public License as published by |
990 | + * the Free Software Foundation; version 3. |
991 | + * |
992 | + * This program is distributed in the hope that it will be useful, |
993 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
994 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
995 | + * GNU General Public License for more details. |
996 | + * |
997 | + * You should have received a copy of the GNU General Public License |
998 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
999 | + */ |
1000 | + |
1001 | +#include "mostcalledproxymodel.h" |
1002 | + |
1003 | +#include <QtContacts/QContactManager> |
1004 | +#include <QtContacts/QContactFilter> |
1005 | +#include <QtContacts/QContactPhoneNumber> |
1006 | + |
1007 | +#include <QDebug> |
1008 | + |
1009 | +using namespace QtContacts; |
1010 | + |
1011 | +bool mostCalledContactsModelDataLessThan(const MostCalledContactsModelData &d1, const MostCalledContactsModelData &d2) |
1012 | +{ |
1013 | + return d1.callCount < d2.callCount; |
1014 | +} |
1015 | + |
1016 | +MostCalledContactsModel::MostCalledContactsModel(QObject *parent) |
1017 | + : QAbstractListModel(parent), |
1018 | + m_sourceModel(0), |
1019 | + m_manager(new QContactManager("galera")), |
1020 | + m_maxCount(20), |
1021 | + m_average(0), |
1022 | + m_outdated(true), |
1023 | + m_reloadingModel(false) |
1024 | +{ |
1025 | + connect(this, SIGNAL(sourceModelChanged(QAbstractItemModel*)), SLOT(markAsOutdated())); |
1026 | + connect(this, SIGNAL(maxCountChanged(uint)), SLOT(markAsOutdated())); |
1027 | + connect(this, SIGNAL(startIntervalChanged(QDateTime)), SLOT(markAsOutdated())); |
1028 | + connect(this, SIGNAL(modelReset()), SIGNAL(outdatedChange(bool))); |
1029 | +} |
1030 | + |
1031 | +MostCalledContactsModel::~MostCalledContactsModel() |
1032 | +{ |
1033 | +} |
1034 | + |
1035 | +QVariant MostCalledContactsModel::data(const QModelIndex &index, int role) const |
1036 | +{ |
1037 | + if (!index.isValid()) { |
1038 | + return QVariant(); |
1039 | + } |
1040 | + |
1041 | + int row = index.row(); |
1042 | + if ((row >= 0) && (row < m_data.size())) { |
1043 | + switch (role) |
1044 | + { |
1045 | + case MostCalledContactsModel::ContactIdRole: |
1046 | + return m_data[row].contactId; |
1047 | + case MostCalledContactsModel::PhoneNumberRole: |
1048 | + return m_data[row].phoneNumber; |
1049 | + case MostCalledContactsModel::CallCountRole: |
1050 | + return m_data[row].callCount; |
1051 | + default: |
1052 | + return QVariant(); |
1053 | + } |
1054 | + } |
1055 | + return QVariant(); |
1056 | +} |
1057 | + |
1058 | +QHash<int, QByteArray> MostCalledContactsModel::roleNames() const |
1059 | +{ |
1060 | + static QHash<int, QByteArray> roles; |
1061 | + if (roles.isEmpty()) { |
1062 | + roles.insert(MostCalledContactsModel::ContactIdRole, "contactId"); |
1063 | + roles.insert(MostCalledContactsModel::PhoneNumberRole, "phoneNumber"); |
1064 | + roles.insert(MostCalledContactsModel::CallCountRole, "callCount"); |
1065 | + } |
1066 | + return roles; |
1067 | +} |
1068 | + |
1069 | +int MostCalledContactsModel::rowCount(const QModelIndex &) const |
1070 | +{ |
1071 | + return m_data.size(); |
1072 | +} |
1073 | + |
1074 | +QAbstractItemModel *MostCalledContactsModel::sourceModel() const |
1075 | +{ |
1076 | + return m_sourceModel; |
1077 | +} |
1078 | + |
1079 | +void MostCalledContactsModel::setSourceModel(QAbstractItemModel *model) |
1080 | +{ |
1081 | + if (m_sourceModel != model) { |
1082 | + if (m_sourceModel) { |
1083 | + disconnect(m_sourceModel); |
1084 | + } |
1085 | + |
1086 | + m_sourceModel = model; |
1087 | + connect(m_sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(markAsOutdated())); |
1088 | + connect(m_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(markAsOutdated())); |
1089 | + connect(m_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(markAsOutdated())); |
1090 | + connect(m_sourceModel, SIGNAL(modelReset()), SLOT(markAsOutdated())); |
1091 | + |
1092 | + Q_EMIT sourceModelChanged(m_sourceModel); |
1093 | + } |
1094 | +} |
1095 | + |
1096 | + |
1097 | +uint MostCalledContactsModel::maxCount() const |
1098 | +{ |
1099 | + return m_maxCount; |
1100 | +} |
1101 | + |
1102 | +void MostCalledContactsModel::setMaxCount(uint value) |
1103 | +{ |
1104 | + if (m_maxCount != value) { |
1105 | + m_maxCount = value; |
1106 | + Q_EMIT maxCountChanged(m_maxCount); |
1107 | + } |
1108 | +} |
1109 | + |
1110 | +int MostCalledContactsModel::callAverage() const |
1111 | +{ |
1112 | + return m_average; |
1113 | +} |
1114 | + |
1115 | +QDateTime MostCalledContactsModel::startInterval() const |
1116 | +{ |
1117 | + return m_startInterval; |
1118 | +} |
1119 | + |
1120 | +void MostCalledContactsModel::setStartInterval(const QDateTime &value) |
1121 | +{ |
1122 | + if (m_startInterval != value) { |
1123 | + m_startInterval = value; |
1124 | + Q_EMIT startIntervalChanged(m_startInterval); |
1125 | + } |
1126 | +} |
1127 | + |
1128 | +QVariant MostCalledContactsModel::getSourceData(int row, int role) |
1129 | +{ |
1130 | + QAbstractItemModel *source = sourceModel(); |
1131 | + if (!source) { |
1132 | + return QVariant(); |
1133 | + } |
1134 | + |
1135 | + while ((source->rowCount() <= row) && (source->canFetchMore(QModelIndex()))) { |
1136 | + source->fetchMore(QModelIndex()); |
1137 | + } |
1138 | + |
1139 | + if (source->rowCount() < row) { |
1140 | + return QVariant(); |
1141 | + } |
1142 | + |
1143 | + QModelIndex sourceIndex = source->index(row, 0); |
1144 | + return source->data(sourceIndex, role); |
1145 | +} |
1146 | + |
1147 | +QString MostCalledContactsModel::fetchContactId(const QString &phoneNumber) |
1148 | +{ |
1149 | + QContactFilter filter(QContactPhoneNumber::match(phoneNumber)); |
1150 | + QContactFetchHint hint; |
1151 | + hint.setDetailTypesHint(QList<QContactDetail::DetailType>() << QContactDetail::TypeGuid); |
1152 | + QList<QContact> contacts = m_manager->contacts(filter, QList<QContactSortOrder>() , hint); |
1153 | + if (contacts.isEmpty()) { |
1154 | + return QString(); |
1155 | + } |
1156 | + return contacts[0].id().toString(); |
1157 | +} |
1158 | + |
1159 | +void MostCalledContactsModel::update() |
1160 | +{ |
1161 | + // skip update if not necessary |
1162 | + if (!m_outdated || m_reloadingModel) { |
1163 | + return; |
1164 | + } |
1165 | + |
1166 | + Q_EMIT beginResetModel(); |
1167 | + |
1168 | + m_reloadingModel = true; |
1169 | + m_outdated = false; |
1170 | + m_data.clear(); |
1171 | + m_average = 0; |
1172 | + |
1173 | + if (m_maxCount <= 0) { |
1174 | + qWarning() << "update model requested with invalid maxCount"; |
1175 | + Q_EMIT endResetModel(); |
1176 | + m_reloadingModel = false; |
1177 | + return; |
1178 | + } |
1179 | + |
1180 | + if (!m_startInterval.isValid()) { |
1181 | + qWarning() << "Update model requested with invalid startInterval"; |
1182 | + Q_EMIT endResetModel(); |
1183 | + m_reloadingModel = false; |
1184 | + return; |
1185 | + } |
1186 | + |
1187 | + QAbstractItemModel *source = sourceModel(); |
1188 | + if (!source) { |
1189 | + qWarning() << "Update model requested with null source model"; |
1190 | + m_outdated = false; |
1191 | + Q_EMIT endResetModel(); |
1192 | + m_reloadingModel = false; |
1193 | + return; |
1194 | + } |
1195 | + |
1196 | + QHash<int, QByteArray> roles = source->roleNames(); |
1197 | + int participantsRole = roles.key("participants", -1); |
1198 | + int timestampRole = roles.key("timestamp", -1); |
1199 | + int row = 0; |
1200 | + |
1201 | + Q_ASSERT(participantsRole != -1); |
1202 | + Q_ASSERT(timestampRole != -1); |
1203 | + |
1204 | + QMap<QString, QString> phoneToContactCache; |
1205 | + QMap<QString, MostCalledContactsModelData > contactsData; |
1206 | + |
1207 | + // get all call in the interval |
1208 | + int totalCalls = 0; |
1209 | + while(true) { |
1210 | + QVariant date = getSourceData(row, timestampRole); |
1211 | + |
1212 | + // end of source model |
1213 | + if (date.isNull()) { |
1214 | + break; |
1215 | + } |
1216 | + |
1217 | + // exit if date is out of interval |
1218 | + if (date.toDateTime() < m_startInterval) { |
1219 | + break; |
1220 | + } |
1221 | + |
1222 | + QVariant participants = getSourceData(row, participantsRole); |
1223 | + if (participants.isValid()) { |
1224 | + Q_FOREACH(const QString phone, participants.toStringList()) { |
1225 | + QString contactId; |
1226 | + if (phoneToContactCache.contains(phone)) { |
1227 | + contactId = phoneToContactCache.value(phone); |
1228 | + } else { |
1229 | + contactId = fetchContactId(phone); |
1230 | + } |
1231 | + |
1232 | + // skip uknown contacts |
1233 | + if (contactId.isEmpty()) { |
1234 | + continue; |
1235 | + } |
1236 | + |
1237 | + if (contactsData.contains(contactId)) { |
1238 | + MostCalledContactsModelData &data = contactsData[contactId]; |
1239 | + data.callCount++; |
1240 | + } else { |
1241 | + MostCalledContactsModelData data; |
1242 | + data.contactId = contactId; |
1243 | + data.phoneNumber = phone; |
1244 | + data.callCount = 1; |
1245 | + contactsData.insert(contactId, data); |
1246 | + } |
1247 | + totalCalls++; |
1248 | + } |
1249 | + } |
1250 | + row++; |
1251 | + } |
1252 | + |
1253 | + if (!contactsData.isEmpty()) { |
1254 | + // sort by callCount |
1255 | + QList<MostCalledContactsModelData> data = contactsData.values(); |
1256 | + qSort(data.begin(), data.end(), mostCalledContactsModelDataLessThan); |
1257 | + |
1258 | + // average |
1259 | + m_average = totalCalls / contactsData.size(); |
1260 | + |
1261 | + Q_FOREACH(const MostCalledContactsModelData &d, data) { |
1262 | + if (d.callCount >= m_average) { |
1263 | + m_data << d; |
1264 | + } |
1265 | + if ((uint) m_data.size() > m_maxCount) { |
1266 | + break; |
1267 | + } |
1268 | + } |
1269 | + } |
1270 | + |
1271 | + Q_EMIT endResetModel(); |
1272 | + m_reloadingModel = false; |
1273 | +} |
1274 | + |
1275 | +void MostCalledContactsModel::markAsOutdated() |
1276 | +{ |
1277 | + // skip if model is being reloaded |
1278 | + if (m_reloadingModel) { |
1279 | + return; |
1280 | + } |
1281 | + |
1282 | + if (!m_outdated) { |
1283 | + m_outdated = true; |
1284 | + Q_EMIT outdatedChange(m_outdated); |
1285 | + } |
1286 | +} |
1287 | |
1288 | === added file 'src/imports/Ubuntu/Contacts/mostcalledproxymodel.h' |
1289 | --- src/imports/Ubuntu/Contacts/mostcalledproxymodel.h 1970-01-01 00:00:00 +0000 |
1290 | +++ src/imports/Ubuntu/Contacts/mostcalledproxymodel.h 2014-07-04 14:53:15 +0000 |
1291 | @@ -0,0 +1,95 @@ |
1292 | +/* |
1293 | + * Copyright (C) 2012-2013 Canonical, Ltd. |
1294 | + * |
1295 | + * This program is free software; you can redistribute it and/or modify |
1296 | + * it under the terms of the GNU General Public License as published by |
1297 | + * the Free Software Foundation; version 3. |
1298 | + * |
1299 | + * This program is distributed in the hope that it will be useful, |
1300 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1301 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1302 | + * GNU General Public License for more details. |
1303 | + * |
1304 | + * You should have received a copy of the GNU General Public License |
1305 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1306 | + */ |
1307 | + |
1308 | +#ifndef __MOSTCALLEDCONTACTSMODEL_H__ |
1309 | +#define __MOSTCALLEDCONTACTSMODEL_H__ |
1310 | + |
1311 | +#include <QtCore/QAbstractListModel> |
1312 | +#include <QtCore/QScopedPointer> |
1313 | +#include <QtCore/QDateTime> |
1314 | + |
1315 | +#include <QtContacts/QContactManager> |
1316 | + |
1317 | +struct MostCalledContactsModelData |
1318 | +{ |
1319 | + QString contactId; |
1320 | + QString phoneNumber; |
1321 | + int callCount; |
1322 | +}; |
1323 | + |
1324 | +class MostCalledContactsModel : public QAbstractListModel |
1325 | +{ |
1326 | + Q_OBJECT |
1327 | + Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged) |
1328 | + Q_PROPERTY(uint maxCount READ maxCount WRITE setMaxCount NOTIFY maxCountChanged) |
1329 | + Q_PROPERTY(int callAverage READ callAverage NOTIFY callAverageChanged) |
1330 | + Q_PROPERTY(QDateTime startInterval READ startInterval WRITE setStartInterval NOTIFY startIntervalChanged) |
1331 | + Q_PROPERTY(bool outdated READ outdated NOTIFY outdatedChange) |
1332 | + |
1333 | +public: |
1334 | + enum Role { |
1335 | + ContactIdRole = 0, |
1336 | + PhoneNumberRole, |
1337 | + CallCountRole |
1338 | + }; |
1339 | + |
1340 | + MostCalledContactsModel(QObject *parent=0); |
1341 | + ~MostCalledContactsModel(); |
1342 | + |
1343 | + QVariant data(const QModelIndex &index, int role) const; |
1344 | + QHash<int, QByteArray> roleNames() const; |
1345 | + int rowCount(const QModelIndex&) const; |
1346 | + |
1347 | + QAbstractItemModel *sourceModel() const; |
1348 | + void setSourceModel(QAbstractItemModel *model); |
1349 | + |
1350 | + uint maxCount() const; |
1351 | + void setMaxCount(uint value); |
1352 | + |
1353 | + int callAverage() const; |
1354 | + bool outdated() const; |
1355 | + |
1356 | + QDateTime startInterval() const; |
1357 | + void setStartInterval(const QDateTime &value); |
1358 | + |
1359 | + Q_INVOKABLE void update(); |
1360 | + |
1361 | +Q_SIGNALS: |
1362 | + void maxCountChanged(uint value); |
1363 | + void callAverageChanged(int value); |
1364 | + void startIntervalChanged(const QDateTime &value); |
1365 | + void sourceModelChanged(QAbstractItemModel *value); |
1366 | + void outdatedChange(bool value); |
1367 | + |
1368 | +private Q_SLOTS: |
1369 | + void markAsOutdated(); |
1370 | + |
1371 | +private: |
1372 | + QAbstractItemModel *m_sourceModel; |
1373 | + QScopedPointer<QtContacts::QContactManager> m_manager; |
1374 | + QList<MostCalledContactsModelData> m_data; |
1375 | + uint m_maxCount; |
1376 | + int m_average; |
1377 | + QDateTime m_startInterval; |
1378 | + bool m_outdated; |
1379 | + bool m_reloadingModel; |
1380 | + |
1381 | + QString fetchContactId(const QString &phoneNumber); |
1382 | + QVariant getSourceData(int row, int role); |
1383 | +}; |
1384 | + |
1385 | + |
1386 | +#endif |
1387 | |
1388 | === added file 'src/imports/Ubuntu/Contacts/plugin.cpp' |
1389 | --- src/imports/Ubuntu/Contacts/plugin.cpp 1970-01-01 00:00:00 +0000 |
1390 | +++ src/imports/Ubuntu/Contacts/plugin.cpp 2014-07-04 14:53:15 +0000 |
1391 | @@ -0,0 +1,33 @@ |
1392 | +/* |
1393 | + * Copyright (C) 2012-2013 Canonical, Ltd. |
1394 | + * |
1395 | + * This program is free software; you can redistribute it and/or modify |
1396 | + * it under the terms of the GNU General Public License as published by |
1397 | + * the Free Software Foundation; version 3. |
1398 | + * |
1399 | + * This program is distributed in the hope that it will be useful, |
1400 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1401 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1402 | + * GNU General Public License for more details. |
1403 | + * |
1404 | + * You should have received a copy of the GNU General Public License |
1405 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1406 | + */ |
1407 | + |
1408 | +#include "plugin.h" |
1409 | +#include "mostcalledproxymodel.h" |
1410 | + |
1411 | +#include <QQmlEngine> |
1412 | +#include <qqml.h> |
1413 | + |
1414 | +void UbuntuContacts::initializeEngine(QQmlEngine *engine, const char *uri) |
1415 | +{ |
1416 | + Q_UNUSED(engine); |
1417 | + Q_UNUSED(uri); |
1418 | +} |
1419 | + |
1420 | +void UbuntuContacts::registerTypes(const char *uri) |
1421 | +{ |
1422 | + // @uri Ubuntu.Contacts |
1423 | + qmlRegisterType<MostCalledContactsModel>(uri, 0, 1, "MostCalledContactsModel"); |
1424 | +} |
1425 | |
1426 | === added file 'src/imports/Ubuntu/Contacts/plugin.h' |
1427 | --- src/imports/Ubuntu/Contacts/plugin.h 1970-01-01 00:00:00 +0000 |
1428 | +++ src/imports/Ubuntu/Contacts/plugin.h 2014-07-04 14:53:15 +0000 |
1429 | @@ -0,0 +1,34 @@ |
1430 | +/* |
1431 | + * Copyright (C) 2012-2013 Canonical, Ltd. |
1432 | + * |
1433 | + * This program is free software; you can redistribute it and/or modify |
1434 | + * it under the terms of the GNU General Public License as published by |
1435 | + * the Free Software Foundation; version 3. |
1436 | + * |
1437 | + * This program is distributed in the hope that it will be useful, |
1438 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1439 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1440 | + * GNU General Public License for more details. |
1441 | + * |
1442 | + * You should have received a copy of the GNU General Public License |
1443 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1444 | + */ |
1445 | + |
1446 | + |
1447 | +#ifndef _UBUNTU_CONTACTS_H_ |
1448 | +#define _UBUNTU_CONTACTS_H_ |
1449 | + |
1450 | +#include <QQmlContext> |
1451 | +#include <QQmlExtensionPlugin> |
1452 | + |
1453 | +class UbuntuContacts : public QQmlExtensionPlugin |
1454 | +{ |
1455 | + Q_OBJECT |
1456 | + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") |
1457 | + |
1458 | +public: |
1459 | + void initializeEngine(QQmlEngine *engine, const char *uri); |
1460 | + void registerTypes(const char *uri); |
1461 | +}; |
1462 | + |
1463 | +#endif //_UBUNTU_CONTACTS_H_ |
1464 | |
1465 | === modified file 'src/imports/Ubuntu/Contacts/qmldir' |
1466 | --- src/imports/Ubuntu/Contacts/qmldir 2014-06-18 18:19:09 +0000 |
1467 | +++ src/imports/Ubuntu/Contacts/qmldir 2014-07-04 14:53:15 +0000 |
1468 | @@ -1,10 +1,13 @@ |
1469 | module Ubuntu.Contacts |
1470 | |
1471 | +plugin ubuntu-contacts-qml |
1472 | + |
1473 | ContactAvatar 0.1 ContactAvatar.qml |
1474 | ContactDetailOnlineAccountTypeModel 0.1 ContactDetailOnlineAccountTypeModel.qml |
1475 | ContactDetailPhoneNumberTypeModel 0.1 ContactDetailPhoneNumberTypeModel.qml |
1476 | ContactFetch 0.1 ContactFetch.qml |
1477 | ContactListView 0.1 ContactListView.qml |
1478 | +ListItemWithActions 0.1 ListItemWithActions.qml |
1479 | MultipleSelectionListView 0.1 MultipleSelectionListView.qml |
1480 | |
1481 | internal ContactSimpleListView ContactSimpleListView.qml |
1482 | |
1483 | === modified file 'tests/CMakeLists.txt' |
1484 | --- tests/CMakeLists.txt 2013-12-12 15:55:57 +0000 |
1485 | +++ tests/CMakeLists.txt 2014-07-04 14:53:15 +0000 |
1486 | @@ -1,3 +1,5 @@ |
1487 | +add_subdirectory(qml) |
1488 | + |
1489 | if(ENABLE_AUTOPILOT) |
1490 | add_subdirectory(autopilot) |
1491 | add_subdirectory(data) |
1492 | |
1493 | === modified file 'tests/autopilot/address_book_app/tests/test_add_contact.py' |
1494 | --- tests/autopilot/address_book_app/tests/test_add_contact.py 2014-06-17 00:20:22 +0000 |
1495 | +++ tests/autopilot/address_book_app/tests/test_add_contact.py 2014-07-04 14:53:15 +0000 |
1496 | @@ -49,96 +49,6 @@ |
1497 | list_view = self.app.main_window.get_contact_list_view() |
1498 | self.assertThat(list_view.count, Eventually(Equals(0))) |
1499 | |
1500 | - def test_add_contact_without_name(self): |
1501 | - # execute add new contact |
1502 | - contact_editor = self.app.main_window.go_to_add_contact() |
1503 | - |
1504 | - # Try to save a empty contact |
1505 | - acceptButton = self.app.main_window.get_button("save") |
1506 | - |
1507 | - # Save button must be disabled |
1508 | - self.assertThat(acceptButton.enabled, Eventually(Equals(False))) |
1509 | - |
1510 | - contact_editor.fill_form(data.Contact(first_name='Fulano')) |
1511 | - |
1512 | - # Save button must be enabled |
1513 | - self.assertThat(acceptButton.enabled, Eventually(Equals(True))) |
1514 | - |
1515 | - contact_editor.fill_form(data.Contact(first_name='')) |
1516 | - |
1517 | - # Save button must be disabled |
1518 | - self.assertThat(acceptButton.enabled, Eventually(Equals(False))) |
1519 | - |
1520 | - contact_editor.fill_form(data.Contact(last_name='de Tal')) |
1521 | - |
1522 | - # Save button must be enabled |
1523 | - self.assertThat(acceptButton.enabled, Eventually(Equals(True))) |
1524 | - |
1525 | - # clear lastName field |
1526 | - contact_editor.fill_form(data.Contact(last_name='')) |
1527 | - |
1528 | - # Save button must be disabled |
1529 | - self.assertThat(acceptButton.enabled, Eventually(Equals(False))) |
1530 | - |
1531 | - # Clicking on disabled button should do nothing |
1532 | - self.pointing_device.click_object(acceptButton) |
1533 | - |
1534 | - # Check if the contact editor still visbile |
1535 | - list_page = self.app.main_window.get_contact_list_page() |
1536 | - |
1537 | - self.assertThat(list_page.visible, Eventually(Equals(False))) |
1538 | - self.assertThat(contact_editor.visible, Eventually(Equals(True))) |
1539 | - |
1540 | - # Check if the contact list still empty |
1541 | - list_view = self.app.main_window.get_contact_list_view() |
1542 | - self.assertThat(list_view.count, Eventually(Equals(0))) |
1543 | - |
1544 | - def test_add_contact_with_full_name(self): |
1545 | - test_contact = data.Contact(first_name='Fulano', last_name='de Tal') |
1546 | - |
1547 | - # execute add new contact |
1548 | - contact_editor = self.app.main_window.go_to_add_contact() |
1549 | - contact_editor.fill_form(test_contact) |
1550 | - |
1551 | - # Save contact |
1552 | - self.app.main_window.save() |
1553 | - |
1554 | - # Check if the contact list is visible again |
1555 | - list_page = self.app.main_window.get_contact_list_page() |
1556 | - self.assertThat(list_page.visible, Eventually(Equals(True))) |
1557 | - |
1558 | - # Check if contact was added |
1559 | - list_view = self.app.main_window.get_contact_list_view() |
1560 | - self.assertThat(list_view.count, Eventually(Equals(1))) |
1561 | - |
1562 | - def test_add_contact_with_first_name(self): |
1563 | - test_contact = data.Contact(first_name='Fulano') |
1564 | - |
1565 | - # execute add new contact |
1566 | - contact_editor = self.app.main_window.go_to_add_contact() |
1567 | - contact_editor.fill_form(test_contact) |
1568 | - |
1569 | - # Save contact |
1570 | - self.app.main_window.save() |
1571 | - |
1572 | - # Check if contact was added |
1573 | - list_view = self.app.main_window.get_contact_list_view() |
1574 | - self.assertThat(list_view.count, Eventually(Equals(1))) |
1575 | - |
1576 | - def test_add_contact_with_last_name(self): |
1577 | - test_contact = data.Contact(last_name='de Tal') |
1578 | - |
1579 | - # execute add new contact |
1580 | - contact_editor = self.app.main_window.go_to_add_contact() |
1581 | - contact_editor.fill_form(test_contact) |
1582 | - |
1583 | - # Save contact |
1584 | - self.app.main_window.save() |
1585 | - |
1586 | - # Check if contact was added |
1587 | - list_view = self.app.main_window.get_contact_list_view() |
1588 | - self.assertThat(list_view.count, Eventually(Equals(1))) |
1589 | - |
1590 | def test_add_contact_with_name_and_phone(self): |
1591 | test_contact = data.Contact( |
1592 | first_name='Fulano', last_name='de Tal', |
1593 | |
1594 | === added directory 'tests/qml' |
1595 | === added file 'tests/qml/CMakeLists.txt' |
1596 | --- tests/qml/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
1597 | +++ tests/qml/CMakeLists.txt 2014-07-04 14:53:15 +0000 |
1598 | @@ -0,0 +1,22 @@ |
1599 | +find_program(QMLTESTRUNNER_BIN |
1600 | + NAMES qmltestrunner |
1601 | + PATHS /usr/lib/*/qt5/bin |
1602 | + NO_DEFAULT_PATH |
1603 | +) |
1604 | + |
1605 | +find_program(XVFB_RUN_BIN |
1606 | + NAMES xvfb-run |
1607 | +) |
1608 | + |
1609 | +if(QMLTESTRUNNER_BIN AND XVFB_RUN_BIN) |
1610 | + add_test(NAME qmltestrunner |
1611 | + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} |
1612 | + COMMAND ${XVFB_RUN_BIN} -a -s "-screen 0 1024x768x24" ${QMLTESTRUNNER_BIN} -import ${imports_BINARY_DIR} -input ${CMAKE_SOURCE_DIR}/tests/qml |
1613 | + ) |
1614 | +else() |
1615 | + if (NOT QMLTESTRUNNER_BIN) |
1616 | + message(WARNING "Qml tests disabled: qmltestrunner not found") |
1617 | + else() |
1618 | + message(WARNING "Qml tests disabled: xvfb-run not found") |
1619 | + endif() |
1620 | +endif() |
1621 | |
1622 | === added file 'tests/qml/tst_ContactEditor.qml' |
1623 | --- tests/qml/tst_ContactEditor.qml 1970-01-01 00:00:00 +0000 |
1624 | +++ tests/qml/tst_ContactEditor.qml 2014-07-04 14:53:15 +0000 |
1625 | @@ -0,0 +1,143 @@ |
1626 | +/* |
1627 | + * Copyright (C) 2014 Canonical, Ltd. |
1628 | + * |
1629 | + * This program is free software; you can redistribute it and/or modify |
1630 | + * it under the terms of the GNU General Public License as published by |
1631 | + * the Free Software Foundation; version 3. |
1632 | + * |
1633 | + * This program is distributed in the hope that it will be useful, |
1634 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1635 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1636 | + * GNU General Public License for more details. |
1637 | + * |
1638 | + * You should have received a copy of the GNU General Public License |
1639 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1640 | + */ |
1641 | + |
1642 | +import QtQuick 2.2 |
1643 | +import QtTest 1.0 |
1644 | +import Ubuntu.Components 0.1 |
1645 | +import Ubuntu.Test 0.1 |
1646 | +import Ubuntu.Contacts 0.1 |
1647 | + |
1648 | +import '../../src/imports/ContactEdit' |
1649 | + |
1650 | +Item { |
1651 | + |
1652 | + id: root |
1653 | + |
1654 | + width: units.gu(40) |
1655 | + height: units.gu(80) |
1656 | + |
1657 | + MainView { |
1658 | + id: mainView |
1659 | + anchors.fill: parent |
1660 | + useDeprecatedToolbar: false |
1661 | + |
1662 | + ListModel { |
1663 | + // dummy data model. |
1664 | + id: dummyDataModel |
1665 | + |
1666 | + signal contactsFetched |
1667 | + } |
1668 | + |
1669 | + ContactEditor { |
1670 | + id: contactEditor |
1671 | + anchors.fill: parent |
1672 | + model: dummyDataModel |
1673 | + contact: createEmptyContact('') |
1674 | + } |
1675 | + } |
1676 | + |
1677 | + function createEmptyContact(phoneNumber) { |
1678 | + var details = [ |
1679 | + {detail: 'PhoneNumber', field: 'number', value: phoneNumber}, |
1680 | + {detail: 'EmailAddress', field: 'emailAddress', value: ''}, |
1681 | + {detail: 'OnlineAccount', field: 'accountUri', value: ''}, |
1682 | + {detail: 'Address', field: 'street', value: ''}, |
1683 | + {detail: 'Name', field: 'firstName', value: ''}, |
1684 | + {detail: 'Organization', field: 'name', value: ''} |
1685 | + ]; |
1686 | + |
1687 | + var newContact = Qt.createQmlObject( |
1688 | + 'import QtContacts 5.0; Contact{ }', mainView); |
1689 | + var detailSourceTemplate = 'import QtContacts 5.0; %1{ %2: "%3" }'; |
1690 | + for (var i=0; i < details.length; i++) { |
1691 | + var detailMetaData = details[i]; |
1692 | + var template = detailSourceTemplate.arg(detailMetaData.detail).arg( |
1693 | + detailMetaData.field).arg(detailMetaData.value); |
1694 | + var newDetail = Qt.createQmlObject(template, mainView); |
1695 | + newContact.addDetail(newDetail); |
1696 | + } |
1697 | + return newContact; |
1698 | + } |
1699 | + |
1700 | + UbuntuTestCase { |
1701 | + id: contactEditorTestCase |
1702 | + name: 'contactEditorTestCase' |
1703 | + |
1704 | + when: windowShown |
1705 | + |
1706 | + function init() { |
1707 | + waitForRendering(contactEditor); |
1708 | + var saveButton = findChild(root, 'save_header_button'); |
1709 | + compare(saveButton.enabled, false); |
1710 | + } |
1711 | + |
1712 | + function cleanup() { |
1713 | + var textFields = getRequiredTextFields().concat( |
1714 | + getOptionalTextFields()); |
1715 | + textFields.forEach(clearTextField); |
1716 | + } |
1717 | + |
1718 | + function getRequiredTextFields() { |
1719 | + return ['firstName', 'lastName', 'phoneNumber_0']; |
1720 | + } |
1721 | + |
1722 | + function getOptionalTextFields() { |
1723 | + return [ |
1724 | + 'emailAddress_0', 'imUri_0', 'streetAddress_0', |
1725 | + 'localityAddress_0', 'regionAddress_0', 'postcodeAddress_0', |
1726 | + 'countryAddress_0', 'orgName_0', 'orgRole_0', 'orgTitle_0' |
1727 | + ]; |
1728 | + } |
1729 | + |
1730 | + function clearTextField(value, index, array) { |
1731 | + var textField = findChild(root, value); |
1732 | + textField.text = ''; |
1733 | + } |
1734 | + |
1735 | + function test_fillRequiredFieldsMustEnableSaveButton_data() { |
1736 | + var textFields = getRequiredTextFields(); |
1737 | + return objectNamesArrayToDataScenarios(textFields); |
1738 | + } |
1739 | + |
1740 | + function objectNamesArrayToDataScenarios(objectNamesArray) { |
1741 | + var data = []; |
1742 | + for (var index = 0; index < objectNamesArray.length; index++) { |
1743 | + var objectName = objectNamesArray[index]; |
1744 | + data.push({tag: objectName, objectName: objectName}); |
1745 | + } |
1746 | + return data; |
1747 | + } |
1748 | + |
1749 | + function test_fillRequiredFieldsMustEnableSaveButton(data) { |
1750 | + var textField = findChild(root, data.objectName); |
1751 | + textField.text = 'test' |
1752 | + var saveButton = findChild(root, 'save_header_button'); |
1753 | + tryCompare(saveButton, 'enabled', true); |
1754 | + } |
1755 | + |
1756 | + function test_fillOptionalFieldsMustNotEnableSaveButton_data() { |
1757 | + var textFields = getOptionalTextFields(); |
1758 | + return objectNamesArrayToDataScenarios(textFields) |
1759 | + } |
1760 | + |
1761 | + function test_fillOptionalFieldsMustNotEnableSaveButton(data) { |
1762 | + var textField = findChild(root, data.objectName); |
1763 | + textField.text = 'test' |
1764 | + var saveButton = findChild(root, 'save_header_button'); |
1765 | + tryCompare(saveButton, 'enabled', false); |
1766 | + } |
1767 | + } |
1768 | +} |
Are there any related MPs required for this MP to build/function as expected? NO
Is your branch in sync with latest trunk? 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 on device or emulator? YES
If you changed the UI, was the change specified/approved by design? YES
If you changed the packaging (debian), did you add a core-dev as a reviewer to this MP? NO PACKAGE CHANGE