Merge lp:~mcintire-evan/music-app/songs-fastscroll into lp:music-app

Proposed by Evan McIntire on 2016-01-02
Status: Needs review
Proposed branch: lp:~mcintire-evan/music-app/songs-fastscroll
Merge into: lp:music-app
Diff against target: 550 lines (+486/-1)
5 files modified
AUTHORS (+1/-0)
app/components/FastScroll.qml (+316/-0)
app/logic/FastScroll.js (+131/-0)
app/ui/Songs.qml (+35/-1)
debian/changelog (+3/-0)
To merge this branch: bzr merge lp:~mcintire-evan/music-app/songs-fastscroll
Reviewer Review Type Date Requested Status
Jenkins Bot continuous-integration Needs Fixing on 2016-01-29
Andrew Hayzen Needs Fixing on 2016-01-02
Victor Thompson 2016-01-02 Needs Fixing on 2016-01-02
Review via email: mp+281471@code.launchpad.net

Commit message

Add fastscroll to the song list

Description of the change

Add fastscroll to the song list

To post a comment you must log in.
Victor Thompson (vthompson) wrote :

The code for this looks good! I'm not sure however, that we'll want to merge this into the app. The Ubuntu SDK will be introducing scrollbars sometime soon and I think it'd be better to use that feature/component as we'll also want to use it in the Queue and other listviews.

Codewise, I do have 1 inline comment.

review: Needs Fixing
Evan McIntire (mcintire-evan) wrote :

Thanks for the review, you are right with the ListItems comment.

As for actual merging it, ahayzen did tell me that it probably wouldn't be merged, but getting a working example with it was still a good idea, so I understand

959. By Evan McIntire on 2016-01-02

Use lastest ListItem component; added DCH and AUTHORS entry

Andrew Hayzen (ahayzen) wrote :

Awesome, thanks for doing this.

I have on minor inline comment that assists with the styling after moving to the new ListItem component. I also agree with Victor that we'll likely wait for the new SDK scrollbars to see which is best, or a mixture of both. As when testing this you can get it into states where the header covers the letters on the right side, and on smaller screens (eg when in landscape) the fastscroll component does not fit in the vertical space.

1) This label should probably have
anchors {
    leftMargin: units.gu(1)
    verticalCenter: parent.verticalCenter
}
so that it is vertically centred and not against the left edge

review: Needs Fixing
Evan McIntire (mcintire-evan) wrote :

Sorry for the late reply, school got in the way

I've tried applying those anchors to the label, but the leftMargin doesnt seem to do anything, here are some screenshots

http://i.imgur.com/DIEMOdf.png is with the anchors

http://i.imgur.com/fazBT7K.png is without

It seems the verticalCenter works, but leftMargin does not, even if I up the units.gu unit

Andrew Hayzen (ahayzen) wrote :

Try using

anchors {
  left: parent.left
  leftMargin: units.gu(1)
  right: parent.right
  rightMargin: units.gu(1)
  verticalCenter: parent.verticalCenter
}

Its probably because it doesn't have anything to bind to on the left side, so then just uses the x value.
By then setting left: parent.left that should then let you use leftMargin.

960. By Evan McIntire on 2016-01-04

Improve formatting of catagory headers

Evan McIntire (mcintire-evan) wrote :

Formatting is fixed, but I noticed a bug

If you longpress, you are able to select the category headers the same way you would select a song

http://i.imgur.com/sRFgZin.png is a screenshot of that

FAILED: Continuous integration, rev:960
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/~mcintire-evan/music-app/songs-fastscroll/+merge/281471/+edit-commit-message

https://core-apps-jenkins.ubuntu.com/job/run-ap-tests-ci/216/
Executed test runs:
    None: https://core-apps-jenkins.ubuntu.com/job/generic-update-mp/502/console

Click here to trigger a rebuild:
https://core-apps-jenkins.ubuntu.com/job/run-ap-tests-ci/216/rebuild

review: Needs Fixing (continuous-integration)

Unmerged revisions

960. By Evan McIntire on 2016-01-04

Improve formatting of catagory headers

959. By Evan McIntire on 2016-01-02

Use lastest ListItem component; added DCH and AUTHORS entry

958. By Evan McIntire on 2016-01-02

Add FastScroll to the song list

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'AUTHORS'
2--- AUTHORS 2015-09-08 08:12:31 +0000
3+++ AUTHORS 2016-01-04 22:33:32 +0000
4@@ -16,6 +16,7 @@
5 Darran Kelinske <darran.kelinske@gmail.com>
6 David Planella <david.planella@ubuntu.com>
7 Dimitri John Ledkov <dimitri.ledkov@canonical.com>
8+Evan McIntire <mcintire.evan@gmail.com>
9 Francis Ginther <francis.ginther@canonical.com>
10 Guenter Schwann <guenter.schwann@canonical.com>
11 Jamie Strandboge <jamie@ubuntu.com>
12
13=== added file 'app/components/FastScroll.qml'
14--- app/components/FastScroll.qml 1970-01-01 00:00:00 +0000
15+++ app/components/FastScroll.qml 2016-01-04 22:33:32 +0000
16@@ -0,0 +1,316 @@
17+/****************************************************************************
18+**
19+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
20+** Copyright (C) 2014 Canonical Ltda
21+** All rights reserved.
22+** Contact: Nokia Corporation (qt-info@nokia.com)
23+**
24+** This file is part of the Qt Components project.
25+**
26+** $QT_BEGIN_LICENSE:BSD$
27+** You may use this file under the terms of the BSD license as follows:
28+**
29+** "Redistribution and use in source and binary forms, with or without
30+** modification, are permitted provided that the following conditions are
31+** met:
32+** * Redistributions of source code must retain the above copyright
33+** notice, this list of conditions and the following disclaimer.
34+** * Redistributions in binary form must reproduce the above copyright
35+** notice, this list of conditions and the following disclaimer in
36+** the documentation and/or other materials provided with the
37+** distribution.
38+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
39+** the names of its contributors may be used to endorse or promote
40+** products derived from this software without specific prior written
41+** permission.
42+**
43+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
44+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
45+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
46+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
47+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
48+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
49+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
50+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
51+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
52+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
53+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
54+** $QT_END_LICENSE$
55+**
56+****************************************************************************/
57+
58+// FastScroll.qml
59+import QtQuick 2.4
60+import Ubuntu.Components 1.3
61+import "../logic/FastScroll.js" as Sections
62+
63+Item {
64+ id: root
65+
66+ property ListView listView
67+ property int pinSize: units.gu(2)
68+
69+ readonly property var letters: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"]
70+ readonly property alias fastScrolling: internal.fastScrolling
71+ readonly property bool showing: (rail.opacity !== 0.0)
72+ readonly property double minimumHeight: rail.height
73+
74+ width: units.gu(7)
75+ height: rail.height
76+ visible: enabled
77+
78+ onListViewChanged: {
79+ if (listView && listView.model) {
80+ internal.initDirtyObserver();
81+ } else if (listView) {
82+ listView.modelChanged.connect(function() {
83+ if (listView.model) {
84+ internal.initDirtyObserver();
85+ }
86+ });
87+ }
88+ }
89+
90+
91+ Rectangle {
92+ id: magnified
93+
94+ color: Theme.palette.normal.foreground
95+ radius: height * 0.3
96+ height: pinSize * 2
97+ width: height
98+ opacity: internal.fastScrolling && root.enabled ? 1.0 : 0.0
99+ x: -cursor.width - units.gu(3)
100+ y: {
101+ if (internal.currentItem) {
102+ var itemCenterY = rail.y + internal.currentItem.y + (internal.currentItem.height / 2)
103+ return (itemCenterY - (magnified.height / 2))
104+ } else {
105+ return 0
106+ }
107+ }
108+
109+ Label {
110+ color: Theme.palette.normal.foregroundText
111+ anchors.fill: parent
112+ horizontalAlignment: Text.AlignHCenter
113+ verticalAlignment: Text.AlignVCenter
114+ text: internal.desireSection
115+ fontSize: "small"
116+ }
117+
118+ Behavior on opacity {
119+ UbuntuNumberAnimation {}
120+ }
121+ }
122+
123+ Rectangle {
124+ id: cursor
125+
126+ property bool showLabel: false
127+ property string currentSectionName: ""
128+
129+ radius: pinSize * 0.3
130+ height: pinSize
131+ width: height
132+ color: Theme.palette.normal.foreground
133+ opacity: rail.opacity
134+ x: rail.x
135+ y: {
136+ if (internal.currentItem) {
137+ var itemCenterY = rail.y + internal.currentItem.y + (internal.currentItem.height / 2)
138+ return (itemCenterY - (cursor.height / 2))
139+ } else {
140+ return 0
141+ }
142+ }
143+ Behavior on y {
144+ enabled: !internal.fastScrolling
145+ UbuntuNumberAnimation { }
146+ }
147+ }
148+
149+ Column {
150+ id: rail
151+
152+ property bool isVisible: root.enabled &&
153+ (listView.flicking || dragArea.pressed)
154+ anchors {
155+ right: parent.right
156+ rightMargin: units.gu(2)
157+ left: parent.left
158+ leftMargin: units.gu(2)
159+ top: parent.top
160+ }
161+ height: childrenRect.height
162+ opacity: 0.0
163+ onIsVisibleChanged: {
164+ if (isVisible) {
165+ rail.opacity = 1.0
166+ hideTimer.stop()
167+ } else if (!root.enabled) {
168+ rail.opacity = 0.0
169+ } else {
170+ hideTimer.restart()
171+ }
172+ }
173+
174+ Behavior on opacity {
175+ UbuntuNumberAnimation { }
176+ }
177+
178+ Repeater {
179+ id: sectionsRepeater
180+
181+ model: root.letters
182+ Label {
183+ id: lbl
184+
185+ anchors.left: parent.left
186+ height: pinSize
187+ width: pinSize
188+ verticalAlignment: Text.AlignVCenter
189+ horizontalAlignment: Text.AlignHCenter
190+ text: modelData
191+ fontSize: "x-small"
192+ color: cursor.y === y ? Theme.palette.normal.foregroundText : Theme.palette.selected.backgroundText
193+ opacity: !internal.modelDirty && Sections.contains(text) ? 1.0 : 0.5
194+ }
195+ }
196+
197+ Timer {
198+ id: hideTimer
199+
200+ running: false
201+ interval: 2000
202+ onTriggered: rail.opacity = 0.0
203+ }
204+ }
205+
206+ MouseArea {
207+ id: dragArea
208+
209+ anchors {
210+ left: parent.left
211+ right: parent.right
212+ }
213+ y: rail.y
214+ height: rail.height
215+ visible: rail.opacity == 1.0
216+
217+ preventStealing: true
218+ onPressed: {
219+ internal.adjustContentPosition(mouseY)
220+ dragginTimer.start()
221+ }
222+
223+ onReleased: {
224+ dragginTimer.stop()
225+ internal.desireSection = ""
226+ internal.fastScrolling = false
227+ }
228+
229+ onPositionChanged: internal.adjustContentPosition(mouseY)
230+
231+ Timer {
232+ id: dragginTimer
233+
234+ running: false
235+ interval: 150
236+ onTriggered: {
237+ internal.fastScrolling = true
238+ }
239+ }
240+ }
241+
242+ Timer {
243+ id: dirtyTimer
244+ interval: 500
245+ running: false
246+ onTriggered: {
247+ Sections.initSectionData(listView);
248+ internal.modelDirty = false;
249+ }
250+ }
251+
252+ Timer {
253+ id: timerScroll
254+
255+ running: false
256+ interval: 10
257+ onTriggered: {
258+ if (internal.desireSection != internal.currentSection) {
259+ var idx = Sections.getIndexFor(internal.desireSection)
260+ if (idx !== -1) {
261+ listView.cancelFlick()
262+ listView.positionViewAtIndex(idx, ListView.Beginning)
263+ }
264+ }
265+ }
266+ }
267+
268+ QtObject {
269+ id: internal
270+
271+ property string currentSection: listView.currentSection
272+ property string desireSection: ""
273+ property string targetSection: fastScrolling ? desireSection : currentSection
274+ property int oldY: 0
275+ property bool modelDirty: false
276+ property bool down: true
277+ property bool fastScrolling: false
278+ property var currentItem: null
279+
280+ onTargetSectionChanged: moveIndicator(targetSection)
281+
282+ function initDirtyObserver() {
283+ Sections.initialize(listView);
284+ function dirtyObserver() {
285+ if (!internal.modelDirty) {
286+ internal.modelDirty = true;
287+ dirtyTimer.running = true;
288+ }
289+ }
290+
291+ if (listView.model.countChanged)
292+ listView.model.countChanged.connect(dirtyObserver);
293+
294+ if (listView.model.itemsChanged)
295+ listView.model.itemsChanged.connect(dirtyObserver);
296+
297+ if (listView.model.itemsInserted)
298+ listView.model.itemsInserted.connect(dirtyObserver);
299+
300+ if (listView.model.itemsMoved)
301+ listView.model.itemsMoved.connect(dirtyObserver);
302+
303+ if (listView.model.itemsRemoved)
304+ listView.model.itemsRemoved.connect(dirtyObserver);
305+ }
306+
307+ function adjustContentPosition(mouseY) {
308+ var child = rail.childAt(rail.width / 2, mouseY)
309+ if (!child || child.text === "") {
310+ return
311+ }
312+ var section = child.text
313+ if (internal.desireSection !== section) {
314+ internal.desireSection = section
315+ moveIndicator(section)
316+ if (dragArea.pressed) {
317+ timerScroll.restart()
318+ }
319+ }
320+ }
321+
322+ function moveIndicator(section)
323+ {
324+ var index = root.letters.indexOf(section)
325+ if (index != -1) {
326+ currentItem = sectionsRepeater.itemAt(index)
327+ }
328+ }
329+ }
330+}
331+
332+
333
334=== added file 'app/logic/FastScroll.js'
335--- app/logic/FastScroll.js 1970-01-01 00:00:00 +0000
336+++ app/logic/FastScroll.js 2016-01-04 22:33:32 +0000
337@@ -0,0 +1,131 @@
338+/****************************************************************************
339+**
340+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
341+** Copyright (C) 2014 Canonical Ltda
342+** All rights reserved.
343+** Contact: Nokia Corporation (qt-info@nokia.com)
344+**
345+** This file is part of the Qt Components project.
346+**
347+** $QT_BEGIN_LICENSE:BSD$
348+** You may use this file under the terms of the BSD license as follows:
349+**
350+** "Redistribution and use in source and binary forms, with or without
351+** modification, are permitted provided that the following conditions are
352+** met:
353+** * Redistributions of source code must retain the above copyright
354+** notice, this list of conditions and the following disclaimer.
355+** * Redistributions in binary form must reproduce the above copyright
356+** notice, this list of conditions and the following disclaimer in
357+** the documentation and/or other materials provided with the
358+** distribution.
359+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
360+** the names of its contributors may be used to endorse or promote
361+** products derived from this software without specific prior written
362+** permission.
363+**
364+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
365+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
366+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
367+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
368+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
369+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
370+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
371+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
372+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
373+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
374+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
375+** $QT_END_LICENSE$
376+**
377+****************************************************************************/
378+
379+// FastScroll.js - this is just SectionScroller.js with a fix for
380+// section.criteria == ViewSection.FirstCharacter
381+var sectionData = [];
382+var _sections = [];
383+
384+function initialize(list) {
385+ initSectionData(list);
386+}
387+
388+function contains(name) {
389+ return (_sections.indexOf(name) > -1)
390+}
391+
392+function initSectionData(list) {
393+ if (!list || !list.model) return;
394+ sectionData = [];
395+ _sections = [];
396+ var current = "",
397+ prop = list.section.property,
398+ sectionText;
399+
400+ if (list.section.criteria === ViewSection.FullString) {
401+ for (var i = 0, count = list.model.count; i < count; i++) {
402+ sectionText = list.getSectionText(i)
403+ if (sectionText !== current) {
404+ current = sectionText;
405+ _sections.push(current);
406+ sectionData.push({ index: i, header: current });
407+ }
408+ }
409+ } else if (list.section.criteria === ViewSection.FirstCharacter) {
410+ for (var i = 0, count = list.model.count; i < count; i++) {
411+ sectionText = list.getSectionText(i).substring(0, 1)
412+ if (sectionText !== current) {
413+ current = sectionText
414+ _sections.push(sectionText);
415+ sectionData.push({ index: i, header: current });
416+ }
417+ }
418+ }
419+}
420+
421+function getSectionPositionString(name) {
422+ var val = _sections.indexOf(name);
423+ return val === 0 ? "first" :
424+ val === _sections.length - 1 ? "last" : false;
425+}
426+
427+function getAt(pos) {
428+ return _sections[pos] ? _sections[pos] : "";
429+}
430+
431+function getRelativeSections(current) {
432+ var val = _sections.indexOf(current),
433+ sect = [],
434+ sl = _sections.length;
435+
436+ val = val < 1 ? 1 : val >= sl-1 ? sl-2 : val;
437+ sect = [getAt(val - 1), getAt(val), getAt(val + 1)];
438+
439+ return sect;
440+}
441+
442+function getClosestSection(pos, down) {
443+ var tmp = (_sections.length) * pos;
444+ var val = Math.ceil(tmp) // TODO: better algorithm
445+ val = val < 2 ? 1 : val;
446+ return _sections[val-1];
447+}
448+
449+function getNextSection(current) {
450+ var val = _sections.indexOf(current);
451+ return (val > -1 ? _sections[(val < _sections.length - 1 ? val + 1 : val)] : _sections[0]) || "";
452+}
453+
454+function getPreviousSection(current) {
455+ var val = _sections.indexOf(current);
456+ return (val > -1 ? _sections[(val > 0 ? val - 1 : val)] : _sections[0]) || "";
457+}
458+
459+function getIndexFor(sectionName) {
460+ var data = sectionData[_sections.indexOf(sectionName)]
461+ if (data) {
462+ var val = data.index;
463+ return val === 0 || val > 0 ? val : -1;
464+ } else {
465+ return -1
466+ }
467+}
468+
469
470=== modified file 'app/ui/Songs.qml'
471--- app/ui/Songs.qml 2015-10-18 18:16:05 +0000
472+++ app/ui/Songs.qml 2016-01-04 22:33:32 +0000
473@@ -30,7 +30,6 @@
474 import "../components/HeadState"
475 import "../components/ListItemActions"
476
477-
478 MusicPage {
479 id: songsPage
480 objectName: "songsPage"
481@@ -64,7 +63,13 @@
482 bottomMargin: units.gu(2)
483 fill: parent
484 topMargin: units.gu(2)
485+ rightMargin: fastScroll.showing ? fastScroll.width - units.gu(1)
486+ : 0
487 }
488+
489+ clip: true
490+ currentIndex: -1
491+
492 objectName: "trackstab-listview"
493 model: SortFilterModel {
494 id: songsModelFilter
495@@ -90,6 +95,25 @@
496 }
497 }
498
499+ section.property: "title"
500+ section.criteria: ViewSection.FirstCharacter
501+ section.delegate: ListItem {
502+ Label {
503+ text: section
504+ anchors {
505+ left: parent.left
506+ right: parent.right
507+ leftMargin: units.gu(1)
508+ verticalCenter: parent.verticalCenter
509+
510+ }
511+ }
512+ }
513+
514+ function getSectionText(index) {
515+ return model.get(index).title.substring(0,1)
516+ }
517+
518 delegate: MusicListItem {
519 id: track
520 objectName: "tracksPageListItem" + index
521@@ -126,5 +150,15 @@
522 }
523 }
524 }
525+
526+ FastScroll {
527+ id: fastScroll
528+
529+ listView: tracklist
530+ anchors {
531+ right: parent.right
532+ verticalCenter: tracklist.verticalCenter
533+ }
534+ }
535 }
536
537
538=== modified file 'debian/changelog'
539--- debian/changelog 2015-12-17 20:43:26 +0000
540+++ debian/changelog 2016-01-04 22:33:32 +0000
541@@ -9,6 +9,9 @@
542 [ Girish Rawat ]
543 * Expanded and updated READMEs
544
545+ [ Evan McIntire ]
546+ * Added FastScroll to song list (LP: #1342987)
547+
548 -- Andrew Hayzen <ahayzen@gmail.com> Thu, 03 Dec 2015 14:11:35 +0000
549
550 music-app (2.2ubuntu2) vivid; urgency=medium

Subscribers

People subscribed via source and target branches