Merge lp:~ahayzen/ubuntu-weather-app/uc1.3-listitems into lp:ubuntu-weather-app

Proposed by Andrew Hayzen
Status: Merged
Approved by: Andrew Hayzen
Approved revision: 271
Merged at revision: 271
Proposed branch: lp:~ahayzen/ubuntu-weather-app/uc1.3-listitems
Merge into: lp:ubuntu-weather-app
Diff against target: 1447 lines (+246/-960)
9 files modified
app/components/HeadState/MultiSelectHeadState.qml (+6/-9)
app/components/ListItemReorderComponent.qml (+0/-106)
app/components/ListItemWithActions.qml (+0/-496)
app/components/MultiSelectListView.qml (+33/-14)
app/components/WeatherListItem.qml (+31/-111)
app/ui/LocationPane.qml (+72/-74)
app/ui/LocationsPage.qml (+95/-131)
tests/autopilot/ubuntu_weather_app/__init__.py (+7/-17)
tests/autopilot/ubuntu_weather_app/tests/test_locations_page.py (+2/-2)
To merge this branch: bzr merge lp:~ahayzen/ubuntu-weather-app/uc1.3-listitems
Reviewer Review Type Date Requested Status
Michael Sheldon (community) Approve
Jenkins Bot continuous-integration Approve
Ubuntu Weather Developers Pending
Review via email: mp+323134@code.launchpad.net

Commit message

* Migrate remaining old style listitems to UC1.3 ListItems

Description of the change

* Migrate remaining old style listitems to UC1.3 ListItems

To post a comment you must log in.
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

$ autopilot3 run ubuntu_weather_app
Loading tests from: /home/andrew/Workspace/Work/Canonical/code/ubuntu-weather-app/uc1.3-listitems/tests/autopilot

Tests running...

Ran 15 tests in 99.820s
OK
$ pep8 .
$ pyflakes .

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

Looks good :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/components/HeadState/MultiSelectHeadState.qml'
2--- app/components/HeadState/MultiSelectHeadState.qml 2016-07-19 16:14:05 +0000
3+++ app/components/HeadState/MultiSelectHeadState.qml 2017-04-25 12:59:22 +0000
4@@ -1,5 +1,5 @@
5 /*
6- * Copyright (C) 2015
7+ * Copyright (C) 2015, 2017
8 * Andrew Hayzen <ahayzen@gmail.com>
9 * Victor Thompson <victor.thompson@gmail.com>
10 *
11@@ -31,10 +31,7 @@
12 Action {
13 text: i18n.tr("Cancel selection")
14 iconName: "back"
15- onTriggered: {
16- listview.clearSelection()
17- listview.state = "normal"
18- }
19+ onTriggered: listview.closeSelection()
20 }
21 ]
22 }
23@@ -45,7 +42,7 @@
24 iconName: "select"
25 text: i18n.tr("Select All")
26 onTriggered: {
27- if (listview.selectedItems.length === listview.model.count) {
28+ if (listview.getSelectedIndices().length === listview.model.count) {
29 listview.clearSelection()
30 } else {
31 listview.selectAll()
32@@ -53,13 +50,13 @@
33 }
34 },
35 Action {
36- enabled: listview.selectedItems.length > 0
37+ enabled: listview.getSelectedIndices().length > 0
38 iconName: "delete"
39 text: i18n.tr("Delete")
40 visible: removable
41
42 onTriggered: {
43- removed(listview.selectedItems)
44+ removed(listview.getSelectedIndices())
45
46 listview.closeSelection()
47 }
48@@ -74,7 +71,7 @@
49 property bool removable: false
50 property Page thisPage
51
52- signal removed(var selectedItems)
53+ signal removed(var selectedIndices)
54
55 PropertyChanges {
56 target: thisPage
57
58=== removed file 'app/components/ListItemReorderComponent.qml'
59--- app/components/ListItemReorderComponent.qml 2015-11-02 21:28:34 +0000
60+++ app/components/ListItemReorderComponent.qml 1970-01-01 00:00:00 +0000
61@@ -1,106 +0,0 @@
62-/*
63- * Copyright (C) 2013, 2014, 2015
64- * Andrew Hayzen <ahayzen@gmail.com>
65- * Nekhelesh Ramananthan <krnekhelesh@gmail.com>
66- * Victor Thompson <victor.thompson@gmail.com>
67- *
68- * This program is free software; you can redistribute it and/or modify
69- * it under the terms of the GNU General Public License as published by
70- * the Free Software Foundation; version 3.
71- *
72- * This program is distributed in the hope that it will be useful,
73- * but WITHOUT ANY WARRANTY; without even the implied warranty of
74- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
75- * GNU General Public License for more details.
76- *
77- * You should have received a copy of the GNU General Public License
78- * along with this program. If not, see <http://www.gnu.org/licenses/>.
79- */
80-
81-import QtQuick 2.4
82-import Ubuntu.Components 1.3
83-
84-
85-Item {
86- id: actionReorder
87- width: units.gu(4)
88-
89- Icon {
90- anchors {
91- horizontalCenter: parent.horizontalCenter
92- verticalCenter: parent.verticalCenter
93- }
94- name: "navigation-menu" // TODO: use proper image
95- height: width
96- width: units.gu(3)
97- }
98-
99- MouseArea {
100- id: actionReorderMouseArea
101- anchors {
102- fill: parent
103- }
104- property int startY: 0
105- property int startContentY: 0
106-
107- onPressed: {
108- root.parent.parent.interactive = false; // stop scrolling of listview
109- startY = root.y;
110- startContentY = root.parent.parent.contentY;
111- root.z += 10; // force ontop of other elements
112-
113- console.debug("Reorder listitem pressed", root.y)
114- }
115- onMouseYChanged: root.y += mouse.y - (root.height / 2);
116- onReleased: {
117- console.debug("Reorder diff by position", getDiff());
118-
119- var diff = getDiff();
120-
121- // Remove the height of the actual item if moved down
122- if (diff > 0) {
123- diff -= 1;
124- }
125-
126- root.parent.parent.interactive = true; // reenable scrolling
127-
128- if (diff === 0) {
129- // Nothing has changed so reset the item
130- // z index is restored after animation
131- resetListItemYAnimation.start();
132- }
133- else {
134- var newIndex = index + diff;
135-
136- if (newIndex < 0) {
137- newIndex = 0;
138- }
139- else if (newIndex > root.parent.parent.count - 1) {
140- newIndex = root.parent.parent.count - 1;
141- }
142-
143- root.z -= 10; // restore z index
144- reorder(index, newIndex)
145- }
146- }
147-
148- function getDiff() {
149- // Get the amount of items that have been passed over (by centre)
150- return Math.round((((root.y - startY) + (root.parent.parent.contentY - startContentY)) / root.height) + 0.5);
151- }
152- }
153-
154- SequentialAnimation {
155- id: resetListItemYAnimation
156- UbuntuNumberAnimation {
157- target: root;
158- property: "y";
159- to: actionReorderMouseArea.startY
160- }
161- ScriptAction {
162- script: {
163- root.z -= 10; // restore z index
164- }
165- }
166- }
167-}
168
169=== removed file 'app/components/ListItemWithActions.qml'
170--- app/components/ListItemWithActions.qml 2015-11-02 21:28:34 +0000
171+++ app/components/ListItemWithActions.qml 1970-01-01 00:00:00 +0000
172@@ -1,496 +0,0 @@
173-/*
174- * Copyright (C) 2012-2015 Canonical, Ltd.
175- *
176- * This program is free software; you can redistribute it and/or modify
177- * it under the terms of the GNU General Public License as published by
178- * the Free Software Foundation; version 3.
179- *
180- * This program is distributed in the hope that it will be useful,
181- * but WITHOUT ANY WARRANTY; without even the implied warranty of
182- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
183- * GNU General Public License for more details.
184- *
185- * You should have received a copy of the GNU General Public License
186- * along with this program. If not, see <http://www.gnu.org/licenses/>.
187- */
188-
189-import QtQuick 2.4
190-import Ubuntu.Components 1.3
191-import Ubuntu.Components.ListItems 1.0 as ListItem
192-
193-
194-Item {
195- id: root
196- width: parent.width
197-
198- property Action leftSideAction: null
199- property list<Action> rightSideActions
200- property double defaultHeight: units.gu(8)
201- property bool locked: false
202- property Action activeAction: null
203- property var activeItem: null
204- property bool triggerActionOnMouseRelease: false
205- property color color: Theme.palette.normal.background
206- property color selectedColor: "#E6E6E6"
207- property bool selected: false
208- property bool selectionMode: false
209- property alias internalAnchors: mainContents.anchors
210- default property alias contents: mainContents.children
211-
212- readonly property double actionWidth: units.gu(4)
213- readonly property double leftActionWidth: units.gu(10)
214- readonly property double actionThreshold: actionWidth * 0.4
215- readonly property double threshold: 0.4
216- readonly property string swipeState: main.x == 0 ? "Normal" : main.x > 0 ? "LeftToRight" : "RightToLeft"
217- readonly property alias swipping: mainItemMoving.running
218- readonly property bool _showActions: mouseArea.pressed || swipeState != "Normal" || swipping
219-
220- property alias _main: main // CUSTOM
221- property alias pressed: mouseArea.pressed // CUSTOM
222-
223- /* internal */
224- property var _visibleRightSideActions: filterVisibleActions(rightSideActions)
225-
226- signal itemClicked(var mouse)
227- signal itemPressAndHold(var mouse)
228-
229- function returnToBoundsRTL(direction)
230- {
231- var actionFullWidth = actionWidth + units.gu(2)
232-
233- // go back to normal state if swipping reverse
234- if (direction === "LTR") {
235- updatePosition(0)
236- return
237- } else if (!triggerActionOnMouseRelease) {
238- updatePosition(-rightActionsView.width + units.gu(2))
239- return
240- }
241-
242- var xOffset = Math.abs(main.x)
243- var index = Math.min(Math.floor(xOffset / actionFullWidth), _visibleRightSideActions.length)
244- var newX = 0
245-
246- if (index === _visibleRightSideActions.length) {
247- newX = -(rightActionsView.width - units.gu(2))
248- } else if (index >= 1) {
249- newX = -(actionFullWidth * index)
250- }
251-
252- updatePosition(newX)
253- }
254-
255- function returnToBoundsLTR(direction)
256- {
257- var finalX = leftActionWidth
258- if ((direction === "RTL") || (main.x <= (finalX * root.threshold)))
259- finalX = 0
260- updatePosition(finalX)
261- }
262-
263- function returnToBounds(direction)
264- {
265- if (main.x < 0) {
266- returnToBoundsRTL(direction)
267- } else if (main.x > 0) {
268- returnToBoundsLTR(direction)
269- } else {
270- updatePosition(0)
271- }
272- }
273-
274- function contains(item, point, marginX)
275- {
276- var itemStartX = item.x - marginX
277- var itemEndX = item.x + item.width + marginX
278- return (point.x >= itemStartX) && (point.x <= itemEndX) &&
279- (point.y >= item.y) && (point.y <= (item.y + item.height));
280- }
281-
282- function getActionAt(point)
283- {
284- if (leftSideAction && contains(leftActionViewLoader.item, point, 0)) {
285- return leftSideAction
286- } else if (contains(rightActionsView, point, 0)) {
287- var newPoint = root.mapToItem(rightActionsView, point.x, point.y)
288- for (var i = 0; i < rightActionsRepeater.count; i++) {
289- var child = rightActionsRepeater.itemAt(i)
290- if (contains(child, newPoint, units.gu(1))) {
291- return i
292- }
293- }
294- }
295- return -1
296- }
297-
298- function updateActiveAction()
299- {
300- if (triggerActionOnMouseRelease &&
301- (main.x <= -(root.actionWidth + units.gu(2))) &&
302- (main.x > -(rightActionsView.width - units.gu(2)))) {
303- var actionFullWidth = actionWidth + units.gu(2)
304- var xOffset = Math.abs(main.x)
305- var index = Math.min(Math.floor(xOffset / actionFullWidth), _visibleRightSideActions.length)
306- index = index - 1
307- if (index > -1) {
308- root.activeItem = rightActionsRepeater.itemAt(index)
309- root.activeAction = root._visibleRightSideActions[index]
310- }
311- } else {
312- root.activeAction = null
313- }
314- }
315-
316- function resetSwipe()
317- {
318- updatePosition(0)
319- }
320-
321- function filterVisibleActions(actions)
322- {
323- var visibleActions = []
324- for(var i = 0; i < actions.length; i++) {
325- var action = actions[i]
326- if (action.visible) {
327- visibleActions.push(action)
328- }
329- }
330- return visibleActions
331- }
332-
333- function updatePosition(pos)
334- {
335- if (!root.triggerActionOnMouseRelease && (pos !== 0)) {
336- mouseArea.state = pos > 0 ? "RightToLeft" : "LeftToRight"
337- } else {
338- mouseArea.state = ""
339- }
340- main.x = pos
341- }
342-
343- // CUSTOM remove animation
344- SequentialAnimation {
345- id: removeAnimation
346-
347- property var action
348-
349- UbuntuNumberAnimation {
350- target: root
351- duration: UbuntuAnimation.BriskDuration
352- property: "height";
353- to: 0
354- }
355- ScriptAction {
356- script: removeAnimation.action.trigger()
357- }
358- }
359-
360- states: [
361- State {
362- name: "select"
363- when: selectionMode || selected
364- PropertyChanges {
365- target: selectionIcon
366- source: Qt.resolvedUrl("ListItemActions/CheckBox.qml")
367- anchors.leftMargin: units.gu(2)
368- }
369- PropertyChanges {
370- target: root
371- locked: true
372- }
373- PropertyChanges {
374- target: main
375- x: 0
376- }
377- }
378- ]
379-
380- height: defaultHeight
381- //clip: height !== defaultHeight // CUSTOM
382-
383- Loader { // CUSTOM
384- id: leftActionViewLoader
385- anchors {
386- top: parent.top
387- bottom: parent.bottom
388- right: main.left
389- }
390- asynchronous: true
391- sourceComponent: leftSideAction ? leftActionViewComponent : undefined
392- }
393-
394- Component { // CUSTOM
395- id: leftActionViewComponent
396-
397- Rectangle {
398- id: leftActionView
399- width: root.leftActionWidth + actionThreshold
400- color: UbuntuColors.red
401-
402- Icon {
403- id: leftActionIcon
404- anchors {
405- centerIn: parent
406- horizontalCenterOffset: actionThreshold / 2
407- }
408- objectName: "swipeDeleteAction" // CUSTOM
409- name: leftSideAction && _showActions ? leftSideAction.iconName : ""
410- color: Theme.palette.selected.field
411- height: units.gu(3)
412- width: units.gu(3)
413- }
414- }
415- }
416-
417- //Rectangle {
418- Item { // CUSTOM
419- id: rightActionsView
420-
421- anchors {
422- top: main.top
423- left: main.right
424- bottom: main.bottom
425- }
426- visible: _visibleRightSideActions.length > 0
427- width: rightActionsRepeater.count > 0 ? rightActionsRepeater.count * (root.actionWidth + units.gu(2)) + root.actionThreshold + units.gu(2) : 0
428- // color: "white" // CUSTOM
429-
430- Row {
431- anchors{
432- top: parent.top
433- left: parent.left
434- leftMargin: units.gu(2)
435- right: parent.right
436- rightMargin: units.gu(2)
437- bottom: parent.bottom
438- }
439- spacing: units.gu(2)
440- Repeater {
441- id: rightActionsRepeater
442-
443- model: _showActions ? _visibleRightSideActions : []
444- Item {
445- property alias image: img
446-
447- height: rightActionsView.height
448- width: root.actionWidth
449-
450- Icon {
451- id: img
452-
453- anchors.centerIn: parent
454- objectName: rightSideActions[index].objectName // CUSTOM
455- width: units.gu(3)
456- height: units.gu(3)
457- name: modelData.iconName
458- color: root.activeAction === modelData ? UbuntuColors.orange : UbuntuColors.coolGrey // CUSTOM
459- }
460- }
461- }
462- }
463- }
464-
465- Rectangle {
466- id: main
467- objectName: "mainItem"
468-
469- anchors {
470- top: parent.top
471- bottom: parent.bottom
472- }
473-
474- width: parent.width
475- color: root.selected ? root.selectedColor : root.color
476-
477- Loader {
478- id: selectionIcon
479-
480- anchors {
481- left: main.left
482- verticalCenter: main.verticalCenter
483- }
484- asynchronous: true // CUSTOM
485- width: (status === Loader.Ready) ? item.implicitWidth : 0
486- visible: (status === Loader.Ready) && (item.width === item.implicitWidth)
487-
488- Behavior on width {
489- NumberAnimation {
490- duration: UbuntuAnimation.SnapDuration
491- }
492- }
493- }
494-
495- Item {
496- id: mainContents
497-
498- anchors {
499- left: selectionIcon.right
500- //leftMargin: units.gu(2) // CUSTOM
501- top: parent.top
502- //topMargin: units.gu(1) // CUSTOM
503- right: parent.right
504- //rightMargin: units.gu(2) // CUSTOM
505- bottom: parent.bottom
506- //bottomMargin: units.gu(1) // CUSTOM
507- }
508- }
509-
510- Behavior on x {
511- UbuntuNumberAnimation {
512- id: mainItemMoving
513-
514- easing.type: Easing.OutElastic
515- duration: UbuntuAnimation.SlowDuration
516- }
517- }
518- }
519-
520- SequentialAnimation {
521- id: triggerAction
522-
523- property var currentItem: root.activeItem ? root.activeItem.image : null
524-
525- running: false
526- ParallelAnimation {
527- UbuntuNumberAnimation {
528- target: triggerAction.currentItem
529- property: "opacity"
530- from: 1.0
531- to: 0.0
532- duration: UbuntuAnimation.SlowDuration
533- easing {type: Easing.InOutBack; }
534- }
535- UbuntuNumberAnimation {
536- target: triggerAction.currentItem
537- properties: "width, height"
538- from: units.gu(3)
539- to: root.actionWidth
540- duration: UbuntuAnimation.SlowDuration
541- easing {type: Easing.InOutBack; }
542- }
543- }
544- PropertyAction {
545- target: triggerAction.currentItem
546- properties: "width, height"
547- value: units.gu(3)
548- }
549- PropertyAction {
550- target: triggerAction.currentItem
551- properties: "opacity"
552- value: 1.0
553- }
554- ScriptAction {
555- script: {
556- root.activeAction.triggered(root)
557- mouseArea.state = ""
558- }
559- }
560- PauseAnimation {
561- duration: 500
562- }
563- UbuntuNumberAnimation {
564- target: main
565- property: "x"
566- to: 0
567- }
568- }
569-
570- MouseArea {
571- id: mouseArea
572-
573- property bool locked: root.locked || ((root.leftSideAction === null) && (root._visibleRightSideActions.count === 0)) // CUSTOM
574- property bool manual: false
575- property string direction: "None"
576- property real lastX: -1
577-
578- anchors.fill: parent
579- drag {
580- target: locked ? null : main
581- axis: Drag.XAxis
582- minimumX: rightActionsView.visible ? -(rightActionsView.width) : 0
583- maximumX: leftSideAction ? leftActionViewLoader.item.width : 0
584- threshold: root.actionThreshold
585- }
586-
587- states: [
588- State {
589- name: "LeftToRight"
590- PropertyChanges {
591- target: mouseArea
592- drag.maximumX: 0
593- }
594- },
595- State {
596- name: "RightToLeft"
597- PropertyChanges {
598- target: mouseArea
599- drag.minimumX: 0
600- }
601- }
602- ]
603-
604- onMouseXChanged: {
605- var offset = (lastX - mouseX)
606- if (Math.abs(offset) <= root.actionThreshold) {
607- return
608- }
609- lastX = mouseX
610- direction = offset > 0 ? "RTL" : "LTR";
611- }
612-
613- onPressed: {
614- lastX = mouse.x
615- }
616-
617- onReleased: {
618- if (root.triggerActionOnMouseRelease && root.activeAction) {
619- triggerAction.start()
620- } else {
621- root.returnToBounds()
622- root.activeAction = null
623- }
624- lastX = -1
625- direction = "None"
626- }
627- onClicked: {
628- if (selectionMode) { // CUSTOM - selecting a listitem should toggle selection if in selectionMode
629- selected = !selected
630- return
631- } else if (main.x === 0) {
632- root.itemClicked(mouse)
633- } else if (main.x > 0) {
634- var action = getActionAt(Qt.point(mouse.x, mouse.y))
635- if (action && action !== -1) {
636- //action.triggered(root)
637- removeAnimation.action = action // CUSTOM - use our animation instead
638- removeAnimation.start() // CUSTOM
639- }
640- } else {
641- var actionIndex = getActionAt(Qt.point(mouse.x, mouse.y))
642-
643- if (actionIndex !== -1 && actionIndex !== leftSideAction) { // CUSTOM - can be leftAction
644- root.activeItem = rightActionsRepeater.itemAt(actionIndex)
645- root.activeAction = root.rightSideActions[actionIndex]
646- triggerAction.start()
647- return
648- }
649- }
650- root.resetSwipe()
651- }
652-
653- onPositionChanged: {
654- if (mouseArea.pressed) {
655- updateActiveAction()
656-
657- listItemSwiping(index) // CUSTOM - tells other listitems to dismiss any swipe
658- }
659- }
660- onPressAndHold: {
661- if (main.x === 0) {
662- root.itemPressAndHold(mouse)
663- }
664- }
665-
666- z: -1
667- }
668-}
669
670=== modified file 'app/components/MultiSelectListView.qml'
671--- app/components/MultiSelectListView.qml 2015-11-02 21:28:34 +0000
672+++ app/components/MultiSelectListView.qml 2017-04-25 12:59:22 +0000
673@@ -1,5 +1,5 @@
674 /*
675- * Copyright (C) 2013, 2014, 2015
676+ * Copyright (C) 2013, 2014, 2015, 2017
677 * Andrew Hayzen <ahayzen@gmail.com>
678 * Daniel Holm <d.holmen@gmail.com>
679 * Victor Thompson <victor.thompson@gmail.com>
680@@ -20,33 +20,52 @@
681 import QtQuick 2.4
682 import Ubuntu.Components 1.3
683
684-
685 WeatherListView {
686- property var selectedItems: []
687+ // Can't access ViewItems externally
688+ // so we need to expose if in multiselect mode for the header states
689+ state: ViewItems.selectMode ? "multiselectable" : "default"
690
691 signal clearSelection()
692 signal closeSelection()
693+ signal reorder(int from, int to)
694 signal selectAll()
695
696- onClearSelection: selectedItems = []
697+ onClearSelection: ViewItems.selectedIndices = []
698 onCloseSelection: {
699 clearSelection()
700- state = "normal"
701+ ViewItems.selectMode = false
702+ ViewItems.dragMode = false
703 }
704 onSelectAll: {
705- var tmp = selectedItems
706+ var tmp = []
707
708 for (var i=0; i < model.count; i++) {
709- if (tmp.indexOf(i) === -1) {
710- tmp.push(i)
711- }
712+ tmp.push(i)
713 }
714
715- selectedItems = tmp
716- }
717- onVisibleChanged: {
718- if (!visible) {
719- closeSelection()
720+ ViewItems.selectedIndices = tmp
721+ }
722+
723+ // Can't access ViewItems externally
724+ // so for the header actions we need to expose the selectedIndices
725+ function getSelectedIndices() {
726+ var indicies = ViewItems.selectedIndices.slice();
727+
728+ indicies.sort(); // ensure indicies are in-order
729+
730+ return indicies;
731+ }
732+
733+ ViewItems.selectMode: false
734+ ViewItems.dragMode: false
735+ ViewItems.onDragUpdated: {
736+ // Only update the model when the listitem is dropped, not 'live'
737+ if (event.status == ListItemDrag.Moving) {
738+ event.accept = false
739+ } else if (event.status == ListItemDrag.Dropped) {
740+ model.move(event.from, event.to, 1);
741+
742+ reorder(event.from, event.to)
743 }
744 }
745 }
746
747=== modified file 'app/components/WeatherListItem.qml'
748--- app/components/WeatherListItem.qml 2015-11-02 21:28:34 +0000
749+++ app/components/WeatherListItem.qml 2017-04-25 12:59:22 +0000
750@@ -1,5 +1,5 @@
751 /*
752- * Copyright (C) 2013, 2014, 2015
753+ * Copyright (C) 2013, 2014, 2015, 2017
754 * Andrew Hayzen <ahayzen@gmail.com>
755 * Nekhelesh Ramananthan <krnekhelesh@gmail.com>
756 * Victor Thompson <victor.thompson@gmail.com>
757@@ -20,119 +20,39 @@
758 import QtQuick 2.4
759 import Ubuntu.Components 1.3
760
761-ListItemWithActions {
762- id: root
763+ListItem {
764 color: "transparent"
765-
766- property int listItemIndex: index
767+ divider {
768+ visible: true
769+ }
770+ highlightColor: Qt.lighter(color, 1.2)
771+
772+ // Store the currentColor so that actions can bind to it
773+ property var currentColor: highlighted ? highlightColor : color
774+
775 property bool multiselectable: false
776- property int previousListItemIndex: -1
777+ property bool pressAndHoldEnabled: true
778 property bool reorderable: false
779
780- signal reorder(int from, int to)
781-
782- onItemPressAndHold: {
783- if (multiselectable) {
784- selectionMode = true
785- }
786- }
787-
788- onListItemIndexChanged: {
789- var i = parent.parent.selectedItems.lastIndexOf(previousListItemIndex)
790-
791- if (i !== -1) {
792- parent.parent.selectedItems[i] = listItemIndex
793- }
794-
795- previousListItemIndex = listItemIndex
796- }
797-
798- onSelectedChanged: {
799- if (selectionMode) {
800- var tmp = parent.parent.selectedItems
801-
802- if (selected) {
803- if (parent.parent.selectedItems.indexOf(listItemIndex) === -1) {
804- tmp.push(listItemIndex)
805- parent.parent.selectedItems = tmp
806- }
807- } else {
808- tmp.splice(parent.parent.selectedItems.indexOf(listItemIndex), 1)
809- parent.parent.selectedItems = tmp
810- }
811- }
812- }
813-
814- onSelectionModeChanged: {
815- if (reorderable && selectionMode) {
816- resetSwipe()
817- }
818-
819- for (var j=0; j < _main.children.length; j++) {
820- if (_main.children[j] !== actionReorderLoader) {
821- _main.children[j].anchors.rightMargin = reorderable && selectionMode ? actionReorderLoader.width + units.gu(2) : 0
822- }
823- }
824-
825- parent.parent.state = selectionMode ? "multiselectable" : "normal"
826-
827- if (!selectionMode) {
828- selected = false
829- }
830- }
831-
832- /* Highlight the listitem on press */
833- Rectangle {
834- id: listItemBrighten
835- color: root.pressed ? UbuntuColors.coolGrey : "transparent"
836- opacity: 0.1
837- height: root.height
838- x: root.x - parent.x // -parent.x due to selectionIcon in ListItemWithActions
839- width: root.width
840- }
841-
842- /* Reorder Component */
843- Loader {
844- id: actionReorderLoader
845- active: reorderable && selectionMode && root.parent.parent.selectedItems.length === 0
846- anchors {
847- bottom: parent.bottom
848- right: parent.right
849- rightMargin: units.gu(1)
850- top: parent.top
851- }
852- asynchronous: true
853- source: "ListItemReorderComponent.qml"
854- }
855-
856- Item {
857- Connections { // Only allow one ListItem to be swiping at any time
858- target: weatherApp
859- onListItemSwiping: {
860- if (i !== index) {
861- root.resetSwipe();
862- }
863- }
864- }
865-
866- Connections { // Connections from signals in the ListView
867- target: root.parent.parent
868- onClearSelection: selected = false
869- onFlickingChanged: {
870- if (root.parent.parent.flicking) {
871- root.resetSwipe()
872- }
873- }
874- onSelectAll: selected = true
875- onStateChanged: selectionMode = root.parent.parent.state === "multiselectable"
876- }
877- }
878-
879- Component.onCompleted: { // reload settings as delegates are destroyed
880- if (parent.parent.selectedItems.indexOf(index) !== -1) {
881- selected = true
882- }
883-
884- selectionMode = root.parent.parent.state === "multiselectable"
885+ signal itemClicked()
886+
887+ onClicked: {
888+ if (selectMode) {
889+ selected = !selected;
890+ } else {
891+ itemClicked()
892+ }
893+ }
894+
895+ onPressAndHold: {
896+ if (pressAndHoldEnabled) {
897+ if (reorderable) {
898+ ListView.view.ViewItems.dragMode = !ListView.view.ViewItems.dragMode
899+ }
900+
901+ if (multiselectable) {
902+ ListView.view.ViewItems.selectMode = !ListView.view.ViewItems.selectMode
903+ }
904+ }
905 }
906 }
907
908=== modified file 'app/ui/LocationPane.qml'
909--- app/ui/LocationPane.qml 2017-04-12 09:55:14 +0000
910+++ app/ui/LocationPane.qml 2017-04-25 12:59:22 +0000
911@@ -1,5 +1,5 @@
912 /*
913- * Copyright (C) 2015 Canonical Ltd
914+ * Copyright (C) 2015, 2017 Canonical Ltd
915 *
916 * This file is part of Ubuntu Weather App
917 *
918@@ -18,7 +18,6 @@
919
920 import QtQuick 2.4
921 import Ubuntu.Components 1.3
922-import Ubuntu.Components.ListItems 0.1 as ListItem
923 import "../components"
924 import "../data/suncalc.js" as SunCalc
925
926@@ -55,79 +54,78 @@
927
928 modelData: model
929 }
930- header: Column {
931- id: locationTop
932-
933- anchors {
934- left: parent.left
935- right: parent.right
936- margins: units.gu(2)
937- }
938- spacing: units.gu(1)
939-
940- Row { // spacing at top
941- height: units.gu(1)
942- width: parent.width
943- }
944-
945- HeaderRow {
946- id: headerRow
947- locationName: mainPageWeekdayListView.name
948- }
949-
950- HomeGraphic {
951- id: homeGraphic
952- icon: mainPageWeekdayListView.icon
953- visible: graphicVisible;
954- MouseArea {
955- anchors.fill: parent
956- onClicked: {
957- graphicVisible = false;
958- }
959- }
960- }
961-
962- Loader {
963- id: homeHourlyLoader
964- active: !homeGraphic.visible
965- asynchronous: true
966- height: units.gu(32)
967- source: "../components/HomeHourly.qml"
968- visible: active
969- width: parent.width
970- }
971-
972- HomeTempInfo {
973- id: homeTempInfo
974- modelData: todayData
975- now: mainPageWeekdayListView.currentTemp
976- updatedAt: mainPageWeekdayListView.lastFetch
977- }
978-
979- // TODO: Migrate this to using the new SDK list item when possible.
980- ListItem.ThinDivider {
981+ header: ListItem {
982+ divider {
983+ visible: true
984+ }
985+ height: locationTop.height
986+
987+ Column {
988+ id: locationTop
989+
990 anchors {
991- leftMargin: units.gu(-2);
992- rightMargin: units.gu(-2)
993- }
994- }
995-
996- NumberAnimation {
997- id: scrollToTopAnimation
998- target: mainPageWeekdayListView;
999- property: "contentY";
1000- duration: 200;
1001- easing.type: Easing.InOutQuad
1002- to: -height
1003- }
1004-
1005- Connections {
1006- target: locationPages
1007- onCurrentIndexChanged: {
1008- if (locationPages.currentIndex !== index) {
1009- scrollToTopAnimation.start()
1010- } else {
1011- mainPageWeekdayListView.contentY = -locationTop.height
1012+ left: parent.left
1013+ right: parent.right
1014+ margins: units.gu(2)
1015+ }
1016+ spacing: units.gu(1)
1017+
1018+ Row { // spacing at top
1019+ height: units.gu(1)
1020+ width: parent.width
1021+ }
1022+
1023+ HeaderRow {
1024+ id: headerRow
1025+ locationName: mainPageWeekdayListView.name
1026+ }
1027+
1028+ HomeGraphic {
1029+ id: homeGraphic
1030+ icon: mainPageWeekdayListView.icon
1031+ visible: graphicVisible;
1032+ MouseArea {
1033+ anchors.fill: parent
1034+ onClicked: {
1035+ graphicVisible = false;
1036+ }
1037+ }
1038+ }
1039+
1040+ Loader {
1041+ id: homeHourlyLoader
1042+ active: !homeGraphic.visible
1043+ asynchronous: true
1044+ height: units.gu(32)
1045+ source: "../components/HomeHourly.qml"
1046+ visible: active
1047+ width: parent.width
1048+ }
1049+
1050+ HomeTempInfo {
1051+ id: homeTempInfo
1052+ modelData: todayData
1053+ now: mainPageWeekdayListView.currentTemp
1054+ updatedAt: mainPageWeekdayListView.lastFetch
1055+ }
1056+
1057+ NumberAnimation {
1058+ id: scrollToTopAnimation
1059+ target: mainPageWeekdayListView;
1060+ property: "contentY";
1061+ duration: 200;
1062+ easing.type: Easing.InOutQuad
1063+ to: -height
1064+ }
1065+
1066+ Connections {
1067+ target: locationPages
1068+ onCurrentIndexChanged: {
1069+ if (locationPages.currentIndex !== index) {
1070+ scrollToTopAnimation.start()
1071+ } else {
1072+ mainPageWeekdayListView.contentY = -locationTop.height
1073+ }
1074 }
1075 }
1076 }
1077
1078=== modified file 'app/ui/LocationsPage.qml'
1079--- app/ui/LocationsPage.qml 2017-04-18 09:08:08 +0000
1080+++ app/ui/LocationsPage.qml 2017-04-25 12:59:22 +0000
1081@@ -1,5 +1,5 @@
1082 /*
1083- * Copyright (C) 2015 Canonical Ltd
1084+ * Copyright (C) 2015, 2017 Canonical Ltd
1085 *
1086 * This file is part of Ubuntu Weather App
1087 *
1088@@ -18,7 +18,6 @@
1089
1090 import QtQuick 2.4
1091 import Ubuntu.Components 1.3
1092-import Ubuntu.Components.ListItems 0.1 as ListItem
1093 import "../components"
1094 import "../components/HeadState"
1095 import "../components/ListItemActions"
1096@@ -37,7 +36,7 @@
1097 removable: true
1098 thisPage: locationsPage
1099
1100- onRemoved: storage.removeMultiLocations(selectedItems.slice());
1101+ onRemoved: storage.removeMultiLocations(selectedIndices.slice());
1102 }
1103 ]
1104
1105@@ -65,93 +64,91 @@
1106 left: parent.left
1107 right: parent.right
1108 }
1109- height: settings.addedCurrentLocation && settings.detectCurrentLocation ? units.gu(8) : units.gu(0)
1110+ height: settings.addedCurrentLocation && settings.detectCurrentLocation ? contentHeight : units.gu(0)
1111 interactive: false
1112 model: currentLocationModel
1113 delegate: WeatherListItem {
1114 id: currentLocationListItem
1115+ height: currentListItemLayout.height + (divider.visible ? divider.height : 0) + (fakeDivider.visible ? fakeDivider.height : 0)
1116
1117 onItemClicked: {
1118 settings.current = index;
1119+
1120+ // Ensure any selections are closed
1121+ locationsListView.closeSelection();
1122+
1123 locationsPage.pop()
1124 }
1125
1126- Column {
1127- anchors {
1128- left: parent.left
1129- right: currentWeatherImage.left
1130- rightMargin: units.gu(3)
1131- verticalCenter: parent.verticalCenter
1132+ ListItemLayout {
1133+ id: currentListItemLayout
1134+ subtitle {
1135+ elide: Text.ElideRight
1136+ fontSize: "small"
1137+ text: name + ", " + (adminName1 == name ? countryName : adminName1)
1138 }
1139-
1140- Label {
1141- id: currentLocationName
1142-
1143- anchors {
1144- left: parent.left
1145- leftMargin: units.gu(2)
1146- }
1147-
1148+ title {
1149 elide: Text.ElideRight
1150 fontSize: "medium"
1151 text: i18n.tr("Current Location")
1152- width: parent.width
1153- }
1154+ }
1155+
1156+ Icon {
1157+ anchors {
1158+ verticalCenter: parent.verticalCenter
1159+ }
1160+ height: parent.height / 2
1161+ name: icon
1162+ SlotsLayout.position: SlotsLayout.Trailing
1163+ SlotsLayout.overrideVerticalPositioning: true
1164+ width: height
1165+ }
1166+
1167 Label {
1168- id: currentLocationName2
1169-
1170 anchors {
1171- left: parent.left
1172- leftMargin: units.gu(2)
1173+ verticalCenter: parent.verticalCenter
1174 }
1175-
1176- color: UbuntuColors.graphite
1177+ color: UbuntuColors.orange
1178 elide: Text.ElideRight
1179- fontSize: "small"
1180- font.weight: Font.Light
1181- text: name + ", " + (adminName1 == name ? countryName : adminName1)
1182- width: parent.width
1183- }
1184- }
1185-
1186- Icon {
1187- id: currentWeatherImage
1188- anchors {
1189- right: parent.right
1190- rightMargin: units.gu(14)
1191- verticalCenter: parent.verticalCenter
1192- }
1193- name: icon
1194- height: units.gu(3)
1195- width: units.gu(3)
1196- }
1197-
1198- Label {
1199- id: currentTemperatureLabel
1200- anchors {
1201- left: currentWeatherImage.right
1202- leftMargin: units.gu(1)
1203- right: parent.right
1204- rightMargin: units.gu(2)
1205- verticalCenter: parent.verticalCenter
1206- }
1207- color: UbuntuColors.orange
1208- elide: Text.ElideRight
1209- font.pixelSize: units.gu(4)
1210- font.weight: Font.Light
1211- horizontalAlignment: Text.AlignRight
1212- text: temp + settings.tempScale
1213+ font {
1214+ pixelSize: parent.height / 2
1215+ weight: Font.Light
1216+ }
1217+ horizontalAlignment: Text.AlignRight
1218+ SlotsLayout.position: SlotsLayout.Trailing
1219+ SlotsLayout.overrideVerticalPositioning: true
1220+ text: temp + settings.tempScale
1221+ }
1222+ }
1223+
1224+ // Inject extra divider when we are the last as the SDK hides it
1225+ // but we need one as we have another listview directly below
1226+ ListItem {
1227+ id: fakeDivider
1228+ anchors {
1229+ bottom: parent.bottom
1230+ }
1231+ divider {
1232+ visible: true
1233+ }
1234+ height: divider.height
1235+ visible: currentLocationModel.count - 1 == index
1236 }
1237 }
1238 }
1239
1240 delegate: WeatherListItem {
1241 id: locationsListItem
1242+ height: listItemLayout.height + (divider.visible ? divider.height : 0)
1243+ leadingActions: ListItemActions {
1244+ actions: [
1245+ Remove {
1246+ onTriggered: storage.removeLocation(index)
1247+ }
1248+ ]
1249+ }
1250+ multiselectable: true
1251 objectName: "location" + index
1252- leftSideAction: Remove {
1253- onTriggered: storage.removeLocation(index)
1254- }
1255- multiselectable: true
1256 reorderable: true
1257
1258 onItemClicked: {
1259@@ -163,89 +160,56 @@
1260
1261 locationsPage.pop()
1262 }
1263- onReorder: {
1264- console.debug("Move: ", from, to);
1265-
1266- storage.moveLocation(from, to);
1267- }
1268-
1269- ListItem.ThinDivider {
1270- anchors {
1271- top: parent.top
1272- }
1273- visible: index == 0
1274- }
1275-
1276- Item {
1277- anchors {
1278- fill: parent
1279- leftMargin: units.gu(2)
1280- rightMargin: units.gu(2)
1281- }
1282-
1283- Column {
1284- anchors {
1285- left: parent.left
1286- right: weatherImage.visible ? weatherImage.left : parent.right
1287- rightMargin: units.gu(1)
1288- verticalCenter: parent.verticalCenter
1289- }
1290-
1291- Label {
1292- id: locationName
1293- objectName: "name"
1294- elide: Text.ElideRight
1295- fontSize: "medium"
1296- text: name
1297- width: parent.width
1298- }
1299- Label {
1300- id: locationName2
1301- color: UbuntuColors.graphite
1302- elide: Text.ElideRight
1303- fontSize: "small"
1304- font.weight: Font.Light
1305- text: adminName1 == name ? countryName : adminName1
1306- width: parent.width
1307- }
1308+
1309+ ListItemLayout {
1310+ id: listItemLayout
1311+ subtitle {
1312+ elide: Text.ElideRight
1313+ fontSize: "small"
1314+ text: adminName1 == name ? countryName : adminName1
1315+ }
1316+ title {
1317+ elide: Text.ElideRight
1318+ fontSize: "medium"
1319+ objectName: "name"
1320+ text: name
1321 }
1322
1323 Icon {
1324- id: weatherImage
1325 anchors {
1326- right: parent.right
1327- rightMargin: units.gu(12)
1328 verticalCenter: parent.verticalCenter
1329 }
1330+ height: parent.height / 2
1331 name: icon
1332- height: units.gu(3)
1333- width: units.gu(3)
1334- visible: locationsPage.state === "default"
1335+ SlotsLayout.position: SlotsLayout.Trailing
1336+ SlotsLayout.overrideVerticalPositioning: true
1337+ width: height
1338+ visible: locationsListView.state === "default"
1339 }
1340
1341 Label {
1342- id: temperatureLabel
1343 anchors {
1344- left: weatherImage.right
1345- leftMargin: units.gu(1)
1346- right: parent.right
1347 verticalCenter: parent.verticalCenter
1348 }
1349 color: UbuntuColors.orange
1350 elide: Text.ElideRight
1351- font.pixelSize: units.gu(4)
1352- font.weight: Font.Light
1353+ font {
1354+ pixelSize: parent.height / 2
1355+ weight: Font.Light
1356+ }
1357 horizontalAlignment: Text.AlignRight
1358+ SlotsLayout.position: SlotsLayout.Trailing
1359+ SlotsLayout.overrideVerticalPositioning: true
1360 text: temp + settings.tempScale
1361- visible: locationsPage.state === "default"
1362- }
1363- }
1364-
1365- ListItem.ThinDivider {
1366- anchors {
1367- bottom: parent.bottom
1368- }
1369- }
1370+ visible: locationsListView.state === "default"
1371+ }
1372+ }
1373+ }
1374+
1375+ onReorder: {
1376+ console.debug("Move: ", from, to);
1377+
1378+ storage.moveLocation(from, to);
1379 }
1380 }
1381
1382
1383=== modified file 'tests/autopilot/ubuntu_weather_app/__init__.py'
1384--- tests/autopilot/ubuntu_weather_app/__init__.py 2017-04-13 10:38:05 +0000
1385+++ tests/autopilot/ubuntu_weather_app/__init__.py 2017-04-25 12:59:22 +0000
1386@@ -1,5 +1,5 @@
1387 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1388-# Copyright 2013, 2014, 2015 Canonical
1389+# Copyright 2013, 2014, 2015, 2017 Canonical
1390 #
1391 # This program is free software: you can redistribute it and/or modify it
1392 # under the terms of the GNU General Public License version 3, as published
1393@@ -8,7 +8,7 @@
1394 """ubuntu-weather-app tests and emulators - top level package."""
1395 from autopilot.introspection import dbus
1396 import logging
1397-from ubuntuuitoolkit import (MainView, QQuickListView,
1398+from ubuntuuitoolkit import (MainView, QQuickListView, UCListItem,
1399 UbuntuUIToolkitCustomProxyObjectBase)
1400
1401 logger = logging.getLogger(__name__)
1402@@ -329,20 +329,10 @@
1403 "StandardListItem", iconVisible=showIcon)
1404
1405
1406-class WeatherListItem(UbuntuUIToolkitCustomProxyObjectBase):
1407+class WeatherListItem(UCListItem):
1408+ def click_remove_action(self):
1409+ return self.trigger_leading_action("swipeDeleteAction",
1410+ self.wait_until_destroyed)
1411+
1412 def get_name(self):
1413 return self.select_single("UCLabel", objectName="name").text
1414-
1415- @click_object
1416- def select_remove(self):
1417- return self.select_single(objectName="swipeDeleteAction")
1418-
1419- def swipe_and_select_remove(self):
1420- x, y, width, height = self.globalRect
1421- start_x = x + (width * 0.2)
1422- stop_x = x + (width * 0.8)
1423- start_y = stop_y = y + (height // 2)
1424-
1425- self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
1426-
1427- self.select_remove()
1428
1429=== modified file 'tests/autopilot/ubuntu_weather_app/tests/test_locations_page.py'
1430--- tests/autopilot/ubuntu_weather_app/tests/test_locations_page.py 2015-08-10 03:13:48 +0000
1431+++ tests/autopilot/ubuntu_weather_app/tests/test_locations_page.py 2017-04-25 12:59:22 +0000
1432@@ -1,5 +1,5 @@
1433 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1434-# Copyright 2013, 2014, 2015 Canonical
1435+# Copyright 2013, 2014, 2015, 2017 Canonical
1436 #
1437 # This program is free software: you can redistribute it and/or modify it
1438 # under the terms of the GNU General Public License version 3, as published
1439@@ -48,7 +48,7 @@
1440 self.assertThat(list_item.get_name(), Equals("London"))
1441
1442 # Remove the location via the list item action
1443- list_item.swipe_and_select_remove()
1444+ list_item.click_remove_action()
1445
1446 # Check that the location was removed
1447 self.assertThat(self.home_page.get_location_count,

Subscribers

People subscribed via source and target branches

to all changes: