@@ -138,6 +138,43 @@ var kArtShapeHolderCode = 'Item { \n\
138138 } \n\
139139 }\n';
140140
141+// %1 is anchors.fill
142+// %2 is width
143+// %3 is height
144+var kAudioButtonCode = 'AbstractButton { \n\
145+ id: audioButton; \n\
146+ anchors.fill: %1; \n\
147+ width: %2; \n\
148+ height: %3; \n\
149+ readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || ""; \n\
150+ UbuntuShape { \n\
151+ anchors.fill: parent; \n\
152+ visible: parent.pressed; \n\
153+ radius: "medium"; \n\
154+ } \n\
155+ Icon { \n\
156+ anchors.fill: parent; \n\
157+ anchors.margins: parent.height > units.gu(5) ? units.gu(2) : 0; \n\
158+ opacity: 0.9; \n\
159+ name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start"; \n\
160+ } \n\
161+ onClicked: { \n\
162+ if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) { \n\
163+ if (DashAudioPlayer.playing) { \n\
164+ DashAudioPlayer.pause(); \n\
165+ } else { \n\
166+ DashAudioPlayer.play(); \n\
167+ } \n\
168+ } else { \n\
169+ var playlist = (cardData["quickPreviewData"] && cardData["quickPreviewData"]["playlist"]) || null; \n\
170+ DashAudioPlayer.playSource(source, playlist); \n\
171+ } \n\
172+ } \n\
173+ onPressAndHold: { \n\
174+ root.pressAndHold(); \n\
175+ } \n\
176+ }';
177+
141178var kOverlayLoaderCode = 'Loader { \n\
142179 id: overlayLoader; \n\
143180 readonly property real overlayHeight: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2); \n\
@@ -319,6 +356,22 @@ var kSummaryLabelCode = 'Label { \n\
319356 color: %3; \n\
320357 }\n';
321358
359+// %1 is used as bottom anchor of audio progress bar
360+// %2 is used as left anchor of audio progress bar
361+// %3 is used as text color
362+var kAudioProgressBarCode = 'CardAudioProgress { \n\
363+ id: audioProgressBar; \n\
364+ duration: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["duration"]) || 0; \n\
365+ source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || ""; \n\
366+ anchors { \n\
367+ bottom: %1; \n\
368+ left: %2; \n\
369+ right: parent.right; \n\
370+ margins: units.gu(1); \n\
371+ } \n\
372+ color: %3; \n\
373+ }';
374+
322375function cardString(template, components) {
323376 var code;
324377
@@ -354,7 +407,20 @@ function cardString(template, components) {
354407 var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
355408 var hasSubtitle = hasTitle && components["subtitle"] || false;
356409 var hasHeaderRow = hasMascot && hasTitle;
357− var hasAttributes = hasTitle && components["attributes"]["field"] || false;
410+ var hasAttributes = hasTitle && components["attributes"] && components["attributes"]["field"] || false;
411+ var isAudio = template["quick-preview-type"] === "audio";
412+
413+ if (isAudio) {
414+ // For now we only support audio cards with [optional] art, title, subtitle
415+ // in horizontal mode
416+ // Anything else makes it behave not like an audio card
417+ if (hasSummary) isAudio = false;
418+ if (!isHorizontal) isAudio = false;
419+ if (hasMascot) isAudio = false;
420+ if (hasEmblem) isAudio = false;
421+ if (headerAsOverlay) isAudio = false;
422+ if (hasAttributes) isAudio = false;
423+ }
358424
359425 if (hasBackground) {
360426 var templateCardBackground = (template && typeof template["card-background"] === "string") ? template["card-background"] : "";
@@ -424,17 +490,22 @@ function cardString(template, components) {
424490 topMargin: units.gu(1);\n';
425491 }
426492 }
493+
427494 var headerLeftAnchor;
428495 var headerLeftAnchorHasMargin = false;
429496 if (isHorizontal && hasArt) {
430497 headerLeftAnchor = 'left: artShapeHolder.right; \n\
431498 leftMargin: units.gu(1);\n';
432499 headerLeftAnchorHasMargin = true;
500+ } else if (isHorizontal && isAudio) {
501+ headerLeftAnchor = 'left: audioButton.right; \n\
502+ leftMargin: units.gu(1);\n';
503+ headerLeftAnchorHasMargin = true;
433504 } else {
434505 headerLeftAnchor = 'left: parent.left;\n';
435506 }
436507
437− var touchdownOnArtShape = !hasBackground && hasArt && !hasMascot && !hasSummary;
508+ var touchdownOnArtShape = !hasBackground && hasArt && !hasMascot && !hasSummary && !isAudio;
438509
439510 if (hasHeaderRow) {
440511 code += 'readonly property int headerHeight: row.height;\n'
@@ -448,6 +519,14 @@ function cardString(template, components) {
448519 } else {
449520 code += 'readonly property int headerHeight: attributesRow.height;\n'
450521 }
522+ } else if (isAudio) {
523+ if (hasSubtitle) {
524+ code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + audioProgressBar.height + audioProgressBar.anchors.topMargin;\n'
525+ } else if (hasTitle) {
526+ code += 'readonly property int headerHeight: titleLabel.height + audioProgressBar.height + audioProgressBar.anchors.topMargin;\n'
527+ } else {
528+ code += 'readonly property int headerHeight: audioProgressBar.height;\n'
529+ }
451530 } else if (hasSubtitle) {
452531 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
453532 } else if (hasTitle) {
@@ -635,6 +714,30 @@ function cardString(template, components) {
635714 code += mascotShapeCode + mascotCode + titleSubtitleCode;
636715 }
637716
717+ if (isAudio) {
718+ var audioProgressBarLeftAnchor = 'audioButton.right';
719+ var audioProgressBarBottomAnchor = 'audioButton.bottom';
720+ var audioProgressBarTextColor = 'root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText';
721+
722+ code += kAudioProgressBarCode.arg(audioProgressBarBottomAnchor)
723+ .arg(audioProgressBarLeftAnchor)
724+ .arg(audioProgressBarTextColor);
725+
726+ var audioButtonAnchorsFill;
727+ var audioButtonWidth;
728+ var audioButtonHeight;
729+ if (hasArt) {
730+ audioButtonAnchorsFill = 'artShapeHolder';
731+ audioButtonWidth = 'undefined';
732+ audioButtonHeight = 'undefined';
733+ } else {
734+ audioButtonAnchorsFill = 'undefined';
735+ audioButtonWidth = 'height';
736+ audioButtonHeight = '(root.fixedHeaderHeight > 0 ? root.fixedHeaderHeight : headerHeight) + 2 * units.gu(1)';
737+ }
738+ code += kAudioButtonCode.arg(audioButtonAnchorsFill).arg(audioButtonWidth).arg(audioButtonHeight);
739+ }
740+
638741 if (hasSummary) {
639742 var summaryTopAnchor;
640743 if (isHorizontal && hasArt) summaryTopAnchor = 'artShapeHolder.bottom';
@@ -673,6 +776,8 @@ function cardString(template, components) {
673776 var implicitHeight = 'implicitHeight: ';
674777 if (hasSummary) {
675778 implicitHeight += 'summary.y + summary.height + units.gu(1);\n';
779+ } else if (isAudio) {
780+ implicitHeight += 'audioButton.height;\n';
676781 } else if (headerAsOverlay) {
677782 implicitHeight += 'artShapeHolder.height;\n';
678783 } else if (hasHeaderRow) {