Merge lp:~nik90/ubuntu-clock-app/custom-swipe-delete into lp:ubuntu-clock-app
- custom-swipe-delete
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 43 | ||||
Proposed branch: | lp:~nik90/ubuntu-clock-app/custom-swipe-delete | ||||
Merge into: | lp:ubuntu-clock-app | ||||
Prerequisite: | lp:~nik90/ubuntu-clock-app/add-fastscroll | ||||
Diff against target: |
1205 lines (+880/-224) 9 files modified
app/alarm/AlarmDelegate.qml (+104/-0) app/alarm/AlarmList.qml (+86/-78) app/clock/ClockPage.qml (+3/-1) app/upstreamcomponents/ListItemWithActions.qml (+371/-0) app/upstreamcomponents/ListItemWithActionsCheckBox.qml (+25/-0) app/upstreamcomponents/README.components (+9/-0) app/worldclock/UserWorldCityDelegate.qml (+182/-0) app/worldclock/UserWorldCityList.qml (+83/-144) tests/autopilot/ubuntu_clock_app/emulators.py (+17/-1) |
||||
To merge this branch: | bzr merge lp:~nik90/ubuntu-clock-app/custom-swipe-delete | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Nicholas Skaggs (community) | Approve | ||
Ubuntu Phone Apps Jenkins Bot | continuous-integration | Needs Fixing | |
Review via email: mp+229088@code.launchpad.net |
Commit message
Implemented custom swipe delete for user world clock and saved alarm list matching the address book app as specified by design.
Description of the change
This MP implements a custom swipe delete for the lists (world clock, alarm list) etc. The designers requested that it match the implementation of the address book. Hence I imported ListItemWithAct
All upstream components are placed inside the upstreamcomponents folder for easier identification and replacement when they are available in the SDK.
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:41
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:43
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:44
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 48. By Nekhelesh Ramananthan
-
removed unnecessary import
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:48
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Nicholas Skaggs (nskaggs) : | # |
- 49. By Nekhelesh Ramananthan
-
Merged trunk
Preview Diff
1 | === added file 'app/alarm/AlarmDelegate.qml' |
2 | --- app/alarm/AlarmDelegate.qml 1970-01-01 00:00:00 +0000 |
3 | +++ app/alarm/AlarmDelegate.qml 2014-08-08 09:11:15 +0000 |
4 | @@ -0,0 +1,104 @@ |
5 | +/* |
6 | + * Copyright (C) 2014 Canonical Ltd |
7 | + * |
8 | + * This file is part of Ubuntu Clock App |
9 | + * |
10 | + * Ubuntu Clock App is free software: you can redistribute it and/or modify |
11 | + * it under the terms of the GNU General Public License version 3 as |
12 | + * published by the Free Software Foundation. |
13 | + * |
14 | + * Ubuntu Clock App is distributed in the hope that it will be useful, |
15 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | + * GNU General Public License for more details. |
18 | + * |
19 | + * You should have received a copy of the GNU General Public License |
20 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
21 | + */ |
22 | + |
23 | +import QtQuick 2.0 |
24 | +import Ubuntu.Components 1.1 |
25 | +import "../upstreamcomponents" |
26 | + |
27 | +ListItemWithActions { |
28 | + id: root |
29 | + |
30 | + height: units.gu(6) |
31 | + width: parent ? parent.width : 0 |
32 | + color: "Transparent" |
33 | + |
34 | + Item { |
35 | + id: delegate |
36 | + |
37 | + anchors.fill: parent |
38 | + |
39 | + Label { |
40 | + id: alarmTime |
41 | + objectName: "listAlarmTime" + index |
42 | + |
43 | + anchors { |
44 | + top: alarmDetailsColumn.top |
45 | + left: parent.left |
46 | + } |
47 | + |
48 | + fontSize: "medium" |
49 | + text: Qt.formatTime(date) |
50 | + } |
51 | + |
52 | + Column { |
53 | + id: alarmDetailsColumn |
54 | + |
55 | + anchors { |
56 | + left: alarmTime.right |
57 | + right: alarmStatus.left |
58 | + verticalCenter: parent.verticalCenter |
59 | + margins: units.gu(1) |
60 | + } |
61 | + |
62 | + Label { |
63 | + id: alarmLabel |
64 | + objectName: "listAlarmLabel" + index |
65 | + |
66 | + text: message |
67 | + fontSize: "medium" |
68 | + elide: Text.ElideRight |
69 | + color: UbuntuColors.midAubergine |
70 | + } |
71 | + |
72 | + Label { |
73 | + id: alarmSubtitle |
74 | + objectName: "listAlarmSubtitle" + index |
75 | + |
76 | + fontSize: "xx-small" |
77 | + width: parent.width |
78 | + wrapMode: Text.WrapAtWordBoundaryOrAnywhere |
79 | + text: alarmUtils.format_day_string(daysOfWeek) |
80 | + } |
81 | + } |
82 | + |
83 | + Switch { |
84 | + id: alarmStatus |
85 | + objectName: "listAlarmStatus" + index |
86 | + |
87 | + anchors { |
88 | + right: parent.right |
89 | + verticalCenter: parent.verticalCenter |
90 | + } |
91 | + |
92 | + checked: enabled |
93 | + |
94 | + /* |
95 | + #TODO: Add the ability to enable/disable alarms using the |
96 | + switch. At the moment it only shows the alarm status. |
97 | + This was postponed since a similar implementation in the |
98 | + old clock app caused it to loop. So if user clicks on the |
99 | + switch, it disables and then re-enables the alarm again. |
100 | + */ |
101 | + } |
102 | + } |
103 | + |
104 | + onItemClicked: { |
105 | + mainStack.push(Qt.resolvedUrl("EditAlarmPage.qml"), |
106 | + {"isNewAlarm": false, "alarmIndex": index}) |
107 | + } |
108 | +} |
109 | |
110 | === modified file 'app/alarm/AlarmList.qml' |
111 | --- app/alarm/AlarmList.qml 2014-08-05 12:49:45 +0000 |
112 | +++ app/alarm/AlarmList.qml 2014-08-08 09:11:15 +0000 |
113 | @@ -63,87 +63,95 @@ |
114 | anchors.fill: parent |
115 | |
116 | Repeater { |
117 | + id: alarmRepeater |
118 | + objectName: "alarmListRepeater" |
119 | + |
120 | + property var _currentSwipedItem: null |
121 | + |
122 | + function _updateSwipeState(item) |
123 | + { |
124 | + if (item.swipping) { |
125 | + return |
126 | + } |
127 | + |
128 | + if (item.swipeState !== "Normal") { |
129 | + if (alarmRepeater._currentSwipedItem !== item) { |
130 | + if (alarmRepeater._currentSwipedItem) { |
131 | + alarmRepeater._currentSwipedItem.resetSwipe() |
132 | + } |
133 | + alarmRepeater._currentSwipedItem = item |
134 | + } |
135 | + } else if (item.swipeState !== "Normal" |
136 | + && alarmRepeater._currentSwipedItem === item) { |
137 | + alarmRepeater._currentSwipedItem = null |
138 | + } |
139 | + } |
140 | + |
141 | model: alarmListFlickable.model |
142 | - objectName: "alarmListRepeater" |
143 | - ListItem.Base { |
144 | + |
145 | + delegate: AlarmDelegate { |
146 | + id: alarmDelegate |
147 | objectName: "alarm" + index |
148 | |
149 | - Label { |
150 | - id: alarmTime |
151 | - objectName: "listAlarmTime" + index |
152 | - |
153 | - anchors { |
154 | - top: alarmDetailsColumn.top |
155 | - left: parent.left |
156 | - leftMargin: units.gu(0) |
157 | - } |
158 | - |
159 | - fontSize: "medium" |
160 | - text: Qt.formatTime(date) |
161 | - } |
162 | - |
163 | - Column { |
164 | - id: alarmDetailsColumn |
165 | - |
166 | - anchors { |
167 | - left: alarmTime.right |
168 | - right: alarmStatus.left |
169 | - verticalCenter: parent.verticalCenter |
170 | - margins: units.gu(1) |
171 | - } |
172 | - |
173 | - Label { |
174 | - id: alarmLabel |
175 | - objectName: "listAlarmLabel" + index |
176 | - |
177 | - text: message |
178 | - fontSize: "medium" |
179 | - elide: Text.ElideRight |
180 | - color: UbuntuColors.midAubergine |
181 | - } |
182 | - |
183 | - Label { |
184 | - id: alarmSubtitle |
185 | - objectName: "listAlarmSubtitle" + index |
186 | - |
187 | - fontSize: "xx-small" |
188 | - width: parent.width |
189 | - wrapMode: Text.WrapAtWordBoundaryOrAnywhere |
190 | - text: alarmUtils.format_day_string(daysOfWeek) |
191 | - } |
192 | - } |
193 | - |
194 | - Switch { |
195 | - id: alarmStatus |
196 | - objectName: "listAlarmStatus" + index |
197 | - |
198 | - anchors { |
199 | - right: parent.right |
200 | - verticalCenter: parent.verticalCenter |
201 | - } |
202 | - |
203 | - checked: enabled |
204 | - |
205 | - /* |
206 | - #TODO: Add the ability to enable/disable alarms using the |
207 | - switch. At the moment it only shows the alarm status. |
208 | - This was postponed since a similar implementation in the |
209 | - old clock app caused it to loop. So if user clicks on the |
210 | - switch, it disables and then re-enables the alarm again. |
211 | - */ |
212 | - } |
213 | - |
214 | - removable: true |
215 | - confirmRemoval: true |
216 | - |
217 | - onItemRemoved: { |
218 | - var alarm = alarmModel.get(index) |
219 | - alarm.cancel() |
220 | - } |
221 | - |
222 | - onClicked: mainStack.push(Qt.resolvedUrl("EditAlarmPage.qml"), |
223 | - {"isNewAlarm": false, |
224 | - "alarmIndex": index}) |
225 | + property var removalAnimation |
226 | + |
227 | + function remove() { |
228 | + removalAnimation.start() |
229 | + } |
230 | + |
231 | + onSwippingChanged: { |
232 | + alarmRepeater._updateSwipeState(alarmDelegate) |
233 | + } |
234 | + |
235 | + onSwipeStateChanged: { |
236 | + alarmRepeater._updateSwipeState(alarmDelegate) |
237 | + } |
238 | + |
239 | + leftSideAction: Action { |
240 | + iconName: "delete" |
241 | + text: i18n.tr("Delete") |
242 | + onTriggered: { |
243 | + alarmDelegate.remove() |
244 | + } |
245 | + } |
246 | + |
247 | + ListView.onRemove: ScriptAction { |
248 | + script: { |
249 | + if (alarmRepeater._currentSwipedItem |
250 | + === alarmDelegate) { |
251 | + alarmRepeater._currentSwipedItem = null |
252 | + } |
253 | + } |
254 | + } |
255 | + |
256 | + removalAnimation: SequentialAnimation { |
257 | + alwaysRunToEnd: true |
258 | + |
259 | + PropertyAction { |
260 | + target: alarmDelegate |
261 | + property: "ListView.delayRemove" |
262 | + value: true |
263 | + } |
264 | + |
265 | + UbuntuNumberAnimation { |
266 | + target: alarmDelegate |
267 | + property: "height" |
268 | + to: 1 |
269 | + } |
270 | + |
271 | + PropertyAction { |
272 | + target: alarmDelegate |
273 | + property: "ListView.delayRemove" |
274 | + value: false |
275 | + } |
276 | + |
277 | + ScriptAction { |
278 | + script: { |
279 | + var alarm = alarmModel.get(index) |
280 | + alarm.cancel() |
281 | + } |
282 | + } |
283 | + } |
284 | } |
285 | } |
286 | } |
287 | |
288 | === modified file 'app/clock/ClockPage.qml' |
289 | --- app/clock/ClockPage.qml 2014-08-06 20:12:32 +0000 |
290 | +++ app/clock/ClockPage.qml 2014-08-08 09:11:15 +0000 |
291 | @@ -49,7 +49,7 @@ |
292 | anchors.fill: parent |
293 | contentWidth: parent.width |
294 | contentHeight: clock.height + date.height + locationRow.height |
295 | - + worldCityColumn.height + units.gu(20) |
296 | + + worldCityColumn.height + units.gu(14) |
297 | |
298 | PullToAdd { |
299 | id: addCityButton |
300 | @@ -150,6 +150,8 @@ |
301 | id: worldCityColumn |
302 | objectName: "worldCityColumn" |
303 | opacity: settingsIcon.opacity |
304 | + anchors.top: locationRow.bottom |
305 | + anchors.topMargin: units.gu(4) |
306 | } |
307 | |
308 | onDragEnded: { |
309 | |
310 | === added file 'app/upstreamcomponents/ListItemWithActions.qml' |
311 | --- app/upstreamcomponents/ListItemWithActions.qml 1970-01-01 00:00:00 +0000 |
312 | +++ app/upstreamcomponents/ListItemWithActions.qml 2014-08-08 09:11:15 +0000 |
313 | @@ -0,0 +1,371 @@ |
314 | +/* |
315 | + * Copyright (C) 2012-2014 Canonical, Ltd. |
316 | + * |
317 | + * This program is free software; you can redistribute it and/or modify |
318 | + * it under the terms of the GNU General Public License as published by |
319 | + * the Free Software Foundation; version 3. |
320 | + * |
321 | + * This program is distributed in the hope that it will be useful, |
322 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
323 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
324 | + * GNU General Public License for more details. |
325 | + * |
326 | + * You should have received a copy of the GNU General Public License |
327 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
328 | + */ |
329 | + |
330 | +import QtQuick 2.2 |
331 | +import Ubuntu.Components 1.1 |
332 | + |
333 | +Item { |
334 | + id: root |
335 | + |
336 | + property Action leftSideAction: null |
337 | + property list<Action> rightSideActions |
338 | + property double defaultHeight: units.gu(8) |
339 | + property bool locked: false |
340 | + property Action activeAction: null |
341 | + property var activeItem: null |
342 | + property bool triggerActionOnMouseRelease: false |
343 | + property color color: Theme.palette.normal.background |
344 | + property color selectedColor: "#E6E6E6" |
345 | + property bool selected: false |
346 | + property bool selectionMode: false |
347 | + property alias internalAnchors: mainContents.anchors |
348 | + default property alias contents: mainContents.children |
349 | + |
350 | + readonly property double actionWidth: units.gu(5) |
351 | + readonly property double leftActionWidth: units.gu(10) |
352 | + readonly property double actionThreshold: actionWidth * 0.4 |
353 | + readonly property double threshold: 0.4 |
354 | + readonly property string swipeState: main.x == 0 ? "Normal" : main.x > 0 ? "LeftToRight" : "RightToLeft" |
355 | + readonly property alias swipping: mainItemMoving.running |
356 | + |
357 | + signal itemClicked(var mouse) |
358 | + signal itemPressAndHold(var mouse) |
359 | + |
360 | + function returnToBoundsRTL() |
361 | + { |
362 | + var actionFullWidth = actionWidth + units.gu(2) |
363 | + var xOffset = Math.abs(main.x) |
364 | + var index = Math.min(Math.floor(xOffset / actionFullWidth), rightSideActions.length) |
365 | + |
366 | + if (index < 1) { |
367 | + main.x = 0 |
368 | + } else if (index === rightSideActions.length) { |
369 | + main.x = -rightActionsView.width |
370 | + } else { |
371 | + main.x = -(actionFullWidth * index) |
372 | + } |
373 | + } |
374 | + |
375 | + function returnToBoundsLTR() |
376 | + { |
377 | + var finalX = leftActionWidth |
378 | + if (main.x > (finalX * root.threshold)) |
379 | + main.x = finalX |
380 | + else |
381 | + main.x = 0 |
382 | + } |
383 | + |
384 | + function returnToBounds() |
385 | + { |
386 | + if (main.x < 0) { |
387 | + returnToBoundsRTL() |
388 | + } else if (main.x > 0) { |
389 | + returnToBoundsLTR() |
390 | + } |
391 | + } |
392 | + |
393 | + function contains(item, point) |
394 | + { |
395 | + return (point.x >= item.x) && (point.x <= (item.x + item.width)) && (point.y >= item.y) && (point.y <= (item.y + item.height)); |
396 | + } |
397 | + |
398 | + function getActionAt(point) |
399 | + { |
400 | + if (contains(leftActionView, point)) { |
401 | + return leftSideAction |
402 | + } else if (contains(rightActionsView, point)) { |
403 | + var newPoint = root.mapToItem(rightActionsView, point.x, point.y) |
404 | + for (var i = 0; i < rightActionsRepeater.count; i++) { |
405 | + var child = rightActionsRepeater.itemAt(i) |
406 | + if (contains(child, newPoint)) { |
407 | + return i |
408 | + } |
409 | + } |
410 | + } |
411 | + return -1 |
412 | + } |
413 | + |
414 | + function updateActiveAction() |
415 | + { |
416 | + if ((main.x <= -root.actionWidth) && |
417 | + (main.x > -rightActionsView.width)) { |
418 | + var actionFullWidth = actionWidth + units.gu(2) |
419 | + var xOffset = Math.abs(main.x) |
420 | + var index = Math.min(Math.floor(xOffset / actionFullWidth), rightSideActions.length) |
421 | + index = index - 1 |
422 | + if (index > -1) { |
423 | + root.activeItem = rightActionsRepeater.itemAt(index) |
424 | + root.activeAction = root.rightSideActions[index] |
425 | + } |
426 | + } else { |
427 | + root.activeAction = null |
428 | + } |
429 | + } |
430 | + |
431 | + function resetSwipe() |
432 | + { |
433 | + main.x = 0 |
434 | + } |
435 | + |
436 | + states: [ |
437 | + State { |
438 | + name: "select" |
439 | + when: selectionMode || selected |
440 | + PropertyChanges { |
441 | + target: selectionIcon |
442 | + source: Qt.resolvedUrl("ListItemWithActionsCheckBox.qml") |
443 | + anchors.leftMargin: units.gu(2) |
444 | + } |
445 | + PropertyChanges { |
446 | + target: root |
447 | + locked: true |
448 | + } |
449 | + PropertyChanges { |
450 | + target: main |
451 | + x: 0 |
452 | + } |
453 | + } |
454 | + ] |
455 | + |
456 | + height: defaultHeight |
457 | + clip: height !== defaultHeight |
458 | + |
459 | + Rectangle { |
460 | + id: leftActionView |
461 | + |
462 | + anchors { |
463 | + top: parent.top |
464 | + bottom: parent.bottom |
465 | + right: main.left |
466 | + } |
467 | + width: root.leftActionWidth + actionThreshold |
468 | + visible: leftSideAction |
469 | + color: "red" |
470 | + |
471 | + Icon { |
472 | + anchors { |
473 | + centerIn: parent |
474 | + horizontalCenterOffset: actionThreshold / 2 |
475 | + } |
476 | + name: leftSideAction ? leftSideAction.iconName : "" |
477 | + color: Theme.palette.selected.field |
478 | + height: units.gu(3) |
479 | + width: units.gu(3) |
480 | + } |
481 | + } |
482 | + |
483 | + Item { |
484 | + id: rightActionsView |
485 | + |
486 | + anchors { |
487 | + top: main.top |
488 | + left: main.right |
489 | + leftMargin: units.gu(1) |
490 | + bottom: main.bottom |
491 | + } |
492 | + visible: rightSideActions.length > 0 |
493 | + width: rightActionsRepeater.count > 0 ? rightActionsRepeater.count * (root.actionWidth + units.gu(2)) + actionThreshold : 0 |
494 | + Row { |
495 | + anchors.fill: parent |
496 | + spacing: units.gu(2) |
497 | + Repeater { |
498 | + id: rightActionsRepeater |
499 | + |
500 | + model: rightSideActions |
501 | + Item { |
502 | + property alias image: img |
503 | + |
504 | + anchors { |
505 | + top: parent.top |
506 | + bottom: parent.bottom |
507 | + } |
508 | + width: root.actionWidth |
509 | + |
510 | + Icon { |
511 | + id: img |
512 | + |
513 | + anchors.centerIn: parent |
514 | + width: units.gu(3) |
515 | + height: units.gu(3) |
516 | + name: iconName |
517 | + color: root.activeAction === modelData || !root.triggerActionOnMouseRelease ? UbuntuColors.lightAubergine : Theme.palette.selected.background |
518 | + } |
519 | + } |
520 | + } |
521 | + } |
522 | + } |
523 | + |
524 | + |
525 | + Rectangle { |
526 | + id: main |
527 | + objectName: "mainItem" |
528 | + |
529 | + anchors { |
530 | + top: parent.top |
531 | + bottom: parent.bottom |
532 | + } |
533 | + |
534 | + width: parent.width |
535 | + color: root.selected ? root.selectedColor : root.color |
536 | + |
537 | + Loader { |
538 | + id: selectionIcon |
539 | + |
540 | + anchors { |
541 | + left: main.left |
542 | + verticalCenter: main.verticalCenter |
543 | + } |
544 | + width: (status === Loader.Ready) ? item.implicitWidth : 0 |
545 | + visible: (status === Loader.Ready) && (item.width === item.implicitWidth) |
546 | + Behavior on width { |
547 | + NumberAnimation { |
548 | + duration: UbuntuAnimation.SnapDuration |
549 | + } |
550 | + } |
551 | + } |
552 | + |
553 | + |
554 | + Item { |
555 | + id: mainContents |
556 | + |
557 | + anchors { |
558 | + left: selectionIcon.right |
559 | + leftMargin: units.gu(2) |
560 | + top: parent.top |
561 | + topMargin: units.gu(1) |
562 | + right: parent.right |
563 | + rightMargin: units.gu(2) |
564 | + bottom: parent.bottom |
565 | + bottomMargin: units.gu(1) |
566 | + } |
567 | + } |
568 | + |
569 | + Behavior on x { |
570 | + UbuntuNumberAnimation { |
571 | + id: mainItemMoving |
572 | + |
573 | + easing.type: Easing.OutElastic |
574 | + duration: UbuntuAnimation.SlowDuration |
575 | + } |
576 | + } |
577 | + Behavior on color { |
578 | + ColorAnimation {} |
579 | + } |
580 | + } |
581 | + |
582 | + SequentialAnimation { |
583 | + id: triggerAction |
584 | + |
585 | + property var currentItem: root.activeItem ? root.activeItem.image : null |
586 | + |
587 | + running: false |
588 | + ParallelAnimation { |
589 | + UbuntuNumberAnimation { |
590 | + target: triggerAction.currentItem |
591 | + property: "opacity" |
592 | + from: 1.0 |
593 | + to: 0.0 |
594 | + duration: UbuntuAnimation.SlowDuration |
595 | + easing {type: Easing.InOutBack; } |
596 | + } |
597 | + UbuntuNumberAnimation { |
598 | + target: triggerAction.currentItem |
599 | + properties: "width, height" |
600 | + from: units.gu(3) |
601 | + to: root.actionWidth |
602 | + duration: UbuntuAnimation.SlowDuration |
603 | + easing {type: Easing.InOutBack; } |
604 | + } |
605 | + } |
606 | + PropertyAction { |
607 | + target: triggerAction.currentItem |
608 | + properties: "width, height" |
609 | + value: units.gu(3) |
610 | + } |
611 | + PropertyAction { |
612 | + target: triggerAction.currentItem |
613 | + properties: "opacity" |
614 | + value: 1.0 |
615 | + } |
616 | + ScriptAction { |
617 | + script: root.activeAction.triggered(root) |
618 | + } |
619 | + PauseAnimation { |
620 | + duration: 500 |
621 | + } |
622 | + UbuntuNumberAnimation { |
623 | + target: main |
624 | + property: "x" |
625 | + to: 0 |
626 | + |
627 | + } |
628 | + } |
629 | + |
630 | + MouseArea { |
631 | + id: mouseArea |
632 | + |
633 | + property bool locked: root.locked || ((root.leftSideAction === null) && (root.rightSideActions.count === 0)) |
634 | + property bool manual: false |
635 | + |
636 | + anchors.fill: parent |
637 | + drag { |
638 | + target: locked ? null : main |
639 | + axis: Drag.XAxis |
640 | + minimumX: rightActionsView.visible ? -(rightActionsView.width + root.actionThreshold) : 0 |
641 | + maximumX: leftActionView.visible ? leftActionView.width : 0 |
642 | + } |
643 | + |
644 | + onReleased: { |
645 | + if (root.triggerActionOnMouseRelease && root.activeAction) { |
646 | + triggerAction.start() |
647 | + } else { |
648 | + root.returnToBounds() |
649 | + root.activeAction = null |
650 | + } |
651 | + } |
652 | + onClicked: { |
653 | + if (main.x === 0) { |
654 | + root.itemClicked(mouse) |
655 | + } else if (main.x > 0) { |
656 | + var action = getActionAt(Qt.point(mouse.x, mouse.y)) |
657 | + if (action && action !== -1) { |
658 | + action.triggered(root) |
659 | + } |
660 | + } else { |
661 | + var actionIndex = getActionAt(Qt.point(mouse.x, mouse.y)) |
662 | + if (actionIndex !== -1) { |
663 | + root.activeItem = rightActionsRepeater.itemAt(actionIndex) |
664 | + root.activeAction = root.rightSideActions[actionIndex] |
665 | + triggerAction.start() |
666 | + return |
667 | + } |
668 | + } |
669 | + root.resetSwipe() |
670 | + } |
671 | + |
672 | + onPositionChanged: { |
673 | + if (mouseArea.pressed) { |
674 | + updateActiveAction() |
675 | + } |
676 | + } |
677 | + onPressAndHold: { |
678 | + if (main.x === 0) { |
679 | + root.itemPressAndHold(mouse) |
680 | + } |
681 | + } |
682 | + z: -1 |
683 | + } |
684 | +} |
685 | |
686 | === added file 'app/upstreamcomponents/ListItemWithActionsCheckBox.qml' |
687 | --- app/upstreamcomponents/ListItemWithActionsCheckBox.qml 1970-01-01 00:00:00 +0000 |
688 | +++ app/upstreamcomponents/ListItemWithActionsCheckBox.qml 2014-08-08 09:11:15 +0000 |
689 | @@ -0,0 +1,25 @@ |
690 | +/* |
691 | + * Copyright (C) 2012-2014 Canonical, Ltd. |
692 | + * |
693 | + * This program is free software; you can redistribute it and/or modify |
694 | + * it under the terms of the GNU General Public License as published by |
695 | + * the Free Software Foundation; version 3. |
696 | + * |
697 | + * This program is distributed in the hope that it will be useful, |
698 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
699 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
700 | + * GNU General Public License for more details. |
701 | + * |
702 | + * You should have received a copy of the GNU General Public License |
703 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
704 | + */ |
705 | + |
706 | +import QtQuick 2.2 |
707 | +import Ubuntu.Components 1.1 |
708 | + |
709 | +CheckBox { |
710 | + checked: root.selected |
711 | + width: implicitWidth |
712 | + // disable item mouse area to avoid conflicts with parent mouse area |
713 | + __mouseArea.enabled: false |
714 | +} |
715 | |
716 | === modified file 'app/upstreamcomponents/README.components' |
717 | --- app/upstreamcomponents/README.components 2014-08-06 23:57:59 +0000 |
718 | +++ app/upstreamcomponents/README.components 2014-08-08 09:11:15 +0000 |
719 | @@ -15,3 +15,12 @@ |
720 | FastScroll.qml |
721 | |
722 | https://bazaar.launchpad.net/~phablet-team/address-book-app/trunk/view/head:/src/imports/Ubuntu/Contacts/FastScroll.qml |
723 | + |
724 | +ListItemWithActions.qml |
725 | + |
726 | +https://bazaar.launchpad.net/~phablet-team/address-book-app/trunk/view/head:/src/imports/Ubuntu/Contacts/ListItemWithActions.qml |
727 | + |
728 | +ListItemWithActionsCheckBox.qml |
729 | + |
730 | +https://bazaar.launchpad.net/~phablet-team/address-book-app/trunk/view/head:/src/imports/Ubuntu/Contacts/ListItemWithActionsCheckBox.qml |
731 | + |
732 | |
733 | === added file 'app/worldclock/UserWorldCityDelegate.qml' |
734 | --- app/worldclock/UserWorldCityDelegate.qml 1970-01-01 00:00:00 +0000 |
735 | +++ app/worldclock/UserWorldCityDelegate.qml 2014-08-08 09:11:15 +0000 |
736 | @@ -0,0 +1,182 @@ |
737 | +/* |
738 | + * Copyright (C) 2014 Canonical Ltd |
739 | + * |
740 | + * This file is part of Ubuntu Clock App |
741 | + * |
742 | + * Ubuntu Clock App is free software: you can redistribute it and/or modify |
743 | + * it under the terms of the GNU General Public License version 3 as |
744 | + * published by the Free Software Foundation. |
745 | + * |
746 | + * Ubuntu Clock App is distributed in the hope that it will be useful, |
747 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
748 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
749 | + * GNU General Public License for more details. |
750 | + * |
751 | + * You should have received a copy of the GNU General Public License |
752 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
753 | + */ |
754 | + |
755 | +import QtQuick 2.0 |
756 | +import Ubuntu.Components 1.1 |
757 | +import "../components" |
758 | +import "../upstreamcomponents" |
759 | + |
760 | +ListItemWithActions { |
761 | + id: root |
762 | + |
763 | + function getTimeDiff(time) { |
764 | + var hours, minutes; |
765 | + time = Math.floor(time / 60) |
766 | + minutes = time % 60 |
767 | + hours = Math.floor(time / 60) |
768 | + return [hours, minutes] |
769 | + } |
770 | + |
771 | + height: units.gu(9) |
772 | + width: parent ? parent.width : 0 |
773 | + color: "Transparent" |
774 | + |
775 | + Item { |
776 | + id: delegate |
777 | + |
778 | + anchors.fill: parent |
779 | + |
780 | + Column { |
781 | + id: cityColumn |
782 | + |
783 | + anchors { |
784 | + left: parent.left |
785 | + verticalCenter: parent.verticalCenter |
786 | + } |
787 | + |
788 | + Label { |
789 | + id: cityNameText |
790 | + fontSize: "medium" |
791 | + text: model.city |
792 | + color: UbuntuColors.midAubergine |
793 | + } |
794 | + |
795 | + Label { |
796 | + id: countryNameText |
797 | + text: model.country |
798 | + fontSize: "xx-small" |
799 | + } |
800 | + } |
801 | + |
802 | + Clock { |
803 | + id: localTimeVisual |
804 | + objectName: "localTimeVisual" + index |
805 | + |
806 | + /* |
807 | + This function would not be required once the upstream QT bug at |
808 | + https://bugreports.qt-project.org/browse/QTBUG-40275 is fixed. |
809 | + Due to this bug we are returning a time string instead of a |
810 | + time object which forces us to parse the string and convert it |
811 | + into a time object here. |
812 | + */ |
813 | + function getTime(timeString) { |
814 | + var properTime = new Date() |
815 | + properTime.setHours(timeString.split(":")[0]) |
816 | + properTime.setMinutes(timeString.split(":")[1]) |
817 | + properTime.setSeconds(0) |
818 | + return properTime |
819 | + } |
820 | + |
821 | + fontSize: units.dp(14) |
822 | + periodFontSize: units.dp(7) |
823 | + innerCircleWidth: units.gu(5) |
824 | + width: units.gu(7) |
825 | + |
826 | + analogTime: getTime(model.localTime) |
827 | + |
828 | + anchors.centerIn: parent |
829 | + |
830 | + Connections { |
831 | + target: clock |
832 | + onTriggerFlip: { |
833 | + localTimeVisual.flipClock() |
834 | + } |
835 | + } |
836 | + |
837 | + Component.onCompleted: { |
838 | + isDigital = clockModeDocument.contents.digitalMode ? true : false |
839 | + if (clockModeDocument.contents.digitalMode) { |
840 | + digitalModeLoader.setSource |
841 | + ("../components/DigitalMode.qml", |
842 | + { |
843 | + "width": innerCircleWidth, |
844 | + "timeFontSize": fontSize, |
845 | + "timePeriodFontSize": periodFontSize |
846 | + }) |
847 | + } |
848 | + else { |
849 | + analogModeLoader.setSource( |
850 | + "../components/AnalogMode.qml", |
851 | + { |
852 | + "width": innerCircleWidth, |
853 | + "showSeconds": isMainClock |
854 | + }) |
855 | + } |
856 | + } |
857 | + } |
858 | + |
859 | + Label { |
860 | + id: relativeTimeLabel |
861 | + objectName: "relativeTimeLabel" + index |
862 | + |
863 | + anchors.right: parent.right |
864 | + anchors.verticalCenter: parent.verticalCenter |
865 | + |
866 | + fontSize: "xx-small" |
867 | + horizontalAlignment: Text.AlignRight |
868 | + text: { |
869 | + var day; |
870 | + |
871 | + if(model.daysTo === 0) { |
872 | + day = i18n.tr("Today") |
873 | + } |
874 | + |
875 | + else if(model.daysTo === 1) { |
876 | + day = i18n.tr("Tomorrow") |
877 | + } |
878 | + |
879 | + else if(model.daysTo === -1) { |
880 | + day = i18n.tr("Yesterday") |
881 | + } |
882 | + |
883 | + var isBehind = model.timeTo > 0 ? i18n.tr("behind") |
884 | + : i18n.tr("ahead") |
885 | + |
886 | + var timediff = getTimeDiff(Math.abs(model.timeTo)) |
887 | + var minute = timediff[1] |
888 | + var hour = timediff[0] |
889 | + |
890 | + if(hour > 0 && minute > 0) { |
891 | + return ("%1\n%2hr%3min %4") |
892 | + .arg(day) |
893 | + .arg(hour) |
894 | + .arg(minute) |
895 | + .arg(isBehind) |
896 | + } |
897 | + |
898 | + else if(hour > 0 && minute === 0) { |
899 | + return ("%1\n%2hr %3") |
900 | + .arg(day) |
901 | + .arg(hour) |
902 | + .arg(isBehind) |
903 | + } |
904 | + |
905 | + else if(hour === 0 && minute > 0) { |
906 | + return ("%1\n%2min %3") |
907 | + .arg(day) |
908 | + .arg(minute) |
909 | + .arg(isBehind) |
910 | + } |
911 | + |
912 | + else { |
913 | + return i18n.tr("No Time Difference") |
914 | + } |
915 | + } |
916 | + } |
917 | + } |
918 | +} |
919 | |
920 | === modified file 'app/worldclock/UserWorldCityList.qml' |
921 | --- app/worldclock/UserWorldCityList.qml 2014-08-05 12:49:45 +0000 |
922 | +++ app/worldclock/UserWorldCityList.qml 2014-08-08 09:11:15 +0000 |
923 | @@ -20,23 +20,14 @@ |
924 | import Timezone 1.0 |
925 | import U1db 1.0 as U1db |
926 | import Ubuntu.Components 1.1 |
927 | -import "../components" |
928 | + |
929 | import "../components/Utils.js" as Utils |
930 | |
931 | Column { |
932 | id: worldCityColumn |
933 | |
934 | - function getTimeDiff(time) { |
935 | - var hours, minutes; |
936 | - time = Math.floor(time / 60) |
937 | - minutes = time % 60 |
938 | - hours = Math.floor(time / 60) |
939 | - return [hours, minutes] |
940 | - } |
941 | - |
942 | - anchors.top: locationRow.bottom |
943 | - anchors.topMargin: units.gu(4) |
944 | width: parent.width |
945 | + height: childrenRect.height |
946 | |
947 | // U1db Index to index all documents storing the world city details |
948 | U1db.Index { |
949 | @@ -63,143 +54,91 @@ |
950 | } |
951 | |
952 | Repeater { |
953 | + id: userWorldCityRepeater |
954 | objectName: "userWorldCityRepeater" |
955 | + |
956 | + property var _currentSwipedItem: null |
957 | + |
958 | + function _updateSwipeState(item) |
959 | + { |
960 | + if (item.swipping) { |
961 | + return |
962 | + } |
963 | + |
964 | + if (item.swipeState !== "Normal") { |
965 | + if (userWorldCityRepeater._currentSwipedItem !== item) { |
966 | + if (userWorldCityRepeater._currentSwipedItem) { |
967 | + userWorldCityRepeater._currentSwipedItem.resetSwipe() |
968 | + } |
969 | + userWorldCityRepeater._currentSwipedItem = item |
970 | + } |
971 | + } else if (item.swipeState !== "Normal" |
972 | + && userWorldCityRepeater._currentSwipedItem === item) { |
973 | + userWorldCityRepeater._currentSwipedItem = null |
974 | + } |
975 | + } |
976 | + |
977 | model: u1dbModel |
978 | - delegate: SubtitledListItem { |
979 | + |
980 | + delegate: UserWorldCityDelegate { |
981 | + id: userWorldCityDelegate |
982 | objectName: "userWorldCityItem" + index |
983 | |
984 | - height: units.gu(9) |
985 | - |
986 | - text: model.city |
987 | - subText: model.country |
988 | - showDivider: false |
989 | - removable: true |
990 | - confirmRemoval: true |
991 | - |
992 | - Clock { |
993 | - id: localTimeVisual |
994 | - objectName: "localTimeVisual" + index |
995 | - |
996 | - /* |
997 | - This function would not be required once the upstream QT bug at |
998 | - https://bugreports.qt-project.org/browse/QTBUG-40275 is fixed. |
999 | - Due to this bug we are returning a time string instead of a |
1000 | - time object which forces us to parse the string and convert it |
1001 | - into a time object here. |
1002 | - */ |
1003 | - function getTime(timeString) { |
1004 | - var properTime = new Date() |
1005 | - properTime.setHours(timeString.split(":")[0]) |
1006 | - properTime.setMinutes(timeString.split(":")[1]) |
1007 | - properTime.setSeconds(0) |
1008 | - return properTime |
1009 | - } |
1010 | - |
1011 | - fontSize: units.dp(14) |
1012 | - periodFontSize: units.dp(7) |
1013 | - innerCircleWidth: units.gu(5) |
1014 | - width: units.gu(7) |
1015 | - |
1016 | - analogTime: getTime(model.localTime) |
1017 | - |
1018 | - anchors.centerIn: parent |
1019 | - |
1020 | - Connections { |
1021 | - target: clock |
1022 | - onTriggerFlip: { |
1023 | - localTimeVisual.flipClock() |
1024 | - } |
1025 | - } |
1026 | - |
1027 | - Component.onCompleted: { |
1028 | - isDigital = clockModeDocument.contents.digitalMode ? true : false |
1029 | - if (clockModeDocument.contents.digitalMode) { |
1030 | - digitalModeLoader.setSource |
1031 | - ("../components/DigitalMode.qml", |
1032 | - { |
1033 | - "width": innerCircleWidth, |
1034 | - "timeFontSize": fontSize, |
1035 | - "timePeriodFontSize": periodFontSize |
1036 | - }) |
1037 | - } |
1038 | - else { |
1039 | - analogModeLoader.setSource( |
1040 | - "../components/AnalogMode.qml", |
1041 | - { |
1042 | - "width": innerCircleWidth, |
1043 | - "showSeconds": isMainClock |
1044 | - }) |
1045 | - } |
1046 | - } |
1047 | - } |
1048 | - |
1049 | - Label { |
1050 | - id: relativeTimeLabel |
1051 | - objectName: "relativeTimeLabel" + index |
1052 | - |
1053 | - anchors.right: parent.right |
1054 | - anchors.verticalCenter: parent.verticalCenter |
1055 | - |
1056 | - fontSize: "xx-small" |
1057 | - horizontalAlignment: Text.AlignRight |
1058 | - text: { |
1059 | - var day; |
1060 | - |
1061 | - if(model.daysTo === 0) { |
1062 | - day = i18n.tr("Today") |
1063 | - } |
1064 | - |
1065 | - else if(model.daysTo === 1) { |
1066 | - day = i18n.tr("Tomorrow") |
1067 | - } |
1068 | - |
1069 | - else if(model.daysTo === -1) { |
1070 | - day = i18n.tr("Yesterday") |
1071 | - } |
1072 | - |
1073 | - var isBehind = model.timeTo > 0 ? i18n.tr("behind") |
1074 | - : i18n.tr("ahead") |
1075 | - |
1076 | - var timediff = worldCityColumn.getTimeDiff(Math.abs(model.timeTo)) |
1077 | - var minute = timediff[1] |
1078 | - var hour = timediff[0] |
1079 | - |
1080 | - if(hour > 0 && minute > 0) { |
1081 | - return ("%1\n%2hr%3min %4") |
1082 | - .arg(day) |
1083 | - .arg(hour) |
1084 | - .arg(minute) |
1085 | - .arg(isBehind) |
1086 | - } |
1087 | - |
1088 | - else if(hour > 0 && minute === 0) { |
1089 | - return ("%1\n%2hr %3") |
1090 | - .arg(day) |
1091 | - .arg(hour) |
1092 | - .arg(isBehind) |
1093 | - } |
1094 | - |
1095 | - else if(hour === 0 && minute > 0) { |
1096 | - return ("%1\n%2min %3") |
1097 | - .arg(day) |
1098 | - .arg(minute) |
1099 | - .arg(isBehind) |
1100 | - } |
1101 | - |
1102 | - else { |
1103 | - return i18n.tr("No Time Difference") |
1104 | - } |
1105 | - } |
1106 | - } |
1107 | - |
1108 | - onItemRemoved: { |
1109 | - /* |
1110 | - NOTE: This causes the document to be deleted twice resulting |
1111 | - in an error. The bug has been reported at |
1112 | - https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1276118 |
1113 | - */ |
1114 | - Utils.log("Deleting world location: " + model.city) |
1115 | - clockDB.deleteDoc(worldCityQuery.documents[index]) |
1116 | + property var removalAnimation |
1117 | + |
1118 | + function remove() { |
1119 | + removalAnimation.start() |
1120 | + } |
1121 | + |
1122 | + onSwippingChanged: { |
1123 | + userWorldCityRepeater._updateSwipeState(userWorldCityDelegate) |
1124 | + } |
1125 | + |
1126 | + onSwipeStateChanged: { |
1127 | + userWorldCityRepeater._updateSwipeState(userWorldCityDelegate) |
1128 | + } |
1129 | + |
1130 | + leftSideAction: Action { |
1131 | + iconName: "delete" |
1132 | + text: i18n.tr("Delete") |
1133 | + onTriggered: { |
1134 | + userWorldCityDelegate.remove() |
1135 | + } |
1136 | + } |
1137 | + |
1138 | + ListView.onRemove: ScriptAction { |
1139 | + script: { |
1140 | + if (userWorldCityRepeater._currentSwipedItem |
1141 | + === userWorldCityDelegate) { |
1142 | + userWorldCityRepeater._currentSwipedItem = null |
1143 | + } |
1144 | + } |
1145 | + } |
1146 | + |
1147 | + removalAnimation: SequentialAnimation { |
1148 | + alwaysRunToEnd: true |
1149 | + |
1150 | + PropertyAction { |
1151 | + target: userWorldCityDelegate |
1152 | + property: "ListView.delayRemove" |
1153 | + value: true |
1154 | + } |
1155 | + |
1156 | + UbuntuNumberAnimation { |
1157 | + target: userWorldCityDelegate |
1158 | + property: "height" |
1159 | + to: 1 |
1160 | + } |
1161 | + |
1162 | + PropertyAction { |
1163 | + target: userWorldCityDelegate |
1164 | + property: "ListView.delayRemove" |
1165 | + value: false |
1166 | + } |
1167 | + |
1168 | + ScriptAction { |
1169 | + script: clockDB.deleteDoc(worldCityQuery.documents[index]) |
1170 | + } |
1171 | } |
1172 | } |
1173 | } |
1174 | |
1175 | === modified file 'tests/autopilot/ubuntu_clock_app/emulators.py' |
1176 | --- tests/autopilot/ubuntu_clock_app/emulators.py 2014-08-07 13:41:43 +0000 |
1177 | +++ tests/autopilot/ubuntu_clock_app/emulators.py 2014-08-08 09:11:15 +0000 |
1178 | @@ -386,10 +386,26 @@ |
1179 | """Delete an alarm at the specified index.""" |
1180 | old_alarm_count = self.get_num_of_alarms() |
1181 | alarm = self.proxy_object.wait_select_single( |
1182 | - 'Base', objectName='alarm{}'.format(index)) |
1183 | + objectName='alarm{}'.format(index)) |
1184 | + |
1185 | alarm.swipe_to_delete() |
1186 | alarm.confirm_removal() |
1187 | try: |
1188 | self._get_saved_alarms_list().count.wait_for(old_alarm_count - 1) |
1189 | except AssertionError: |
1190 | raise ClockEmulatorException('Error deleting alarm.') |
1191 | + |
1192 | + |
1193 | +class ListItemWithActions(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase): |
1194 | + |
1195 | + def swipe_to_delete(self): |
1196 | + x, y, width, height = self.globalRect |
1197 | + start_x = x + (width * 0.2) |
1198 | + stop_x = x + (width * 0.8) |
1199 | + start_y = stop_y = y + (height // 2) |
1200 | + |
1201 | + self.pointing_device.drag(start_x, start_y, stop_x, stop_y) |
1202 | + |
1203 | + def confirm_removal(self): |
1204 | + deleteButton = self.wait_select_single(name='delete') |
1205 | + self.pointing_device.click_object(deleteButton) |
FAILED: Continuous integration, rev:40 /code.launchpad .net/~nik90/ ubuntu- clock-app/ custom- swipe-delete/ +merge/ 229088/ +edit-commit- message
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http:// 91.189. 93.70:8080/ job/ubuntu- clock-dev- ubuntu- clock-app- utopic- 3.0-ci/ 91/ 91.189. 93.70:8080/ job/generic- mediumtests- utopic/ 1229/console 91.189. 93.70:8080/ job/ubuntu- clock-dev- ubuntu- clock-app- utopic- 3.0-utopic- amd64-ci/ 91/console
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: 91.189. 93.70:8080/ job/ubuntu- clock-dev- ubuntu- clock-app- utopic- 3.0-ci/ 91/rebuild
http://