Merge lp:~nik90/ubuntu-clock-app/custom-swipe-delete into lp:ubuntu-clock-app

Proposed by Nekhelesh Ramananthan on 2014-07-31
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
Reviewer Review Type Date Requested Status
Nicholas Skaggs (community) 2014-07-31 Approve on 2014-08-07
Ubuntu Phone Apps Jenkins Bot continuous-integration Needs Fixing on 2014-08-07
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 ListItemWithActions.qml from the address book app. So it is not necessary to review that file since no changes were made to it.

All upstream components are placed inside the upstreamcomponents folder for easier identification and replacement when they are available in the SDK.

To post a comment you must log in.

FAILED: Continuous integration, rev:40
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://code.launchpad.net/~nik90/ubuntu-clock-app/custom-swipe-delete/+merge/229088/+edit-commit-message

http://91.189.93.70:8080/job/ubuntu-clock-dev-ubuntu-clock-app-utopic-3.0-ci/91/
Executed test runs:
    FAILURE: http://91.189.93.70:8080/job/generic-mediumtests-utopic/1229/console
    FAILURE: http://91.189.93.70:8080/job/ubuntu-clock-dev-ubuntu-clock-app-utopic-3.0-utopic-amd64-ci/91/console

Click here to trigger a rebuild:
http://91.189.93.70:8080/job/ubuntu-clock-dev-ubuntu-clock-app-utopic-3.0-ci/91/rebuild

review: Needs Fixing (continuous-integration)
48. By Nekhelesh Ramananthan on 2014-08-07

removed unnecessary import

Nicholas Skaggs (nskaggs) :
review: Approve
49. By Nekhelesh Ramananthan on 2014-08-08

Merged trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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)

Subscribers

People subscribed via source and target branches