on xenial help sign in
Introduce audio cards
ed38dceAlbert Astals Cid
debian/control+3−3
@@ -29,7 +29,7 @@ Build-Depends: cmake,
29 libqt5xmlpatterns5-dev,
29 libqt5xmlpatterns5-dev,
30 libsystemsettings-dev,
30 libsystemsettings-dev,
31 libudev-dev,
31 libudev-dev,
32− libunity-api-dev (>= 7.104),
32+ libunity-api-dev (>= 7.105),
33 libusermetricsoutput1-dev,
33 libusermetricsoutput1-dev,
34 libxcb1-dev,
34 libxcb1-dev,
35 pkg-config,
35 pkg-config,
@@ -48,7 +48,7 @@ Build-Depends: cmake,
48 qtdeclarative5-dev (>= 5.4),
48 qtdeclarative5-dev (>= 5.4),
49 qtdeclarative5-dev-tools,
49 qtdeclarative5-dev-tools,
50 qtdeclarative5-private-dev (>= 5.4),
50 qtdeclarative5-private-dev (>= 5.4),
51− qtdeclarative5-qtmultimedia-plugin,
51+ qtdeclarative5-qtmultimedia-plugin (>= 5.4.1-1ubuntu19~overlay2),
52 qtdeclarative5-ubuntu-content1,
52 qtdeclarative5-ubuntu-content1,
53 qtdeclarative5-ubuntu-settings-components (>= 0.6),
53 qtdeclarative5-ubuntu-settings-components (>= 0.6),
54 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1676) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1676),
54 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1676) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1676),
@@ -131,7 +131,7 @@ Depends: qml-module-qtquick-layouts,
131 unity-application-impl-12,
131 unity-application-impl-12,
132 unity-notifications-impl-3,
132 unity-notifications-impl-3,
133 unity-plugin-scopes | unity-scopes-impl,
133 unity-plugin-scopes | unity-scopes-impl,
134− unity-scopes-impl-7,
134+ unity-scopes-impl-9,
135 unity8-fake-env | unity-application-impl,
135 unity8-fake-env | unity-application-impl,
136 ${misc:Depends},
136 ${misc:Depends},
137Breaks: unity8 (<< 7.86),
137Breaks: unity8 (<< 7.86),
plugins/Dash/AudioProgressBar.qmlnew file+53
@@ -0,0 +1,53 @@
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: progressBarImage.height
25+
26+ property url source
27+ readonly property double progress: AudioUrlComparer.compare(source, DashAudioPlayer.currentSource) ? DashAudioPlayer.progress : 0
28+
29+ Image {
30+ id: progressBarImage
31+ anchors { left: parent.left; right: parent.right }
32+ height: units.dp(6)
33+ source: "graphics/music_progress_bg.png"
34+ sourceSize.width: width
35+ sourceSize.height: height
36+ }
37+
38+ UbuntuShape {
39+ id: progressBarFill
40+ objectName: "progressBarFill"
41+
42+ readonly property int maxWidth: progressBarImage.width
43+
44+ anchors {
45+ left: progressBarImage.left
46+ right: progressBarImage.right
47+ verticalCenter: progressBarImage.verticalCenter
48+ rightMargin: maxWidth - (maxWidth * root.progress)
49+ }
50+ height: units.dp(2)
51+ backgroundColor: UbuntuColors.orange
52+ }
53+}
plugins/Dash/CMakeLists.txt+3
@@ -38,3 +38,6 @@ target_link_libraries(Dash-qml
38qt5_use_modules(Dash-qml Qml Quick Concurrent)
38qt5_use_modules(Dash-qml Qml Quick Concurrent)
39
39
40add_unity8_plugin(Dash 0.1 Dash TARGETS Dash-qml)
40add_unity8_plugin(Dash 0.1 Dash TARGETS Dash-qml)
41+
42+install(FILES graphics/music_progress_bg.png DESTINATION ${SHELL_INSTALL_QML}/Dash/graphics/)
43+file(COPY graphics/music_progress_bg.png DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/graphics/)
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+ visible: AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)
26+
27+ property int duration: 0
28+ property alias source: progress.source
29+ property color color: theme.palette.normal.baseText
30+ readonly property int position: root.visible ? DashAudioPlayer.position / 1000 : 0
31+
32+ AudioProgressBar {
33+ id: progress
34+ anchors { left: parent.left; right: parent.right }
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+107−2
@@ -136,6 +136,43 @@ var kArtShapeHolderCode = 'Item { \n\
136 } \n\
136 } \n\
137 }\n';
137 }\n';
138
138
139+// %1 is anchors.fill
140+// %2 is width
141+// %3 is height
142+var kAudioButtonCode = 'AbstractButton { \n\
143+ id: audioButton; \n\
144+ anchors.fill: %1; \n\
145+ width: %2; \n\
146+ height: %3; \n\
147+ readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || ""; \n\
148+ UbuntuShape { \n\
149+ anchors.fill: parent; \n\
150+ visible: parent.pressed; \n\
151+ radius: "medium"; \n\
152+ } \n\
153+ Icon { \n\
154+ anchors.fill: parent; \n\
155+ anchors.margins: parent.height > units.gu(5) ? units.gu(2) : 0; \n\
156+ opacity: 0.9; \n\
157+ name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start"; \n\
158+ } \n\
159+ onClicked: { \n\
160+ if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) { \n\
161+ if (DashAudioPlayer.playing) { \n\
162+ DashAudioPlayer.pause(); \n\
163+ } else { \n\
164+ DashAudioPlayer.play(); \n\
165+ } \n\
166+ } else { \n\
167+ var playlist = (cardData["quickPreviewData"] && cardData["quickPreviewData"]["playlist"]) || null; \n\
168+ DashAudioPlayer.playSource(source, playlist); \n\
169+ } \n\
170+ } \n\
171+ onPressAndHold: { \n\
172+ root.pressAndHold(); \n\
173+ } \n\
174+ }';
175+
139var kOverlayLoaderCode = 'Loader { \n\
176var kOverlayLoaderCode = 'Loader { \n\
140 id: overlayLoader; \n\
177 id: overlayLoader; \n\
141 readonly property real overlayHeight: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2); \n\
178 readonly property real overlayHeight: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2); \n\
@@ -316,6 +353,22 @@ var kSummaryLabelCode = 'Label { \n\
316 color: %3; \n\
353 color: %3; \n\
317 }\n';
354 }\n';
318
355
356+// %1 is used as bottom anchor of audio progress bar
357+// %2 is used as left anchor of audio progress bar
358+// %3 is used as text color
359+var kAudioProgressBarCode = 'CardAudioProgress { \n\
360+ id: audioProgressBar; \n\
361+ duration: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["duration"]) || 0; \n\
362+ source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || ""; \n\
363+ anchors { \n\
364+ bottom: %1; \n\
365+ left: %2; \n\
366+ right: parent.right; \n\
367+ margins: units.gu(1); \n\
368+ } \n\
369+ color: %3; \n\
370+ }';
371+
319function cardString(template, components) {
372function cardString(template, components) {
320 var code;
373 var code;
321
374
@@ -351,7 +404,20 @@ function cardString(template, components) {
351 var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
404 var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
352 var hasSubtitle = hasTitle && components["subtitle"] || false;
405 var hasSubtitle = hasTitle && components["subtitle"] || false;
353 var hasHeaderRow = hasMascot && hasTitle;
406 var hasHeaderRow = hasMascot && hasTitle;
354− var hasAttributes = hasTitle && components["attributes"]["field"] || false;
407+ var hasAttributes = hasTitle && components["attributes"] && components["attributes"]["field"] || false;
408+ var isAudio = template["quick-preview-type"] === "audio";
409+
410+ if (isAudio) {
411+ // For now we only support audio cards with [optional] art, title, subtitle
412+ // in horizontal mode
413+ // Anything else makes it behave not like an audio card
414+ if (hasSummary) isAudio = false;
415+ if (!isHorizontal) isAudio = false;
416+ if (hasMascot) isAudio = false;
417+ if (hasEmblem) isAudio = false;
418+ if (headerAsOverlay) isAudio = false;
419+ if (hasAttributes) isAudio = false;
420+ }
355
421
356 if (hasBackground) {
422 if (hasBackground) {
357 var templateCardBackground = (template && typeof template["card-background"] === "string") ? template["card-background"] : "";
423 var templateCardBackground = (template && typeof template["card-background"] === "string") ? template["card-background"] : "";
@@ -421,17 +487,22 @@ function cardString(template, components) {
421 topMargin: units.gu(1);\n';
487 topMargin: units.gu(1);\n';
422 }
488 }
423 }
489 }
490+
424 var headerLeftAnchor;
491 var headerLeftAnchor;
425 var headerLeftAnchorHasMargin = false;
492 var headerLeftAnchorHasMargin = false;
426 if (isHorizontal && hasArt) {
493 if (isHorizontal && hasArt) {
427 headerLeftAnchor = 'left: artShapeHolder.right; \n\
494 headerLeftAnchor = 'left: artShapeHolder.right; \n\
428 leftMargin: units.gu(1);\n';
495 leftMargin: units.gu(1);\n';
429 headerLeftAnchorHasMargin = true;
496 headerLeftAnchorHasMargin = true;
497+ } else if (isHorizontal && isAudio) {
498+ headerLeftAnchor = 'left: audioButton.right; \n\
499+ leftMargin: units.gu(1);\n';
500+ headerLeftAnchorHasMargin = true;
430 } else {
501 } else {
431 headerLeftAnchor = 'left: parent.left;\n';
502 headerLeftAnchor = 'left: parent.left;\n';
432 }
503 }
433
504
434− var touchdownOnArtShape = !hasBackground && hasArt && !hasMascot && !hasSummary;
505+ var touchdownOnArtShape = !hasBackground && hasArt && !hasMascot && !hasSummary && !isAudio;
435
506
436 if (hasHeaderRow) {
507 if (hasHeaderRow) {
437 code += 'readonly property int headerHeight: row.height;\n'
508 code += 'readonly property int headerHeight: row.height;\n'
@@ -445,6 +516,14 @@ function cardString(template, components) {
445 } else {
516 } else {
446 code += 'readonly property int headerHeight: attributesRow.height;\n'
517 code += 'readonly property int headerHeight: attributesRow.height;\n'
447 }
518 }
519+ } else if (isAudio) {
520+ if (hasSubtitle) {
521+ code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + audioProgressBar.height + audioProgressBar.anchors.topMargin;\n'
522+ } else if (hasTitle) {
523+ code += 'readonly property int headerHeight: titleLabel.height + audioProgressBar.height + audioProgressBar.anchors.topMargin;\n'
524+ } else {
525+ code += 'readonly property int headerHeight: audioProgressBar.height;\n'
526+ }
448 } else if (hasSubtitle) {
527 } else if (hasSubtitle) {
449 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
528 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
450 } else if (hasTitle) {
529 } else if (hasTitle) {
@@ -632,6 +711,30 @@ function cardString(template, components) {
632 code += mascotShapeCode + mascotCode + titleSubtitleCode;
711 code += mascotShapeCode + mascotCode + titleSubtitleCode;
633 }
712 }
634
713
714+ if (isAudio) {
715+ var audioProgressBarLeftAnchor = 'audioButton.right';
716+ var audioProgressBarBottomAnchor = 'audioButton.bottom';
717+ var audioProgressBarTextColor = 'root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText';
718+
719+ code += kAudioProgressBarCode.arg(audioProgressBarBottomAnchor)
720+ .arg(audioProgressBarLeftAnchor)
721+ .arg(audioProgressBarTextColor);
722+
723+ var audioButtonAnchorsFill;
724+ var audioButtonWidth;
725+ var audioButtonHeight;
726+ if (hasArt) {
727+ audioButtonAnchorsFill = 'artShapeHolder';
728+ audioButtonWidth = 'undefined';
729+ audioButtonHeight = 'undefined';
730+ } else {
731+ audioButtonAnchorsFill = 'undefined';
732+ audioButtonWidth = 'height';
733+ audioButtonHeight = '(root.fixedHeaderHeight > 0 ? root.fixedHeaderHeight : headerHeight) + 2 * units.gu(1)';
734+ }
735+ code += kAudioButtonCode.arg(audioButtonAnchorsFill).arg(audioButtonWidth).arg(audioButtonHeight);
736+ }
737+
635 if (hasSummary) {
738 if (hasSummary) {
636 var summaryTopAnchor;
739 var summaryTopAnchor;
637 if (isHorizontal && hasArt) summaryTopAnchor = 'artShapeHolder.bottom';
740 if (isHorizontal && hasArt) summaryTopAnchor = 'artShapeHolder.bottom';
@@ -670,6 +773,8 @@ function cardString(template, components) {
670 var implicitHeight = 'implicitHeight: ';
773 var implicitHeight = 'implicitHeight: ';
671 if (hasSummary) {
774 if (hasSummary) {
672 implicitHeight += 'summary.y + summary.height + units.gu(1);\n';
775 implicitHeight += 'summary.y + summary.height + units.gu(1);\n';
776+ } else if (isAudio) {
777+ implicitHeight += 'audioButton.height;\n';
673 } else if (headerAsOverlay) {
778 } else if (headerAsOverlay) {
674 implicitHeight += 'artShapeHolder.height;\n';
779 implicitHeight += 'artShapeHolder.height;\n';
675 } else if (hasHeaderRow) {
780 } else if (hasHeaderRow) {
plugins/Dash/DashAudioPlayer.qmlnew file+96
@@ -0,0 +1,96 @@
1+/*
2+ * Copyright (C) 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+pragma Singleton
18+import QtQuick 2.4
19+import QtMultimedia 5.6
20+import Dash 0.1
21+
22+QtObject {
23+ readonly property real progress: audio.position / audio.duration
24+ readonly property bool playing: audio.playbackState === Audio.PlayingState
25+ readonly property bool paused: audio.playbackState === Audio.PausedState
26+ readonly property bool stopped: audio.playbackState === Audio.StoppedState
27+ readonly property alias position: audio.position
28+ readonly property url currentSource: audio.playlist.currentItemSource
29+
30+ function playSource(newSource, newPlaylist) {
31+ stop();
32+ audio.playlist.clear();
33+ if (newPlaylist) {
34+ // Look for newSource in newPlaylist
35+ var sourceIndex = -1;
36+ for (var i in newPlaylist) {
37+ if (AudioUrlComparer.compare(newSource, newPlaylist[i])) {
38+ sourceIndex = i;
39+ break;
40+ }
41+ }
42+ var urls = [];
43+ if (sourceIndex === -1 && newSource != "") {
44+ // If the playing song is not in the playlist, add it
45+ urls.push(newSource);
46+ sourceIndex = 0;
47+ }
48+ for (var i in newPlaylist) {
49+ urls.push(newPlaylist[i]);
50+ }
51+ audio.playlist.addItems(urls);
52+ audio.playlist.currentIndex = sourceIndex;
53+ } else {
54+ audio.playlist.addItem(newSource);
55+ audio.playlist.currentIndex = 0;
56+ }
57+ play();
58+ }
59+
60+ function stop() {
61+ audio.stop();
62+ }
63+
64+ function play() {
65+ audio.play();
66+ }
67+
68+ function pause() {
69+ audio.pause();
70+ }
71+
72+ property QtObject audio: Audio {
73+ id: audio
74+ objectName: "audio"
75+ playlist: Playlist {
76+ objectName: "playlist"
77+ }
78+
79+ onErrorStringChanged: console.warn("Dash Audio player error:", errorString)
80+ }
81+
82+ function lengthToString(s) {
83+ if (typeof(s) !== "number" || s < 0) return "";
84+
85+ var sec = "" + s % 60;
86+ if (sec.length == 1) sec = "0" + sec;
87+ var hour = Math.floor(s / 3600);
88+ if (hour < 1) {
89+ return Math.floor(s / 60) + ":" + sec;
90+ } else {
91+ var min = "" + Math.floor(s / 60) % 60;
92+ if (min.length == 1) min = "0" + min;
93+ return hour + ":" + min + ":" + sec;
94+ }
95+ }
96+}
plugins/Dash/graphics/music_progress_bg.pngnew file

Binary file not shown.

plugins/Dash/plugin.cpp+35
@@ -23,6 +23,38 @@
23#include "verticaljournal.h"
23#include "verticaljournal.h"
24
24
25#include <QAbstractItemModel>
25#include <QAbstractItemModel>
26+#include <QUrlQuery>
27+
28+static QUrl oauthCleanedUrl(QUrl u)
29+{
30+ QUrlQuery q(u);
31+ q.removeQueryItem("oauth_nonce");
32+ q.removeQueryItem("oauth_timestamp");
33+ q.removeQueryItem("oauth_consumer_key");
34+ q.removeQueryItem("oauth_signature_method");
35+ q.removeQueryItem("oauth_version");
36+ q.removeQueryItem("oauth_signature");
37+ u.setQuery(q);
38+ return u;
39+}
40+
41+class AudioComparer : public QObject
42+{
43+ Q_OBJECT
44+public:
45+ Q_INVOKABLE bool compare(const QUrl &url1, const QUrl &url2)
46+ {
47+ return oauthCleanedUrl(url1) == oauthCleanedUrl(url2);
48+ }
49+};
50+
51+static QObject *audio_comparer_singleton_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
52+{
53+ Q_UNUSED(engine)
54+ Q_UNUSED(scriptEngine)
55+
56+ return new AudioComparer();
57+}
26
58
27void DashPlugin::registerTypes(const char *uri)
59void DashPlugin::registerTypes(const char *uri)
28{
60{
@@ -32,4 +64,7 @@ void DashPlugin::registerTypes(const char *uri)
32 qmlRegisterType<ListViewWithPageHeader>(uri, 0, 1, "ListViewWithPageHeader");
64 qmlRegisterType<ListViewWithPageHeader>(uri, 0, 1, "ListViewWithPageHeader");
33 qmlRegisterType<OrganicGrid>(uri, 0, 1, "OrganicGrid");
65 qmlRegisterType<OrganicGrid>(uri, 0, 1, "OrganicGrid");
34 qmlRegisterType<VerticalJournal>(uri, 0, 1, "VerticalJournal");
66 qmlRegisterType<VerticalJournal>(uri, 0, 1, "VerticalJournal");
67+ qmlRegisterSingletonType<AudioComparer>(uri, 0, 1, "AudioUrlComparer", audio_comparer_singleton_provider);
35}
68}
69+
70+#include "plugin.moc"
plugins/Dash/qmldir+3
@@ -2,6 +2,9 @@ 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
10+CardAudioProgress 0.1 CardAudioProgress.qml
qml/Dash/Previews/PreviewAudioPlayback.qml+16−76
@@ -15,8 +15,8 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18−import QtMultimedia 5.0
19import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19+import Dash 0.1
20
20
21/*! \brief Preview widget for audio tracks.
21/*! \brief Preview widget for audio tracks.
22
22
@@ -36,38 +36,6 @@ PreviewWidget {
36 id: root
36 id: root
37 implicitHeight: childrenRect.height
37 implicitHeight: childrenRect.height
38
38
39− onIsCurrentPreviewChanged: if (!isCurrentPreview) audio.stop()
40−
41− Audio {
42− id: audio
43− objectName: "audio"
44−
45− property real progress: audio.position / audio.duration
46− property Item playingItem
47−
48− Component.onDestruction: {
49− // destroying the component doesn't automatically send stop to the media service, probably a bug in QtMultimedia
50− audio.stop();
51− }
52−
53− onErrorStringChanged: console.warn("Audio player error:", errorString)
54−
55− function lengthToString(s) {
56− if (typeof(s) !== "number" || s <= 0) return "";
57−
58− var sec = "" + s % 60;
59− if (sec.length == 1) sec = "0" + sec;
60− var hour = Math.floor(s / 3600);
61− if (hour < 1) {
62− return Math.floor(s / 60) + ":" + sec;
63− } else {
64− var min = "" + Math.floor(s / 60) % 60;
65− if (min.length == 1) min = "0" + min;
66− return hour + ":" + min + ":" + sec;
67− }
68− }
69− }
70−
71 Column {
39 Column {
72 anchors { left: parent.left; right: parent.right }
40 anchors { left: parent.left; right: parent.right }
73 visible: trackRepeater.count > 0
41 visible: trackRepeater.count > 0
@@ -77,20 +45,12 @@ PreviewWidget {
77 objectName: "trackRepeater"
45 objectName: "trackRepeater"
78 model: root.widgetData["tracks"]
46 model: root.widgetData["tracks"]
79
47
80− function play(item, source) {
81− audio.stop();
82− // Make sure we change the source, even if two items point to the same uri location
83− audio.source = "";
84− audio.source = source;
85− audio.playingItem = item;
86− audio.play();
87− }
88−
89 delegate: Item {
48 delegate: Item {
90 id: trackItem
49 id: trackItem
91 objectName: "trackItem" + index
50 objectName: "trackItem" + index
92
51
93− property bool isPlayingItem: audio.playingItem == trackItem
52+ readonly property url sourceUrl: modelData["source"]
53+ readonly property bool isPlayingItem: AudioUrlComparer.compare(sourceUrl, DashAudioPlayer.currentSource)
94
54
95 anchors { left: parent.left; right: parent.right }
55 anchors { left: parent.left; right: parent.right }
96 height: units.gu(5)
56 height: units.gu(5)
@@ -98,9 +58,9 @@ PreviewWidget {
98 Row {
58 Row {
99 id: trackRow
59 id: trackRow
100
60
101− property int column1Width: units.gu(3)
61+ readonly property int column1Width: units.gu(3)
102− property int column2Width: width - (2 * spacing) - column1Width - column3Width
62+ readonly property int column2Width: width - (2 * spacing) - column1Width - column3Width
103− property int column3Width: units.gu(4)
63+ readonly property int column3Width: units.gu(4)
104
64
105 anchors.verticalCenter: parent.verticalCenter
65 anchors.verticalCenter: parent.verticalCenter
106 width: parent.width
66 width: parent.width
@@ -110,7 +70,7 @@ PreviewWidget {
110 objectName: "playButton"
70 objectName: "playButton"
111 width: trackRow.column1Width
71 width: trackRow.column1Width
112 height: width
72 height: width
113− iconSource: audio.playbackState == Audio.PlayingState && trackItem.isPlayingItem ? "image://theme/media-playback-pause" : "image://theme/media-playback-start"
73+ iconSource: DashAudioPlayer.playing && trackItem.isPlayingItem ? "image://theme/media-playback-pause" : "image://theme/media-playback-start"
114
74
115 // Can't be "transparent" or "#00xxxxxx" as the button optimizes away the surrounding shape
75 // Can't be "transparent" or "#00xxxxxx" as the button optimizes away the surrounding shape
116 // FIXME when this is resolved: https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1251685
76 // FIXME when this is resolved: https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1251685
@@ -118,13 +78,13 @@ PreviewWidget {
118
78
119 onClicked: {
79 onClicked: {
120 if (trackItem.isPlayingItem) {
80 if (trackItem.isPlayingItem) {
121− if (audio.playbackState == Audio.PlayingState) {
81+ if (DashAudioPlayer.playing) {
122− audio.pause();
82+ DashAudioPlayer.pause();
123− } else if (audio.playbackState == Audio.PausedState) {
83+ } else if (DashAudioPlayer.paused) {
124− audio.play();
84+ DashAudioPlayer.play();
125 }
85 }
126 } else {
86 } else {
127− trackRepeater.play(trackItem, modelData["source"]);
87+ DashAudioPlayer.playSource(sourceUrl);
128 }
88 }
129 }
89 }
130 }
90 }
@@ -160,30 +120,10 @@ PreviewWidget {
160 elide: Text.ElideRight
120 elide: Text.ElideRight
161 }
121 }
162
122
163− UbuntuShape {
123+ AudioProgressBar {
164− id: progressBarFill
165− objectName: "progressBarFill"
166−
167− property int maxWidth: progressBarImage.width - units.dp(4)
168−
169− anchors {
170− left: progressBarImage.left
171− right: progressBarImage.right
172− verticalCenter: progressBarImage.verticalCenter
173− margins: units.dp(2)
174− rightMargin: maxWidth - (maxWidth * audio.progress) + units.dp(2)
175− }
176− height: units.dp(2)
177− visible: progressBarImage.visible
178− backgroundColor: UbuntuColors.orange
179− }
180−
181− Image {
182− id: progressBarImage
183 anchors { left: parent.left; top: parent.bottom; right: parent.right }
124 anchors { left: parent.left; top: parent.bottom; right: parent.right }
184− height: units.dp(6)
185− visible: audio.playbackState != Audio.StoppedState && trackItem.isPlayingItem && modelData["length"] > 0
125+ visible: !DashAudioPlayer.stopped && trackItem.isPlayingItem && modelData["length"] > 0
186− source: "graphics/music_progress_bg.png"
126+ source: sourceUrl
187 }
127 }
188 }
128 }
189
129
@@ -196,7 +136,7 @@ PreviewWidget {
196 color: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
136 color: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
197 fontSize: "small"
137 fontSize: "small"
198 horizontalAlignment: Text.AlignRight
138 horizontalAlignment: Text.AlignRight
199− text: audio.lengthToString(modelData["length"])
139+ text: DashAudioPlayer.lengthToString(modelData["length"])
200 }
140 }
201 }
141 }
202 }
142 }
qml/Dash/Previews/graphics/music_progress_bg.pngdeleted

Binary file not shown.

tests/mocks/QtMultimedia/CMakeLists.txt+1
@@ -1,6 +1,7 @@
1add_library(QtMultimedia-qml MODULE
1add_library(QtMultimedia-qml MODULE
2 plugin.cpp
2 plugin.cpp
3 audio.cpp
3 audio.cpp
4+ declarativeplaylist.cpp
4 )
5 )
5
6
6qt5_use_modules(QtMultimedia-qml Qml)
7qt5_use_modules(QtMultimedia-qml Qml)
tests/mocks/QtMultimedia/audio.cpp+21−19
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -20,30 +20,18 @@
20
20
21Audio::Audio(QObject* parent):
21Audio::Audio(QObject* parent):
22 QObject(parent),
22 QObject(parent),
23− m_playbackState(StoppedState)
23+ m_playbackState(StoppedState),
24+ m_playlist(0)
24{
25{
25 qsrand(time(nullptr));
26 qsrand(time(nullptr));
26 m_timer.setInterval(1000);
27 m_timer.setInterval(1000);
27 connect(&m_timer, &QTimer::timeout, this, &Audio::timerEvent);
28 connect(&m_timer, &QTimer::timeout, this, &Audio::timerEvent);
28−}
29−
30−QUrl Audio::source() const
31−{
32− return m_source;
33−}
34
29
35−void Audio::setSource(const QUrl &source)
36−{
37− if (m_source != source) {
38− m_source = source;
39− Q_EMIT sourceChanged(source);
40−
41− m_position = 0;
30+ m_position = 0;
42− Q_EMIT positionChanged(m_position);
31+ Q_EMIT positionChanged(m_position);
43
32
44− m_duration = (qrand() % 20000) + 10000;
33+ m_duration = (qrand() % 20000) + 10000;
45− Q_EMIT durationChanged(m_duration);
34+ Q_EMIT durationChanged(m_duration);
46− }
47}
35}
48
36
49Audio::PlaybackState Audio::playbackState() const
37Audio::PlaybackState Audio::playbackState() const
@@ -115,3 +103,17 @@ void Audio::setAudioRole(Audio::AudioRole audioRole)
115{
103{
116 Q_UNUSED(audioRole);
104 Q_UNUSED(audioRole);
117}
105}
106+
107+DeclarativePlaylist *Audio::playlist() const
108+{
109+ return m_playlist;
110+}
111+
112+void Audio::setPlaylist(DeclarativePlaylist *playlist)
113+{
114+ if (playlist == m_playlist)
115+ return;
116+
117+ m_playlist = playlist;
118+ Q_EMIT playlistChanged();
119+}
tests/mocks/QtMultimedia/audio.h+8−6
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2012,2013 Canonical, Ltd.
2+ * Copyright (C) 2012, 2013, 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
13 * You should have received a copy of the GNU General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
15 *
16− * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17 */
16 */
18
17
19#ifndef MOCK_AUDIO_H
18#ifndef MOCK_AUDIO_H
@@ -23,17 +22,19 @@
23#include <QUrl>
22#include <QUrl>
24#include <QTimer>
23#include <QTimer>
25
24
25+class DeclarativePlaylist;
26+
26class Audio: public QObject
27class Audio: public QObject
27{
28{
28 Q_OBJECT
29 Q_OBJECT
29 Q_ENUMS(PlaybackState)
30 Q_ENUMS(PlaybackState)
30 Q_ENUMS(AudioRole)
31 Q_ENUMS(AudioRole)
31− Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
32 Q_PROPERTY(PlaybackState playbackState READ playbackState NOTIFY playbackStateChanged)
32 Q_PROPERTY(PlaybackState playbackState READ playbackState NOTIFY playbackStateChanged)
33 Q_PROPERTY(int position READ position NOTIFY positionChanged)
33 Q_PROPERTY(int position READ position NOTIFY positionChanged)
34 Q_PROPERTY(int duration READ duration NOTIFY durationChanged)
34 Q_PROPERTY(int duration READ duration NOTIFY durationChanged)
35 Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
35 Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
36 Q_PROPERTY(AudioRole audioRole READ audioRole WRITE setAudioRole)
36 Q_PROPERTY(AudioRole audioRole READ audioRole WRITE setAudioRole)
37+ Q_PROPERTY(DeclarativePlaylist *playlist READ playlist WRITE setPlaylist NOTIFY playlistChanged)
37public:
38public:
38 enum PlaybackState {
39 enum PlaybackState {
39 PlayingState,
40 PlayingState,
@@ -50,8 +51,8 @@ public:
50
51
51 explicit Audio(QObject *parent = 0);
52 explicit Audio(QObject *parent = 0);
52
53
53− QUrl source() const;
54+ DeclarativePlaylist *playlist() const;
54− void setSource(const QUrl &source);
55+ void setPlaylist(DeclarativePlaylist *playlist);
55
56
56 PlaybackState playbackState() const;
57 PlaybackState playbackState() const;
57
58
@@ -70,6 +71,7 @@ public Q_SLOTS:
70 void stop();
71 void stop();
71
72
72Q_SIGNALS:
73Q_SIGNALS:
74+ void playlistChanged();
73 void sourceChanged(const QUrl &source);
75 void sourceChanged(const QUrl &source);
74 void playbackStateChanged(PlaybackState playbackState);
76 void playbackStateChanged(PlaybackState playbackState);
75 void positionChanged(int position);
77 void positionChanged(int position);
@@ -80,11 +82,11 @@ private Q_SLOTS:
80 void timerEvent();
82 void timerEvent();
81
83
82private:
84private:
83− QUrl m_source;
84 PlaybackState m_playbackState;
85 PlaybackState m_playbackState;
85 QTimer m_timer;
86 QTimer m_timer;
86 int m_position;
87 int m_position;
87 int m_duration;
88 int m_duration;
89+ DeclarativePlaylist *m_playlist;
88};
90};
89
91
90#endif
92#endif
tests/mocks/QtMultimedia/declarativeplaylist.cppnew file+73
@@ -0,0 +1,73 @@
1+/*
2+ * Copyright (C) 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+
18+#include "declarativeplaylist.h"
19+
20+DeclarativePlaylist::DeclarativePlaylist(QObject *parent)
21+ : QObject(parent)
22+ , m_currentIndex(-1)
23+{
24+}
25+
26+QUrl DeclarativePlaylist::currentItemSource() const
27+{
28+ return itemSource(currentIndex());
29+}
30+
31+int DeclarativePlaylist::currentIndex() const
32+{
33+ return m_currentIndex;
34+}
35+
36+void DeclarativePlaylist::setCurrentIndex(int index)
37+{
38+ if (currentIndex() == index)
39+ return;
40+
41+ m_currentIndex = index;
42+ Q_EMIT currentIndexChanged();
43+ Q_EMIT currentItemSourceChanged();
44+}
45+
46+QUrl DeclarativePlaylist::itemSource(int index) const
47+{
48+ if (index < 0 || index >= m_medias.count())
49+ return QUrl();
50+ return m_medias[index];
51+}
52+
53+bool DeclarativePlaylist::addItem(const QUrl &source)
54+{
55+ m_medias << source;
56+ setCurrentIndex(0);
57+ return true;
58+}
59+
60+bool DeclarativePlaylist::addItems(const QList<QUrl> &sources)
61+{
62+ m_medias << sources;
63+ if (!sources.isEmpty())
64+ setCurrentIndex(0);
65+ return true;
66+}
67+
68+bool DeclarativePlaylist::clear()
69+{
70+ m_medias.clear();
71+ setCurrentIndex(-1);
72+ return true;
73+}
tests/mocks/QtMultimedia/declarativeplaylist.hnew file+54
@@ -0,0 +1,54 @@
1+/*
2+ * Copyright (C) 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+
18+#ifndef DECLARATIVEPLAYLIST_H
19+#define DECLARATIVEPLAYLIST_H
20+
21+#include <QObject>
22+#include <QUrl>
23+
24+class DeclarativePlaylist : public QObject
25+{
26+ Q_OBJECT
27+ Q_PROPERTY(QUrl currentItemSource READ currentItemSource NOTIFY currentItemSourceChanged)
28+ Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
29+
30+public:
31+ DeclarativePlaylist(QObject *parent = 0);
32+
33+ QUrl currentItemSource() const;
34+ int currentIndex() const;
35+ void setCurrentIndex(int currentIndex);
36+
37+public Q_SLOTS:
38+ QUrl itemSource(int index) const;
39+ bool addItem(const QUrl &source);
40+ bool addItems(const QList<QUrl> &sources);
41+ bool clear();
42+
43+Q_SIGNALS:
44+ void currentItemSourceChanged();
45+ void currentIndexChanged();
46+
47+private:
48+ Q_DISABLE_COPY(DeclarativePlaylist)
49+
50+ int m_currentIndex;
51+ QList<QUrl> m_medias;
52+};
53+
54+#endif
tests/mocks/QtMultimedia/plugin.cpp+2
@@ -18,6 +18,7 @@
18
18
19#include "plugin.h"
19#include "plugin.h"
20#include "audio.h"
20#include "audio.h"
21+#include "declarativeplaylist.h"
21
22
22#include <QtQml/qqml.h>
23#include <QtQml/qqml.h>
23
24
@@ -26,4 +27,5 @@ void MockQtMultimediaPlugin::registerTypes(const char *uri)
26 Q_ASSERT(uri == QLatin1String("QtMultimedia"));
27 Q_ASSERT(uri == QLatin1String("QtMultimedia"));
27 qmlRegisterType<Audio>(uri, 5, 0, "Audio");
28 qmlRegisterType<Audio>(uri, 5, 0, "Audio");
28 qmlRegisterType<Audio>(uri, 5, 0, "MediaPlayer");
29 qmlRegisterType<Audio>(uri, 5, 0, "MediaPlayer");
30+ qmlRegisterType<DeclarativePlaylist>(uri, 5, 6, "Playlist");
29}
31}
tests/mocks/Unity/CMakeLists.txt+1−1
@@ -7,7 +7,7 @@ add_subdirectory(Notifications)
7add_subdirectory(DashCommunicator)
7add_subdirectory(DashCommunicator)
8
8
9pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
9pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
10−pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=8)
10+pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=9)
11
11
12include_directories(
12include_directories(
13 ${CMAKE_CURRENT_BINARY_DIR}
13 ${CMAKE_CURRENT_BINARY_DIR}
tests/plugins/Dash/cardcreator/9.resnew file+112
@@ -0,0 +1,112 @@
1+AbstractButton {
2+ id: root;
3+ property var components;
4+ property var cardData;
5+ property string artShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property int titleAlignment: Text.AlignLeft;
9+ property int fixedHeaderHeight: -1;
10+ property size fixedArtShapeSize: Qt.size(-1, -1);
11+ readonly property string title: cardData && cardData["title"] || "";
12+ property bool asynchronous: true;
13+ property bool showHeader: true;
14+ implicitWidth: childrenRect.width;
15+ enabled: true;
16+
17+readonly property size artShapeSize: Qt.size(-1, -1);
18+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + audioProgressBar.height + audioProgressBar.anchors.topMargin;
19+Label {
20+ id: titleLabel;
21+ objectName: "titleLabel";
22+ anchors { right: parent.right;
23+rightMargin: units.gu(1);
24+left: audioButton.right;
25+ leftMargin: units.gu(1);
26+top: parent.top;
27+ topMargin: units.gu(1);
28+ }
29+ elide: Text.ElideRight;
30+ fontSize: "small";
31+ wrapMode: Text.Wrap;
32+ maximumLineCount: 2;
33+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
34+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
35+ visible: showHeader ;
36+ width: undefined;
37+ text: root.title;
38+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
39+ horizontalAlignment: root.titleAlignment;
40+ }
41+Label {
42+ id: subtitleLabel;
43+ objectName: "subtitleLabel";
44+ anchors { left: titleLabel.left;
45+ leftMargin: titleLabel.leftMargin;
46+rightMargin: units.gu(1);
47+right: titleLabel.right;
48+top: titleLabel.bottom;
49+ }
50+ anchors.topMargin: units.dp(2);
51+ elide: Text.ElideRight;
52+ maximumLineCount: 1;
53+ fontSize: "x-small";
54+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
55+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
56+ visible: titleLabel.visible && titleLabel.text;
57+ text: cardData && cardData["subtitle"] || "";
58+ font.weight: Font.Light;
59+ }
60+CardAudioProgress {
61+ id: audioProgressBar;
62+ duration: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["duration"]) || 0;
63+ source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
64+ anchors {
65+ bottom: audioButton.bottom;
66+ left: audioButton.right;
67+ right: parent.right;
68+ margins: units.gu(1);
69+ }
70+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
71+ }AbstractButton {
72+ id: audioButton;
73+ anchors.fill: undefined;
74+ width: height;
75+ height: (root.fixedHeaderHeight > 0 ? root.fixedHeaderHeight : headerHeight) + 2 * units.gu(1);
76+ readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
77+ UbuntuShape {
78+ anchors.fill: parent;
79+ visible: parent.pressed;
80+ radius: "medium";
81+ }
82+ Icon {
83+ anchors.fill: parent;
84+ anchors.margins: parent.height > units.gu(5) ? units.gu(2) : 0;
85+ opacity: 0.9;
86+ name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start";
87+ }
88+ onClicked: {
89+ if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) {
90+ if (DashAudioPlayer.playing) {
91+ DashAudioPlayer.pause();
92+ } else {
93+ DashAudioPlayer.play();
94+ }
95+ } else {
96+ var playlist = (cardData["quickPreviewData"] && cardData["quickPreviewData"]["playlist"]) || null;
97+ DashAudioPlayer.playSource(source, playlist);
98+ }
99+ }
100+ onPressAndHold: {
101+ root.pressAndHold();
102+ }
103+ }UbuntuShape {
104+ id: touchdown;
105+ objectName: "touchdown";
106+ anchors { fill: root }
107+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
108+ radius: "medium";
109+ borderSource: "radius_pressed.sci"
110+ }
111+implicitHeight: audioButton.height;
112+}
tests/plugins/Dash/cardcreator/9.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-layout":"horizontal","card-size":"medium","category-layout":"grid","collapsed-rows":2,"quick-preview-type": "audio"}
2+components: {"art":{"aspect-ratio":1},"subtitle":{"field":"author"},"title":{"field":"title"}, "quickPreviewData": "quickPreviewData"}
3+result: 9.res
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+5−9
@@ -18,6 +18,7 @@ import QtQuick 2.4
18import QtTest 1.0
18import QtTest 1.0
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20import "../../../../qml/Dash/Previews"
20import "../../../../qml/Dash/Previews"
21+import Dash 0.1
21import Unity.Test 0.1 as UT
22import Unity.Test 0.1 as UT
22import QtMultimedia 5.0
23import QtMultimedia 5.0
23
24
@@ -58,8 +59,8 @@ Rectangle {
58 function test_time_formatter_data() {
59 function test_time_formatter_data() {
59 return [
60 return [
60 { tag: "NaN", value: "not a number", result: "" },
61 { tag: "NaN", value: "not a number", result: "" },
61− { tag: "0", value: 0, result: "" },
62 { tag: "-1", value: -1, result: "" },
62 { tag: "-1", value: -1, result: "" },
63+ { tag: "0", value: 0, result: "0:00" },
63 { tag: "30", value: 30, result: "0:30" },
64 { tag: "30", value: 30, result: "0:30" },
64 { tag: "60", value: 60, result: "1:00" },
65 { tag: "60", value: 60, result: "1:00" },
65 { tag: "3600", value: 3600, result: "1:00:00" }
66 { tag: "3600", value: 3600, result: "1:00:00" }
@@ -67,8 +68,7 @@ Rectangle {
67 }
68 }
68
69
69 function test_time_formatter(data) {
70 function test_time_formatter(data) {
70− var audio = findInvisibleChild(previewAudioPlayback, "audio");
71− compare(audio.lengthToString(data.value), data.result)
71+ compare(DashAudioPlayer.lengthToString(data.value), data.result)
72 }
72 }
73
73
74 function test_tracks_data() {
74 function test_tracks_data() {
@@ -102,7 +102,7 @@ Rectangle {
102
102
103 function checkPlayerSource(index) {
103 function checkPlayerSource(index) {
104 var modelFilename = previewAudioPlayback.widgetData["tracks"][index]["source"].replace(/^.*[\\\/]/, '');
104 var modelFilename = previewAudioPlayback.widgetData["tracks"][index]["source"].replace(/^.*[\\\/]/, '');
105− var playerFilename = findInvisibleChild(previewAudioPlayback, "audio").source.toString().replace(/^.*[\\\/]/, '');
105+ var playerFilename = DashAudioPlayer.currentSource.toString().replace(/^.*[\\\/]/, '');
106
106
107 compare(modelFilename, playerFilename, "Player source is not set correctly.");
107 compare(modelFilename, playerFilename, "Player source is not set correctly.");
108 }
108 }
@@ -123,7 +123,7 @@ Rectangle {
123 var track1PlayButton = findChild(track1Item, "playButton");
123 var track1PlayButton = findChild(track1Item, "playButton");
124 var track2PlayButton = findChild(track2Item, "playButton");
124 var track2PlayButton = findChild(track2Item, "playButton");
125
125
126− var audio = findInvisibleChild(previewAudioPlayback, "audio");
126+ var audio = DashAudioPlayer.audio;
127
127
128 // All progress bars must be hidden in the beginning
128 // All progress bars must be hidden in the beginning
129 compare(track0ProgressBar.visible, false);
129 compare(track0ProgressBar.visible, false);
@@ -170,10 +170,6 @@ Rectangle {
170 tryCompare(track0ProgressBar, "visible", false);
170 tryCompare(track0ProgressBar, "visible", false);
171 tryCompare(track1ProgressBar, "visible", false);
171 tryCompare(track1ProgressBar, "visible", false);
172 tryCompare(track2ProgressBar, "visible", true);
172 tryCompare(track2ProgressBar, "visible", true);
173−
174− // Changing preview should make all players shut up!
175− previewAudioPlayback.isCurrentPreview = false
176− tryCompare(audio, "playbackState", Audio.StoppedState);
177 }
173 }
178 }
174 }
179}
175}
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", "quick-preview-type": "audio" },
113+ "components": { "art": "art", "title": "title", "subtitle": "subtitle", "quickPreviewData": "quickPreviewData" } }
114+ },
109 ]
115 ]
110
116
111 CardTool {
117 CardTool {
@@ -129,6 +135,7 @@ Rectangle {
129 item.width = Qt.binding(function() { return cardTool.cardWidth || item.implicitWidth; });
135 item.width = Qt.binding(function() { return cardTool.cardWidth || item.implicitWidth; });
130 item.height = Qt.binding(function() { return cardTool.cardHeight || item.implicitHeight; });
136 item.height = Qt.binding(function() { return cardTool.cardHeight || item.implicitHeight; });
131 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
137 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
138+ item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
132 }
139 }
133 }
140 }
134
141