on xenial help sign in
Introduce the Audio card type
2258785Albert Astals Cid
plugins/Dash/CMakeLists.txt+4
@@ -40,3 +40,7 @@ target_link_libraries(Dash-qml
40qt5_use_modules(Dash-qml Qml Quick Concurrent)
40qt5_use_modules(Dash-qml Qml Quick Concurrent)
41
41
42add_unity8_plugin(Dash 0.1 Dash TARGETS Dash-qml)
42add_unity8_plugin(Dash 0.1 Dash TARGETS Dash-qml)
43+
44+install(FILES graphics/music_progress_bg.png DESTINATION ${SHELL_INSTALL_QML}/graphics/)
45+file(COPY graphics/music_progress_bg.png DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/graphics/)
46+
plugins/Dash/CardAudioProgress.qmlnew file+59
@@ -0,0 +1,59 @@
1+/*
2+ * Copyright 2015 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.4
18+import Ubuntu.Components 1.3
19+import Dash 0.1
20+
21+Item {
22+ id: root
23+
24+ implicitHeight: positionLabel.y + positionLabel.height
25+
26+ property int duration: 0
27+ property url source
28+ property color color: Theme.palette.normal.baseText
29+ readonly property int position: DashAudioPlayer.isCurrentSource(source) ? DashAudioPlayer.position / 1000 : 0
30+
31+ AudioProgressBar {
32+ id: progress
33+ anchors { left: parent.left; right: parent.right }
34+ source: root.source
35+ }
36+
37+ Label {
38+ id: positionLabel
39+ anchors {
40+ left: parent.left
41+ top: progress.bottom
42+ }
43+ verticalAlignment: Text.AlignBottom
44+ fontSize: "x-small"
45+ text: DashAudioPlayer.lengthToString(root.position)
46+ color: root.color
47+ }
48+
49+ Label {
50+ anchors {
51+ right: parent.right
52+ top: progress.bottom
53+ }
54+ verticalAlignment: Text.AlignBottom
55+ fontSize: "x-small"
56+ text: DashAudioPlayer.lengthToString(duration)
57+ color: root.color
58+ }
59+}
plugins/Dash/CardCreator.js+103−1
@@ -96,6 +96,40 @@ var kArtShapeHolderCode = 'Item { \n\
96 } \n\
96 } \n\
97 }\n';
97 }\n';
98
98
99+// %1 is anchors.fill
100+// %2 is width
101+// %3 is height
102+var kAudioButtonCode = 'AbstractButton { \n\
103+ id: audioButton; \n\
104+ anchors.fill: %1; \n\
105+ width: %2; \n\
106+ height: %3; \n\
107+ readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || ""; \n\
108+ UbuntuShape { \n\
109+ anchors.fill: parent; \n\
110+ visible: parent.pressed; \n\
111+ radius: "medium"; \n\
112+ borderSource: "radius_pressed.sci" \n\
113+ } \n\
114+ Icon { \n\
115+ anchors.fill: parent; \n\
116+ anchors.margins: parent.height > units.gu(5) ? units.gu(2) : units.gu(0); \n\
117+ opacity: 0.9; \n\
118+ name: DashAudioPlayer.isCurrentSource(parent.source) && DashAudioPlayer.playing ? "media-playback-pause" : "media-playback-start"; \n\
119+ } \n\
120+ onClicked: { \n\
121+ if (DashAudioPlayer.isCurrentSource(source)) { \n\
122+ if (DashAudioPlayer.playing) { \n\
123+ DashAudioPlayer.pause(); \n\
124+ } else { \n\
125+ DashAudioPlayer.play(); \n\
126+ } \n\
127+ } else { \n\
128+ DashAudioPlayer.playSource(source); \n\
129+ } \n\
130+ } \n\
131+ }';
132+
99var kOverlayLoaderCode = 'Loader { \n\
133var kOverlayLoaderCode = 'Loader { \n\
100 id: overlayLoader; \n\
134 id: overlayLoader; \n\
101 anchors { \n\
135 anchors { \n\
@@ -300,6 +334,22 @@ var kSummaryLabelCode = 'Label { \n\
300 color: %3; \n\
334 color: %3; \n\
301 }\n';
335 }\n';
302
336
337+// %1 is used as bottom anchor of audio progress bar
338+// %2 is used as left anchor of audio progress bar
339+// %3 is used as text color
340+var kAudioProgressBarCode = 'CardAudioProgress { \n\
341+ id: audioProgressBar; \n\
342+ duration: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["duration"]) || 0; \n\
343+ source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || ""; \n\
344+ anchors { \n\
345+ bottom: %1; \n\
346+ left: %2; \n\
347+ right: parent.right; \n\
348+ margins: units.gu(1); \n\
349+ } \n\
350+ color: %3; \n\
351+ }';
352+
303function cardString(template, components) {
353function cardString(template, components) {
304 var code;
354 var code;
305 code = 'AbstractButton { \n\
355 code = 'AbstractButton { \n\
@@ -333,6 +383,19 @@ function cardString(template, components) {
333 var hasSubtitle = hasTitle && components["subtitle"] || false;
383 var hasSubtitle = hasTitle && components["subtitle"] || false;
334 var hasHeaderRow = hasMascot && hasTitle;
384 var hasHeaderRow = hasMascot && hasTitle;
335 var hasAttributes = hasTitle && components["attributes"]["field"] || false;
385 var hasAttributes = hasTitle && components["attributes"]["field"] || false;
386+ var isAudio = template["quickPreviewType"] === "audio";
387+
388+ if (isAudio) {
389+ // For now we only support audio cards with [optional] art, title, subtitle
390+ // in horizontal mode
391+ // Anything else makes it behave not like an audio card
392+ if (hasSummary) isAudio = false;
393+ if (!isHorizontal) isAudio = false;
394+ if (hasMascot) isAudio = false;
395+ if (hasEmblem) isAudio = false;
396+ if (headerAsOverlay) isAudio = false;
397+ if (hasAttributes) isAudio = false;
398+ }
336
399
337 if (hasBackground) {
400 if (hasBackground) {
338 code += kBackgroundLoaderCode;
401 code += kBackgroundLoaderCode;
@@ -388,17 +451,22 @@ function cardString(template, components) {
388 topMargin: units.gu(1);\n';
451 topMargin: units.gu(1);\n';
389 }
452 }
390 }
453 }
454+
391 var headerLeftAnchor;
455 var headerLeftAnchor;
392 var headerLeftAnchorHasMargin = false;
456 var headerLeftAnchorHasMargin = false;
393 if (isHorizontal && hasArt) {
457 if (isHorizontal && hasArt) {
394 headerLeftAnchor = 'left: artShapeHolder.right; \n\
458 headerLeftAnchor = 'left: artShapeHolder.right; \n\
395 leftMargin: units.gu(1);\n';
459 leftMargin: units.gu(1);\n';
396 headerLeftAnchorHasMargin = true;
460 headerLeftAnchorHasMargin = true;
461+ } else if (isHorizontal && isAudio) {
462+ headerLeftAnchor = 'left: audioButton.right; \n\
463+ leftMargin: units.gu(1);\n';
464+ headerLeftAnchorHasMargin = true;
397 } else {
465 } else {
398 headerLeftAnchor = 'left: parent.left;\n';
466 headerLeftAnchor = 'left: parent.left;\n';
399 }
467 }
400
468
401− var touchdownOnArtShape = !hasBackground && hasArt && !hasMascot && !hasSummary;
469+ var touchdownOnArtShape = !hasBackground && hasArt && !hasMascot && !hasSummary && !isAudio;
402
470
403 if (hasHeaderRow) {
471 if (hasHeaderRow) {
404 code += 'readonly property int headerHeight: row.height;\n'
472 code += 'readonly property int headerHeight: row.height;\n'
@@ -412,6 +480,14 @@ function cardString(template, components) {
412 } else {
480 } else {
413 code += 'readonly property int headerHeight: attributesRow.height;\n'
481 code += 'readonly property int headerHeight: attributesRow.height;\n'
414 }
482 }
483+ } else if (isAudio) {
484+ if (hasSubtitle) {
485+ code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + audioProgressBar.height + audioProgressBar.anchors.topMargin;\n'
486+ } else if (hasTitle) {
487+ code += 'readonly property int headerHeight: titleLabel.height + audioProgressBar.height + audioProgressBar.anchors.topMargin;\n'
488+ } else {
489+ code += 'readonly property int headerHeight: audioProgressBar.height;\n'
490+ }
415 } else if (hasSubtitle) {
491 } else if (hasSubtitle) {
416 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
492 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
417 } else if (hasTitle) {
493 } else if (hasTitle) {
@@ -593,6 +669,30 @@ function cardString(template, components) {
593 code += mascotShapeCode + mascotCode + titleSubtitleCode;
669 code += mascotShapeCode + mascotCode + titleSubtitleCode;
594 }
670 }
595
671
672+ if (isAudio) {
673+ var audioProgressBarLeftAnchor = 'audioButton.right';
674+ var audioProgressBarBottomAnchor = 'audioButton.bottom';
675+ var audioProgressBarTextColor = 'root.scopeStyle ? root.scopeStyle.foreground : Theme.palette.normal.baseText';
676+
677+ code += kAudioProgressBarCode.arg(audioProgressBarBottomAnchor)
678+ .arg(audioProgressBarLeftAnchor)
679+ .arg(audioProgressBarTextColor);
680+
681+ var audioButtonAnchorsFill;
682+ var audioButtonWidth;
683+ var audioButtonHeight;
684+ if (hasArt) {
685+ audioButtonAnchorsFill = 'artShapeHolder';
686+ audioButtonWidth = 'undefined';
687+ audioButtonHeight = 'undefined';
688+ } else {
689+ audioButtonAnchorsFill = 'undefined';
690+ audioButtonWidth = 'height';
691+ audioButtonHeight = '(root.fixedHeaderHeight > 0 ? root.fixedHeaderHeight : headerHeight) + 2 * units.gu(1)';
692+ }
693+ code += kAudioButtonCode.arg(audioButtonAnchorsFill).arg(audioButtonWidth).arg(audioButtonHeight);
694+ }
695+
596 if (hasSummary) {
696 if (hasSummary) {
597 var summaryTopAnchor;
697 var summaryTopAnchor;
598 if (isHorizontal && hasArt) summaryTopAnchor = 'artShapeHolder.bottom';
698 if (isHorizontal && hasArt) summaryTopAnchor = 'artShapeHolder.bottom';
@@ -631,6 +731,8 @@ function cardString(template, components) {
631 var implicitHeight = 'implicitHeight: ';
731 var implicitHeight = 'implicitHeight: ';
632 if (hasSummary) {
732 if (hasSummary) {
633 implicitHeight += 'summary.y + summary.height + units.gu(1);\n';
733 implicitHeight += 'summary.y + summary.height + units.gu(1);\n';
734+ } else if (isAudio) {
735+ implicitHeight += 'audioButton.height;\n';
634 } else if (hasHeaderRow) {
736 } else if (hasHeaderRow) {
635 implicitHeight += 'row.y + row.height + units.gu(1);\n';
737 implicitHeight += 'row.y + row.height + units.gu(1);\n';
636 } else if (hasMascot) {
738 } else if (hasMascot) {
qml/Dash/DashAudioPlayer/DashAudioPlayer.qml → plugins/Dash/DashAudioPlayer.qmlrenamed+13−2
@@ -24,8 +24,19 @@ QtObject {
24 readonly property bool playing: audio.playbackState === Audio.PlayingState
24 readonly property bool playing: audio.playbackState === Audio.PlayingState
25 readonly property bool paused: audio.playbackState === Audio.PausedState
25 readonly property bool paused: audio.playbackState === Audio.PausedState
26 readonly property bool stopped: audio.playbackState === Audio.StoppedState
26 readonly property bool stopped: audio.playbackState === Audio.StoppedState
27+ readonly property alias position: audio.position
27
28
29+ function isCurrentSource(source) {
28− property alias source: audio.source
30+ return source === audio.source && source != "";
31+ }
32+
33+ function playSource(newSource) {
34+ stop();
35+ // Make sure we change the source, even if two items point to the same uri location
36+ audio.source = "";
37+ audio.source = newSource;
38+ play();
39+ }
29
40
30 function stop() {
41 function stop() {
31 audio.stop();
42 audio.stop();
@@ -47,7 +58,7 @@ QtObject {
47 }
58 }
48
59
49 function lengthToString(s) {
60 function lengthToString(s) {
50− if (typeof(s) !== "number" || s <= 0) return "";
61+ if (typeof(s) !== "number" || s < 0) return "";
51
62
52 var sec = "" + s % 60;
63 var sec = "" + s % 60;
53 if (sec.length == 1) sec = "0" + sec;
64 if (sec.length == 1) sec = "0" + sec;
plugins/Dash/graphics/music_progress_bg.pngnew file

Binary file not shown.

plugins/Dash/qmldir+2
@@ -2,6 +2,8 @@ module Dash
2plugin Dash-qml
2plugin Dash-qml
3typeinfo Dash.qmltypes
3typeinfo Dash.qmltypes
4singleton CardCreatorCache 0.1 CardCreatorCache.qml
4singleton CardCreatorCache 0.1 CardCreatorCache.qml
5+singleton DashAudioPlayer 0.1 DashAudioPlayer.qml
5ScopeStyle 0.1 ScopeStyle.qml
6ScopeStyle 0.1 ScopeStyle.qml
6CardAttributes 0.1 CardAttributes.qml
7CardAttributes 0.1 CardAttributes.qml
7CroppedImageMinimumSourceSize 0.1 CroppedImageMinimumSourceSize.qml
8CroppedImageMinimumSourceSize 0.1 CroppedImageMinimumSourceSize.qml
9+AudioProgressBar 0.1 AudioProgressBar.qml
qml/Dash/DashAudioPlayer/qmldirdeleted−1
@@ -1 +0,0 @@
1−singleton DashAudioPlayer 1.0 DashAudioPlayer.qml
qml/Dash/Previews/PreviewAudioPlayback.qml+5−33
@@ -16,7 +16,7 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19−import "../DashAudioPlayer"
19+import Dash 0.1
20
20
21/*! \brief Preview widget for audio tracks.
21/*! \brief Preview widget for audio tracks.
22
22
@@ -45,20 +45,12 @@ PreviewWidget {
45 objectName: "trackRepeater"
45 objectName: "trackRepeater"
46 model: root.widgetData["tracks"]
46 model: root.widgetData["tracks"]
47
47
48− function play(item, source) {
49− DashAudioPlayer.stop();
50− // Make sure we change the source, even if two items point to the same uri location
51− DashAudioPlayer.source = "";
52− DashAudioPlayer.source = source;
53− DashAudioPlayer.play();
54− }
55−
56 delegate: Item {
48 delegate: Item {
57 id: trackItem
49 id: trackItem
58 objectName: "trackItem" + index
50 objectName: "trackItem" + index
59
51
60 readonly property url sourceUrl: modelData["source"]
52 readonly property url sourceUrl: modelData["source"]
61− readonly property bool isPlayingItem: DashAudioPlayer.source == sourceUrl
53+ readonly property bool isPlayingItem: DashAudioPlayer.isCurrentSource(sourceUrl)
62
54
63 anchors { left: parent.left; right: parent.right }
55 anchors { left: parent.left; right: parent.right }
64 height: units.gu(5)
56 height: units.gu(5)
@@ -92,7 +84,7 @@ PreviewWidget {
92 DashAudioPlayer.play();
84 DashAudioPlayer.play();
93 }
85 }
94 } else {
86 } else {
95− trackRepeater.play(trackItem, sourceUrl);
87+ DashAudioPlayer.playSource(sourceUrl);
96 }
88 }
97 }
89 }
98 }
90 }
@@ -128,30 +120,10 @@ PreviewWidget {
128 elide: Text.ElideRight
120 elide: Text.ElideRight
129 }
121 }
130
122
131− UbuntuShape {
123+ AudioProgressBar {
132− id: progressBarFill
133− objectName: "progressBarFill"
134−
135− readonly property int maxWidth: progressBarImage.width - units.dp(4)
136−
137− anchors {
138− left: progressBarImage.left
139− right: progressBarImage.right
140− verticalCenter: progressBarImage.verticalCenter
141− margins: units.dp(2)
142− rightMargin: maxWidth - (maxWidth * DashAudioPlayer.progress) + units.dp(2)
143− }
144− height: units.dp(2)
145− visible: progressBarImage.visible
146− color: UbuntuColors.orange
147− }
148−
149− Image {
150− id: progressBarImage
151 anchors { left: parent.left; top: parent.bottom; right: parent.right }
124 anchors { left: parent.left; top: parent.bottom; right: parent.right }
152− height: units.dp(6)
153 visible: !DashAudioPlayer.stopped && trackItem.isPlayingItem && modelData["length"] > 0
125 visible: !DashAudioPlayer.stopped && trackItem.isPlayingItem && modelData["length"] > 0
154− source: "graphics/music_progress_bg.png"
126+ source: sourceUrl
155 }
127 }
156 }
128 }
157
129
qml/Dash/Previews/graphics/music_progress_bg.pngdeleted

Binary file not shown.

tests/qmltests/Dash/CardHelpers.js+1−1
@@ -16,7 +16,7 @@
16
16
17.pragma library
17.pragma library
18
18
19−var components = ["title", "art", "subtitle", "mascot", "emblem", "summary", "attributes", "overlayColor"]
19+var components = ["title", "art", "subtitle", "mascot", "emblem", "summary", "attributes", "overlayColor", "quickPreviewData"]
20
20
21var defaultLayout = ' \
21var defaultLayout = ' \
22{ \
22{ \
tests/qmltests/Dash/Previews/tst_PreviewAudioPlayback.qml+2−2
@@ -18,7 +18,7 @@ import QtQuick 2.0
18import QtTest 1.0
18import QtTest 1.0
19import Ubuntu.Components 0.1
19import Ubuntu.Components 0.1
20import "../../../../qml/Dash/Previews"
20import "../../../../qml/Dash/Previews"
21−import "../../../../qml/Dash/DashAudioPlayer"
21+import Dash 0.1
22import Unity.Test 0.1 as UT
22import Unity.Test 0.1 as UT
23import QtMultimedia 5.0
23import QtMultimedia 5.0
24
24
@@ -59,8 +59,8 @@ Rectangle {
59 function test_time_formatter_data() {
59 function test_time_formatter_data() {
60 return [
60 return [
61 { tag: "NaN", value: "not a number", result: "" },
61 { tag: "NaN", value: "not a number", result: "" },
62− { tag: "0", value: 0, result: "" },
63 { tag: "-1", value: -1, result: "" },
62 { tag: "-1", value: -1, result: "" },
63+ { tag: "0", value: 0, result: "0:00" },
64 { tag: "30", value: 30, result: "0:30" },
64 { tag: "30", value: 30, result: "0:30" },
65 { tag: "60", value: 60, result: "1:00" },
65 { tag: "60", value: 60, result: "1:00" },
66 { tag: "3600", value: 3600, result: "1:00:00" }
66 { tag: "3600", value: 3600, result: "1:00:00" }
tests/qmltests/Dash/tst_Card.qml+8−1
@@ -35,7 +35,8 @@ Rectangle {
35 "title": "foo",
35 "title": "foo",
36 "subtitle": "bar",
36 "subtitle": "bar",
37 "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
37 "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
38− "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
38+ "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}],
39+ "quickPreviewData": {"uri": "/some/file", "duration": "14"}
39 }'
40 }'
40
41
41 property var cardsModel: [
42 property var cardsModel: [
@@ -106,6 +107,11 @@ Rectangle {
106 "layout": { "template": { "card-layout": "horizontal" },
107 "layout": { "template": { "card-layout": "horizontal" },
107 "components": JSON.parse(Helpers.fullMapping) }
108 "components": JSON.parse(Helpers.fullMapping) }
108 },
109 },
110+ {
111+ "name": "Art, title, subtitle - Audio",
112+ "layout": { "template": { "card-layout": "horizontal", "quickPreviewType": "audio" },
113+ "components": { "art": "art", "title": "title", "subtitle": "subtitle", "quickPreviewData": "quickPreviewData" } }
114+ },
109 ]
115 ]
110
116
111 CardTool {
117 CardTool {
@@ -130,6 +136,7 @@ Rectangle {
130 item.width = Qt.binding(function() { return cardTool.cardWidth || item.implicitWidth; });
136 item.width = Qt.binding(function() { return cardTool.cardWidth || item.implicitWidth; });
131 item.height = Qt.binding(function() { return cardTool.cardHeight || item.implicitHeight; });
137 item.height = Qt.binding(function() { return cardTool.cardHeight || item.implicitHeight; });
132 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
138 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
139+ item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
133 }
140 }
134 }
141 }
135
142