~ahayzen/music-app/remix-add-card-view

« back to all changes in this revision

Viewing changes to common/ListItemWithActions.qml

* Remove expander, reorder, swipedelete code
* Use ListItemWithActions from other apps
* Implement actions for add to playlist, add to queue, edit playlist, delete playlist
* Implement reordering. Fixes: https://bugs.launchpad.net/bugs/1311800, https://bugs.launchpad.net/bugs/1338042.

Approved by Victor Thompson, Ubuntu Phone Apps Jenkins Bot.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2012-2014 Canonical, Ltd.
 
3
 *
 
4
 * This program is free software; you can redistribute it and/or modify
 
5
 * it under the terms of the GNU General Public License as published by
 
6
 * the Free Software Foundation; version 3.
 
7
 *
 
8
 * This program is distributed in the hope that it will be useful,
 
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
 * GNU General Public License for more details.
 
12
 *
 
13
 * You should have received a copy of the GNU General Public License
 
14
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
 */
 
16
 
 
17
import QtQuick 2.2
 
18
import Ubuntu.Components 1.1
 
19
import Ubuntu.Components.ListItems 0.1 as ListItem
 
20
 
 
21
 
 
22
ListItem.Standard {  // CUSTOM
 
23
//Item {
 
24
    id: root
 
25
 
 
26
    property Action leftSideAction: null
 
27
    property list<Action> rightSideActions
 
28
    property double defaultHeight: units.gu(8)
 
29
    property bool locked: false
 
30
    property Action activeAction: null
 
31
    property var activeItem: null
 
32
    property bool triggerActionOnMouseRelease: false
 
33
    property alias color: main.color
 
34
    default property alias contents: main.children
 
35
 
 
36
    property bool reorderable: false  // CUSTOM
 
37
    property bool reordering: false  // CUSTOM
 
38
 
 
39
    readonly property double actionWidth: units.gu(5)
 
40
    readonly property double leftActionWidth: units.gu(10)
 
41
    readonly property double actionThreshold: actionWidth * 0.4
 
42
    readonly property double threshold: 0.4
 
43
    readonly property string swipeState: main.x == 0 ? "Normal" : main.x > 0 ? "LeftToRight" : "RightToLeft"
 
44
    readonly property alias swipping: mainItemMoving.running
 
45
 
 
46
    signal itemClicked(var mouse)
 
47
    signal itemPressAndHold(var mouse)
 
48
 
 
49
    signal reorder(int from, int to)  // CUSTOM
 
50
 
 
51
    onItemPressAndHold: reordering = reorderable && !reordering  // CUSTOM
 
52
    onReorderingChanged: {  // CUSTOM
 
53
        if (reordering) {
 
54
            resetSwipe()
 
55
        }
 
56
 
 
57
        for (var j=0; j < main.children.length; j++) {
 
58
            main.children[j].anchors.rightMargin = reordering ? actionReorder.width + units.gu(2) : 0
 
59
        }
 
60
 
 
61
        parent.state = reordering ? "reorder" : "normal"
 
62
    }
 
63
 
 
64
    function returnToBoundsRTL()
 
65
    {
 
66
        var actionFullWidth = actionWidth + units.gu(2)
 
67
        var xOffset = Math.abs(main.x)
 
68
        var index = Math.min(Math.floor(xOffset / actionFullWidth), rightSideActions.length)
 
69
        var j;  // CUSTOM
 
70
 
 
71
        if (index < 1) {
 
72
            main.x = 0
 
73
 
 
74
            resetPrimed()  // CUSTOM
 
75
        } else if (index === rightSideActions.length) {
 
76
            main.x = -rightActionsView.width
 
77
 
 
78
            for (j=0; j < rightSideActions.length; j++) {  // CUSTOM
 
79
                rightActionsRepeater.itemAt(j).primed = true
 
80
            }
 
81
        } else {
 
82
            main.x = -(actionFullWidth * index)
 
83
 
 
84
            for (j=0; j < rightSideActions.length; j++) {  // CUSTOM
 
85
                rightActionsRepeater.itemAt(j).primed = j === index
 
86
            }
 
87
        }
 
88
    }
 
89
 
 
90
    function returnToBoundsLTR()
 
91
    {
 
92
        var finalX = leftActionWidth
 
93
        if (main.x > (finalX * root.threshold))
 
94
            main.x = finalX
 
95
        else {
 
96
            main.x = 0
 
97
 
 
98
            resetPrimed()  // CUSTOM
 
99
        }
 
100
 
 
101
        if (leftSideAction !== null) {  // CUSTOM
 
102
            leftActionIcon.primed = main.x > (finalX * root.threshold)
 
103
        }
 
104
    }
 
105
 
 
106
    function returnToBounds()
 
107
    {
 
108
        if (main.x < 0) {
 
109
            returnToBoundsRTL()
 
110
        } else if (main.x > 0) {
 
111
            returnToBoundsLTR()
 
112
        } else {  // CUSTOM
 
113
            resetPrimed()  // CUSTOM
 
114
        }
 
115
    }
 
116
 
 
117
    function contains(item, point)
 
118
    {
 
119
        return (point.x >= item.x) && (point.x <= (item.x + item.width)) && (point.y >= item.y) && (point.y <= (item.y + item.height));
 
120
    }
 
121
 
 
122
    function getActionAt(point)
 
123
    {
 
124
        if (contains(leftActionView, point)) {
 
125
            return leftSideAction
 
126
        } else if (contains(rightActionsView, point)) {
 
127
            var newPoint = root.mapToItem(rightActionsView, point.x, point.y)
 
128
            for (var i = 0; i < rightActionsRepeater.count; i++) {
 
129
                var child = rightActionsRepeater.itemAt(i)
 
130
                if (contains(child, newPoint)) {
 
131
                    return i
 
132
                }
 
133
            }
 
134
        }
 
135
        return -1
 
136
    }
 
137
 
 
138
    function updateActiveAction()
 
139
    {
 
140
        if ((main.x <= -root.actionWidth) &&
 
141
            (main.x > -rightActionsView.width)) {
 
142
            var actionFullWidth = actionWidth + units.gu(2)
 
143
            var xOffset = Math.abs(main.x)
 
144
            var index = Math.min(Math.floor(xOffset / actionFullWidth), rightSideActions.length)
 
145
            index = index - 1
 
146
            if (index > -1) {
 
147
                root.activeItem = rightActionsRepeater.itemAt(index)
 
148
                root.activeAction = root.rightSideActions[index]
 
149
            }
 
150
        } else {
 
151
            root.activeAction = null
 
152
        }
 
153
    }
 
154
 
 
155
    function resetPrimed()  // CUSTOM
 
156
    {
 
157
        if (leftSideAction !== null) {
 
158
            leftActionIcon.primed = false
 
159
        }
 
160
 
 
161
        for (var j=0; j < rightSideActions.length; j++) {
 
162
            rightActionsRepeater.itemAt(j).primed = false
 
163
        }
 
164
    }
 
165
 
 
166
    function resetSwipe()
 
167
    {
 
168
        main.x = 0
 
169
 
 
170
        resetPrimed()  // CUSTOM
 
171
    }
 
172
 
 
173
    Connections {  // CUSTOM
 
174
        target: mainView
 
175
        onListItemSwiping: {
 
176
            if (i !== index) {
 
177
                root.resetSwipe();
 
178
            }
 
179
        }
 
180
    }
 
181
 
 
182
    Connections {  // CUSTOM
 
183
        target: root.parent
 
184
        onStateChanged: reordering = root.parent.state === "reorder"
 
185
        onVisibleChanged: {
 
186
            if (!visible) {
 
187
                reordering = false
 
188
            }
 
189
        }
 
190
    }
 
191
 
 
192
    Component.onCompleted: reordering = root.parent.state === "reorder"  // CUSTOM
 
193
 
 
194
    /* CUSTOM Dim Component */
 
195
    Rectangle {
 
196
        id: listItemDim
 
197
        anchors {
 
198
            fill: parent
 
199
        }
 
200
 
 
201
        color: mouseArea.pressed ? styleMusic.common.black : "transparent"
 
202
        opacity: 0.1
 
203
 
 
204
        property bool dim: false
 
205
 
 
206
        Behavior on color {
 
207
            ColorAnimation {
 
208
                duration: UbuntuAnimation.SlowDuration
 
209
            }
 
210
        }
 
211
    }
 
212
 
 
213
    // CUSTOM remove animation
 
214
    SequentialAnimation {
 
215
        id: removeAnimation
 
216
 
 
217
        property var action
 
218
 
 
219
        UbuntuNumberAnimation {
 
220
            target: root
 
221
            duration: UbuntuAnimation.BriskDuration
 
222
            property: "height";
 
223
            to: 0
 
224
        }
 
225
        ScriptAction {
 
226
            script: removeAnimation.action.trigger()
 
227
        }
 
228
    }
 
229
 
 
230
    height: defaultHeight
 
231
    clip: height !== defaultHeight
 
232
 
 
233
    Rectangle {
 
234
        id: leftActionView
 
235
 
 
236
        anchors {
 
237
            top: parent.top
 
238
            bottom: parent.bottom
 
239
            right: main.left
 
240
        }
 
241
        width: root.leftActionWidth + actionThreshold
 
242
        visible: leftSideAction
 
243
        color: "red"
 
244
 
 
245
        Icon {
 
246
            id: leftActionIcon
 
247
            anchors {
 
248
                centerIn: parent
 
249
                horizontalCenterOffset: actionThreshold / 2
 
250
            }
 
251
            objectName: "swipeDeleteAction"  // CUSTOM
 
252
            name: leftSideAction ? leftSideAction.iconName : ""
 
253
            color: Theme.palette.selected.field
 
254
            height: units.gu(3)
 
255
            width: units.gu(3)
 
256
 
 
257
            property bool primed: false  // CUSTOM
 
258
        }
 
259
    }
 
260
 
 
261
    Item {
 
262
       id: rightActionsView
 
263
 
 
264
       anchors {
 
265
           top: main.top
 
266
           left: main.right
 
267
           leftMargin: reordering ? actionReorder.width : units.gu(1)  // CUSTOM
 
268
           bottom: main.bottom
 
269
       }
 
270
       visible: rightSideActions.length > 0
 
271
       width: rightActionsRepeater.count > 0 ? rightActionsRepeater.count * (root.actionWidth + units.gu(2)) + actionThreshold : 0
 
272
 
 
273
       Rectangle {  // CUSTOM
 
274
           anchors {
 
275
               bottom: parent.bottom
 
276
               left: parent.left
 
277
               top: parent.top
 
278
           }
 
279
           color: styleMusic.common.black
 
280
           opacity: 0.7
 
281
           width: parent.width + actionThreshold
 
282
       }
 
283
 
 
284
       Row {
 
285
           anchors {
 
286
               fill: parent
 
287
               leftMargin: units.gu(2)  // CUSTOM
 
288
           }
 
289
           spacing: units.gu(2)
 
290
           Repeater {
 
291
               id: rightActionsRepeater
 
292
 
 
293
               model: rightSideActions
 
294
               Item {
 
295
                   property alias image: img
 
296
 
 
297
                   anchors {
 
298
                       top: parent.top
 
299
                       bottom: parent.bottom
 
300
                   }
 
301
                   width: root.actionWidth
 
302
 
 
303
                   property alias primed: img.primed  // CUSTOM
 
304
 
 
305
                   Icon {
 
306
                       id: img
 
307
 
 
308
                       anchors.centerIn: parent
 
309
                       objectName: rightSideActions[index].objectName  // CUSTOM
 
310
                       width: units.gu(3)
 
311
                       height: units.gu(3)
 
312
                       name: iconName
 
313
                       color: root.activeAction === modelData || !root.triggerActionOnMouseRelease ? UbuntuColors.orange : styleMusic.common.white  // CUSTOM
 
314
 
 
315
                       property bool primed: false  // CUSTOM
 
316
                   }
 
317
               }
 
318
           }
 
319
       }
 
320
    }
 
321
 
 
322
    Rectangle {
 
323
        id: main
 
324
        objectName: "mainItem"
 
325
 
 
326
        anchors {
 
327
            top: parent.top
 
328
            bottom: parent.bottom
 
329
        }
 
330
 
 
331
        width: parent.width
 
332
 
 
333
        Behavior on x {
 
334
            UbuntuNumberAnimation {
 
335
                id: mainItemMoving
 
336
 
 
337
                easing.type: Easing.OutElastic
 
338
                duration: UbuntuAnimation.SlowDuration
 
339
            }
 
340
        }
 
341
    }
 
342
 
 
343
    /* CUSTOM Reorder Component */
 
344
    Rectangle {
 
345
        id: actionReorder
 
346
        anchors {
 
347
            bottom: parent.bottom
 
348
            right: main.right
 
349
            rightMargin: units.gu(1)
 
350
            top: parent.top
 
351
        }
 
352
        color: "transparent"
 
353
        width: units.gu(4)
 
354
        visible: reordering
 
355
 
 
356
        Icon {
 
357
            anchors {
 
358
                horizontalCenter: parent.horizontalCenter
 
359
                verticalCenter: parent.verticalCenter
 
360
            }
 
361
            name: "navigation-menu"  // TODO: use proper image
 
362
            height: width
 
363
            width: units.gu(3)
 
364
        }
 
365
 
 
366
        MouseArea {
 
367
            id: actionReorderMouseArea
 
368
            anchors {
 
369
                fill: parent
 
370
            }
 
371
            property int startY: 0
 
372
            property int startContentY: 0
 
373
 
 
374
            onPressed: {
 
375
                root.parent.parent.interactive = false;  // stop scrolling of listview
 
376
                startY = root.y;
 
377
                startContentY = root.parent.parent.contentY;
 
378
                root.z += 10;  // force ontop of other elements
 
379
 
 
380
                console.debug("Reorder listitem pressed", root.y)
 
381
            }
 
382
            onMouseYChanged: root.y += mouse.y - (root.height / 2);
 
383
            onReleased: {
 
384
                console.debug("Reorder diff by position", getDiff());
 
385
 
 
386
                var diff = getDiff();
 
387
 
 
388
                // Remove the height of the actual item if moved down
 
389
                if (diff > 0) {
 
390
                    diff -= 1;
 
391
                }
 
392
 
 
393
                root.parent.parent.interactive = true;  // reenable scrolling
 
394
 
 
395
                if (diff === 0) {
 
396
                    // Nothing has changed so reset the item
 
397
                    // z index is restored after animation
 
398
                    resetListItemYAnimation.start();
 
399
                }
 
400
                else {
 
401
                    var newIndex = index + diff;
 
402
 
 
403
                    if (newIndex < 0) {
 
404
                        newIndex = 0;
 
405
                    }
 
406
                    else if (newIndex > root.parent.parent.count - 1) {
 
407
                        newIndex = root.parent.parent.count - 1;
 
408
                    }
 
409
 
 
410
                    root.z -= 10;  // restore z index
 
411
                    reorder(index, newIndex)
 
412
                }
 
413
            }
 
414
 
 
415
            function getDiff() {
 
416
                // Get the amount of items that have been passed over (by centre)
 
417
                return Math.round((((root.y - startY) + (root.parent.parent.contentY - startContentY)) / root.height) + 0.5);
 
418
            }
 
419
        }
 
420
 
 
421
        SequentialAnimation {
 
422
            id: resetListItemYAnimation
 
423
            UbuntuNumberAnimation {
 
424
                target: root;
 
425
                property: "y";
 
426
                to: actionReorderMouseArea.startY
 
427
            }
 
428
            ScriptAction {
 
429
                script: {
 
430
                    root.z -= 10;  // restore z index
 
431
                }
 
432
            }
 
433
        }
 
434
    }
 
435
 
 
436
    SequentialAnimation {
 
437
        id: triggerAction
 
438
 
 
439
        property var currentItem: root.activeItem ? root.activeItem.image : null
 
440
 
 
441
        running: false
 
442
        ParallelAnimation {
 
443
            UbuntuNumberAnimation {
 
444
                target: triggerAction.currentItem
 
445
                property: "opacity"
 
446
                from: 1.0
 
447
                to: 0.0
 
448
                duration: UbuntuAnimation.SlowDuration
 
449
                easing {type: Easing.InOutBack; }
 
450
            }
 
451
            UbuntuNumberAnimation {
 
452
                target: triggerAction.currentItem
 
453
                properties: "width, height"
 
454
                from: units.gu(3)
 
455
                to: root.actionWidth
 
456
                duration: UbuntuAnimation.SlowDuration
 
457
                easing {type: Easing.InOutBack; }
 
458
            }
 
459
        }
 
460
        PropertyAction {
 
461
            target: triggerAction.currentItem
 
462
            properties: "width, height"
 
463
            value: units.gu(3)
 
464
        }
 
465
        PropertyAction {
 
466
            target: triggerAction.currentItem
 
467
            properties: "opacity"
 
468
            value: 1.0
 
469
        }
 
470
        ScriptAction {
 
471
            script: root.activeAction.triggered(root)
 
472
        }
 
473
        PauseAnimation {
 
474
            duration: 500
 
475
        }
 
476
        UbuntuNumberAnimation {
 
477
            target: main
 
478
            property: "x"
 
479
            to: 0
 
480
        }
 
481
        ScriptAction {
 
482
            script: resetPrimed()
 
483
        }
 
484
    }
 
485
 
 
486
    MouseArea {
 
487
        id: mouseArea
 
488
 
 
489
        property bool locked: root.locked || ((root.leftSideAction === null) && (root.rightSideActions.count === 0)) || reordering  // CUSTOM
 
490
        property bool manual: false
 
491
 
 
492
        anchors.fill: parent
 
493
        drag {
 
494
            target: locked ? null : main
 
495
            axis: Drag.XAxis
 
496
            minimumX: rightActionsView.visible ? -(rightActionsView.width + root.actionThreshold) : 0
 
497
            maximumX: leftActionView.visible ? leftActionView.width : 0
 
498
        }
 
499
 
 
500
        onReleased: {
 
501
            if (root.triggerActionOnMouseRelease && root.activeAction) {
 
502
                triggerAction.start()
 
503
            } else {
 
504
                root.returnToBounds()
 
505
                root.activeAction = null
 
506
            }
 
507
        }
 
508
        onClicked: {
 
509
            if (reordering) {  // CUSTOM
 
510
                reordering = false
 
511
            }
 
512
            else if (main.x === 0) {
 
513
                root.itemClicked(mouse)
 
514
            } else if (main.x > 0) {
 
515
                var action = getActionAt(Qt.point(mouse.x, mouse.y))
 
516
                if (action && action !== -1) {
 
517
                    //action.triggered(root)
 
518
                    removeAnimation.action = action  // CUSTOM
 
519
                    removeAnimation.start()  // CUSTOM
 
520
                }
 
521
            } else {
 
522
                var actionIndex = getActionAt(Qt.point(mouse.x, mouse.y))
 
523
                if (actionIndex !== -1) {
 
524
                    root.activeItem = rightActionsRepeater.itemAt(actionIndex)
 
525
                    root.activeAction = root.rightSideActions[actionIndex]
 
526
                    triggerAction.start()
 
527
                    return
 
528
                }
 
529
            }
 
530
            root.resetSwipe()
 
531
        }
 
532
 
 
533
        onPositionChanged: {
 
534
            if (mouseArea.pressed) {
 
535
                updateActiveAction()
 
536
 
 
537
                listItemSwiping(index)  // CUSTOM
 
538
            }
 
539
        }
 
540
        onPressAndHold: {
 
541
            if (main.x === 0) {
 
542
                root.itemPressAndHold(mouse)
 
543
            }
 
544
        }
 
545
        z: -1
 
546
    }
 
547
}