BrowserItemActions

This commit is contained in:
Michael Zanetti 2019-07-10 11:40:06 +02:00
parent 2ad624329c
commit 41ebfb48f3
15 changed files with 631 additions and 201 deletions

View File

@ -314,7 +314,7 @@ void DeviceManager::editDeviceResponse(const QVariantMap &params)
void DeviceManager::executeActionResponse(const QVariantMap &params)
{
// qDebug() << "Execute Action response" << params;
qDebug() << "Execute Action response" << params;
emit executeActionReply(params);
}
@ -428,15 +428,15 @@ int DeviceManager::executeAction(const QUuid &deviceId, const QUuid &actionTypeI
return m_jsonClient->sendCommand("Actions.ExecuteAction", p, this, "executeActionResponse");
}
BrowserItems *DeviceManager::browseDevice(const QUuid &deviceId, const QString &nodeId)
BrowserItems *DeviceManager::browseDevice(const QUuid &deviceId, const QString &itemId)
{
QVariantMap params;
params.insert("deviceId", deviceId.toString());
params.insert("nodeId", nodeId);
params.insert("itemId", itemId);
int id = m_jsonClient->sendCommand("Devices.BrowseDevice", params, this, "browseDeviceResponse");
// Intentionally not parented. The caller takes ownership and needs to destroy when not needed any more.
BrowserItems *itemModel = new BrowserItems();
BrowserItems *itemModel = new BrowserItems(deviceId, itemId);
itemModel->setBusy(true);
QPointer<BrowserItems> itemModelPtr(itemModel);
m_browsingRequests.insert(id, itemModelPtr);
@ -444,6 +444,19 @@ BrowserItems *DeviceManager::browseDevice(const QUuid &deviceId, const QString &
return itemModel;
}
void DeviceManager::refreshBrowserItems(BrowserItems *browserItems)
{
QVariantMap params;
params.insert("deviceId", browserItems->deviceId().toString());
params.insert("itemId", browserItems->itemId());
int id = m_jsonClient->sendCommand("Devices.BrowseDevice", params, this, "browseDeviceResponse");
// Intentionally not parented. The caller takes ownership and needs to destroy when not needed any more.
browserItems->setBusy(true);
QPointer<BrowserItems> itemModelPtr(browserItems);
m_browsingRequests.insert(id, browserItems);
}
void DeviceManager::browseDeviceResponse(const QVariantMap &params)
{
qDebug() << "Browsing response:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented));
@ -459,33 +472,68 @@ void DeviceManager::browseDeviceResponse(const QVariantMap &params)
return;
}
QList<BrowserItem*> itemsToRemove = itemModel->list();
foreach (const QVariant &itemVariant, params.value("params").toMap().value("items").toList()) {
QVariantMap itemMap = itemVariant.toMap();
BrowserItem *item = new BrowserItem(itemMap.value("id").toString(), this);
QString itemId = itemMap.value("id").toString();
BrowserItem *item = itemModel->getBrowserItem(itemId);
if (!item) {
item = new BrowserItem(itemId, this);
itemModel->addBrowserItem(item);
}
item->setDisplayName(itemMap.value("displayName").toString());
item->setDescription(itemMap.value("description").toString());
item->setIcon(itemMap.value("icon").toString());
item->setThumbnail(itemMap.value("thumbnail").toString());
item->setExecutable(itemMap.value("executable").toBool());
item->setBrowsable(itemMap.value("browsable").toBool());
item->setDisabled(itemMap.value("disabled").toBool());
item->setActionTypeIds(itemMap.value("actionTypeIds").toStringList());
item->setMediaIcon(itemMap.value("mediaIcon").toString());
itemModel->addBrowserItem(item);
if (itemsToRemove.contains(item)) {
itemsToRemove.removeAll(item);
}
}
while (!itemsToRemove.isEmpty()) {
BrowserItem *item = itemsToRemove.takeFirst();
itemModel->removeItem(item);
}
itemModel->setBusy(false);
}
void DeviceManager::executeBrowserItem(const QUuid &deviceId, const QString &nodeId)
int DeviceManager::executeBrowserItem(const QUuid &deviceId, const QString &itemId)
{
QVariantMap params;
params.insert("deviceId", deviceId);
params.insert("nodeId", nodeId);
m_jsonClient->sendCommand("Devices.ExecuteBrowserItem", params, this, "executeBrowserItemResponse");
params.insert("itemId", itemId);
return m_jsonClient->sendCommand("Actions.ExecuteBrowserItem", params, this, "executeBrowserItemResponse");
}
void DeviceManager::executeBrowserItemResponse(const QVariantMap &params)
{
qDebug() << "Execute Browser Item finished" << params;
emit executeBrowserItemReply(params);
}
int DeviceManager::executeBrowserItemAction(const QUuid &deviceId, const QString &itemId, const QUuid &actionTypeId, const QVariantMap &params)
{
QVariantMap data;
data.insert("deviceId", deviceId);
data.insert("itemId", itemId);
data.insert("actionTypeId", actionTypeId);
data.insert("params", params);
return m_jsonClient->sendCommand("Actions.ExecuteBrowserItemAction", data, this, "executeBrowserItemActionResponse");
}
void DeviceManager::executeBrowserItemActionResponse(const QVariantMap &params)
{
qDebug() << "Execute Browser Item Action finished" << params;
emit executeBrowserItemActionReply(params);
}

View File

@ -74,8 +74,10 @@ public:
Q_INVOKABLE void reconfigureDevice(const QUuid &deviceId, const QVariantList &deviceParams);
Q_INVOKABLE void reconfigureDiscoveredDevice(const QUuid &deviceId, const QUuid &deviceDescriptorId);
Q_INVOKABLE int executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList &params = QVariantList());
Q_INVOKABLE BrowserItems* browseDevice(const QUuid &deviceId, const QString &nodeId = QString());
Q_INVOKABLE void executeBrowserItem(const QUuid &deviceId, const QString &nodeId);
Q_INVOKABLE BrowserItems* browseDevice(const QUuid &deviceId, const QString &itemId = QString());
Q_INVOKABLE void refreshBrowserItems(BrowserItems *browserItems);
Q_INVOKABLE int executeBrowserItem(const QUuid &deviceId, const QString &itemId);
Q_INVOKABLE int executeBrowserItemAction(const QUuid &deviceId, const QString &itemId, const QUuid &actionTypeId, const QVariantMap &params = QVariantMap());
private:
Q_INVOKABLE void notificationReceived(const QVariantMap &data);
@ -94,6 +96,7 @@ private:
Q_INVOKABLE void reconfigureDeviceResponse(const QVariantMap &params);
Q_INVOKABLE void browseDeviceResponse(const QVariantMap &params);
Q_INVOKABLE void executeBrowserItemResponse(const QVariantMap &params);
Q_INVOKABLE void executeBrowserItemActionResponse(const QVariantMap &params);
public slots:
void savePluginConfig(const QUuid &pluginId);
@ -107,6 +110,8 @@ signals:
void editDeviceReply(const QVariantMap &params);
void reconfigureDeviceReply(const QVariantMap &params);
void executeActionReply(const QVariantMap &params);
void executeBrowserItemReply(const QVariantMap &params);
void executeBrowserItemActionReply(const QVariantMap &params);
void fetchingDataChanged();
void notificationReceived(const QString &deviceId, const QString &eventTypeId, const QVariantList &params);

View File

@ -127,6 +127,13 @@ DeviceClass *JsonTypes::unpackDeviceClass(const QVariantMap &deviceClassMap, QOb
}
deviceClass->setActionTypes(actionTypes);
// BrowserItemActionTypes
ActionTypes *browserItemActionTypes = new ActionTypes(deviceClass);
foreach (QVariant actionType, deviceClassMap.value("browserItemActionTypes").toList()) {
browserItemActionTypes->addActionType(JsonTypes::unpackActionType(actionType.toMap(), actionTypes));
}
deviceClass->setBrowserItemActionTypes(browserItemActionTypes);
return deviceClass;
}

View File

@ -19,7 +19,10 @@ QString BrowserItem::displayName() const
void BrowserItem::setDisplayName(const QString &displayName)
{
m_displayName = displayName;
if (m_displayName != displayName) {
m_displayName = displayName;
emit displayNameChanged();
}
}
QString BrowserItem::description() const
@ -29,7 +32,10 @@ QString BrowserItem::description() const
void BrowserItem::setDescription(const QString &description)
{
m_description = description;
if (m_description != description) {
m_description = description;
emit descriptionChanged();
}
}
QString BrowserItem::icon() const
@ -39,7 +45,10 @@ QString BrowserItem::icon() const
void BrowserItem::setIcon(const QString &icon)
{
m_icon = icon;
if (m_icon != icon) {
m_icon = icon;
emit iconChanged();
}
}
QString BrowserItem::thumbnail() const
@ -49,7 +58,10 @@ QString BrowserItem::thumbnail() const
void BrowserItem::setThumbnail(const QString &thumbnail)
{
m_thumbnail = thumbnail;
if (m_thumbnail != thumbnail) {
m_thumbnail = thumbnail;
emit thumbnailChanged();
}
}
bool BrowserItem::executable() const
@ -59,7 +71,10 @@ bool BrowserItem::executable() const
void BrowserItem::setExecutable(bool executable)
{
m_executable = executable;
if (m_executable != executable) {
m_executable = executable;
emit executableChanged();
}
}
bool BrowserItem::browsable() const
@ -69,7 +84,36 @@ bool BrowserItem::browsable() const
void BrowserItem::setBrowsable(bool browsable)
{
m_browsable = browsable;
if (m_browsable != browsable) {
m_browsable = browsable;
emit browsableChanged();
}
}
bool BrowserItem::disabled() const
{
return m_disabled;
}
void BrowserItem::setDisabled(bool disabled)
{
if (m_disabled != disabled) {
m_disabled = disabled;
emit disabledChanged();
}
}
QStringList BrowserItem::actionTypeIds() const
{
return m_actionTypeIds;
}
void BrowserItem::setActionTypeIds(const QStringList &actionTypeIds)
{
if (m_actionTypeIds != actionTypeIds) {
m_actionTypeIds = actionTypeIds;
emit actionTypeIdsChanged();
}
}
QString BrowserItem::mediaIcon() const
@ -79,5 +123,8 @@ QString BrowserItem::mediaIcon() const
void BrowserItem::setMediaIcon(const QString &mediaIcon)
{
m_mediaIcon = mediaIcon;
if (m_mediaIcon != mediaIcon) {
m_mediaIcon = mediaIcon;
emit mediaIconChanged();
}
}

View File

@ -8,14 +8,16 @@ class BrowserItem : public QObject
{
Q_OBJECT
Q_PROPERTY(QUuid id READ id CONSTANT)
Q_PROPERTY(QString displayName READ displayName CONSTANT)
Q_PROPERTY(QString description READ description CONSTANT)
Q_PROPERTY(QString icon READ icon CONSTANT)
Q_PROPERTY(QString thumbnail READ thumbnail CONSTANT)
Q_PROPERTY(bool executable READ executable CONSTANT)
Q_PROPERTY(bool browsable READ browsable CONSTANT)
Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged)
Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged)
Q_PROPERTY(QString thumbnail READ thumbnail NOTIFY thumbnailChanged)
Q_PROPERTY(bool executable READ executable NOTIFY executableChanged)
Q_PROPERTY(bool browsable READ browsable NOTIFY browsableChanged)
Q_PROPERTY(bool disabled READ disabled NOTIFY disabledChanged)
Q_PROPERTY(QStringList actionTypeIds READ actionTypeIds NOTIFY actionTypeIdsChanged)
Q_PROPERTY(QString mediaIcon READ mediaIcon CONSTANT)
Q_PROPERTY(QString mediaIcon READ mediaIcon NOTIFY mediaIconChanged)
public:
explicit BrowserItem(const QString &id, QObject *parent = nullptr);
@ -40,9 +42,27 @@ public:
bool browsable() const;
void setBrowsable(bool browsable);
bool disabled() const;
void setDisabled(bool disabled);
QStringList actionTypeIds() const;
void setActionTypeIds(const QStringList &actionTypeIds);
QString mediaIcon() const;
void setMediaIcon(const QString &mediaIcon);
signals:
void displayNameChanged();
void descriptionChanged();
void iconChanged();
void thumbnailChanged();
void executableChanged();
void browsableChanged();
void disabledChanged();
void actionTypeIdsChanged();
void mediaIconChanged();
private:
QString m_id;
QString m_displayName;
@ -51,6 +71,8 @@ private:
QString m_thumbnail;
bool m_executable = false;
bool m_browsable = false;
bool m_disabled = false;
QStringList m_actionTypeIds;
QString m_mediaIcon;
};

View File

@ -3,7 +3,10 @@
#include <QDebug>
BrowserItems::BrowserItems(QObject *parent): QAbstractListModel(parent)
BrowserItems::BrowserItems(const QUuid &deviceId, const QString &itemId, QObject *parent):
QAbstractListModel (parent),
m_deviceId(deviceId),
m_itemId(itemId)
{
}
@ -13,6 +16,16 @@ BrowserItems::~BrowserItems()
qDebug() << "Deleting BrowserItems";
}
QUuid BrowserItems::deviceId() const
{
return m_deviceId;
}
QString BrowserItems::itemId() const
{
return m_itemId;
}
bool BrowserItems::busy() const
{
return m_busy;
@ -41,6 +54,10 @@ QVariant BrowserItems::data(const QModelIndex &index, int role) const
return m_list.at(index.row())->executable();
case RoleBrowsable:
return m_list.at(index.row())->browsable();
case RoleActionTypeIds:
return m_list.at(index.row())->actionTypeIds();
case RoleDisabled:
return m_list.at(index.row())->disabled();
case RoleMediaIcon:
return m_list.at(index.row())->mediaIcon();
@ -58,6 +75,8 @@ QHash<int, QByteArray> BrowserItems::roleNames() const
roles.insert(RoleThumbnail, "thumbnail");
roles.insert(RoleExecutable, "executable");
roles.insert(RoleBrowsable, "browsable");
roles.insert(RoleDisabled, "disabled");
roles.insert(RoleActionTypeIds, "actionTypeIds");
roles.insert(RoleMediaIcon, "mediaIcon");
return roles;
@ -72,6 +91,22 @@ void BrowserItems::addBrowserItem(BrowserItem *browserItem)
emit countChanged();
}
void BrowserItems::removeItem(BrowserItem *browserItem)
{
int idx = m_list.indexOf(browserItem);
if (idx < 0) {
return;
}
beginRemoveRows(QModelIndex(), idx, idx);
m_list.takeAt(idx)->deleteLater();
endRemoveRows();
}
QList<BrowserItem *> BrowserItems::list() const
{
return m_list;
}
void BrowserItems::setBusy(bool busy)
{
if (m_busy != busy) {
@ -79,3 +114,21 @@ void BrowserItems::setBusy(bool busy)
emit busyChanged();
}
}
BrowserItem *BrowserItems::get(int index) const
{
if (index < 0 || index >= m_list.count()) {
return nullptr;
}
return m_list.at(index);
}
BrowserItem *BrowserItems::getBrowserItem(const QString &itemId)
{
foreach (BrowserItem *item, m_list) {
if (item->id() == itemId) {
return item;
}
}
return nullptr;
}

View File

@ -2,6 +2,7 @@
#define BROWSERITEMS_H
#include <QAbstractListModel>
#include <QUuid>
class BrowserItem;
@ -19,14 +20,19 @@ public:
RoleThumbnail,
RoleBrowsable,
RoleExecutable,
RoleDisabled,
RoleActionTypeIds,
RoleMediaIcon,
};
Q_ENUM(Roles)
explicit BrowserItems(QObject *parent = nullptr);
explicit BrowserItems(const QUuid &deviceId, const QString &itemId, QObject *parent = nullptr);
virtual ~BrowserItems() override;
QUuid deviceId() const;
QString itemId() const;
bool busy() const;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
@ -34,10 +40,14 @@ public:
virtual QHash<int, QByteArray> roleNames() const override;
virtual void addBrowserItem(BrowserItem *browserItem);
void removeItem(BrowserItem *browserItem);
QList<BrowserItem*> list() const;
void setBusy(bool busy);
// Q_INVOKABLE virtual BrowserItem* get(int index) const;
// Q_INVOKABLE virtual BrowserItem* getBrowserItem(const QString &itemId);
Q_INVOKABLE virtual BrowserItem* get(int index) const;
Q_INVOKABLE virtual BrowserItem* getBrowserItem(const QString &itemId);
// void clear();
@ -48,6 +58,9 @@ signals:
protected:
bool m_busy = false;
QList<BrowserItem*> m_list;
QUuid m_deviceId;
QString m_itemId;
};
#endif // BROWSERITEMS_H

View File

@ -261,6 +261,20 @@ void DeviceClass::setActionTypes(ActionTypes *actionTypes)
emit actionTypesChanged();
}
ActionTypes *DeviceClass::browserItemActionTypes() const
{
return m_browserItemActionTypes;
}
void DeviceClass::setBrowserItemActionTypes(ActionTypes *browserActionTypes)
{
if (m_browserItemActionTypes) {
m_browserItemActionTypes->deleteLater();
}
m_browserItemActionTypes = browserActionTypes;
emit browserItemActionTypesChanged();
}
bool DeviceClass::hasActionType(const QString &actionTypeId)
{
foreach (ActionType *actionType, m_actionTypes->actionTypes()) {

View File

@ -53,6 +53,7 @@ class DeviceClass : public QObject
Q_PROPERTY(StateTypes *stateTypes READ stateTypes NOTIFY stateTypesChanged)
Q_PROPERTY(EventTypes *eventTypes READ eventTypes NOTIFY eventTypesChanged)
Q_PROPERTY(ActionTypes *actionTypes READ actionTypes NOTIFY actionTypesChanged)
Q_PROPERTY(ActionTypes *browserItemActionTypes READ browserItemActionTypes NOTIFY browserItemActionTypesChanged)
public:
@ -113,8 +114,20 @@ public:
ActionTypes *actionTypes() const;
void setActionTypes(ActionTypes *actionTypes);
ActionTypes *browserItemActionTypes() const;
void setBrowserItemActionTypes(ActionTypes *browserActionTypes);
Q_INVOKABLE bool hasActionType(const QString &actionTypeId);
signals:
void paramTypesChanged();
void settingsTypesChanged();
void discoveryParamTypesChanged();
void stateTypesChanged();
void eventTypesChanged();
void actionTypesChanged();
void browserItemActionTypesChanged();
private:
QUuid m_id;
QUuid m_vendorId;
@ -132,13 +145,6 @@ private:
StateTypes *m_stateTypes = nullptr;
EventTypes *m_eventTypes = nullptr;
ActionTypes *m_actionTypes = nullptr;
signals:
void paramTypesChanged();
void settingsTypesChanged();
void discoveryParamTypesChanged();
void stateTypesChanged();
void eventTypesChanged();
void actionTypesChanged();
ActionTypes *m_browserItemActionTypes = nullptr;
};
#endif // DEVICECLASS_H

View File

@ -26,6 +26,8 @@ RowLayout {
imageSource: "../images/media-skip-backward.svg"
longpressImageSource: "../images/media-seek-backward.svg"
enabled: root.playbackState.value !== "Stopped"
opacity: enabled ? 1 : .5
repeat: true
onClicked: {
root.executeAction("skipBack")
@ -61,6 +63,7 @@ RowLayout {
imageSource: "../images/media-skip-forward.svg"
longpressImageSource: "../images/media-seek-forward.svg"
enabled: root.playbackState.value !== "Stopped"
opacity: enabled ? 1 : .5
repeat: true
onClicked: {
root.executeAction("skipNext")

View File

@ -3,10 +3,9 @@ import QtQuick.Controls 2.1
import QtQuick.Layouts 1.1
import QtQuick.Controls.Material 2.1
ToolBar {
Item {
id: root
Material.elevation: 1
implicitHeight: toolBar.implicitHeight + infoPane.height
property string text
property alias backButtonVisible: backButton.visible
property alias menuButtonVisible: menuButton.visible
@ -15,33 +14,103 @@ ToolBar {
signal backPressed();
signal menuPressed();
RowLayout {
id: layout
anchors { fill: parent; leftMargin: app.margins; rightMargin: app.margins }
function showInfo(text, isError, isSticky) {
if (isError === undefined) isError = false;
if (isSticky === undefined) isSticky = false;
HeaderButton {
id: menuButton
objectName: "headerMenuButton"
imageSource: "../images/navigation-menu.svg"
visible: false
onClicked: root.menuPressed();
}
infoPane.text = text;
infoPane.isError = isError;
infoPane.isSticky = isSticky;
HeaderButton {
id: backButton
objectName: "backButton"
imageSource: "../images/back.svg"
onClicked: root.backPressed();
}
Label {
id: label
Layout.fillWidth: true
Layout.fillHeight: true
verticalAlignment: Text.AlignVCenter
font.pixelSize: app.mediumFont
elide: Text.ElideRight
text: root.text
color: app.headerForegroundColor
if (!isSticky) {
infoPaneTimer.start();
}
}
ToolBar {
id: toolBar
Material.elevation: 3
anchors { left: parent.left; top: parent.top; right: parent.right }
RowLayout {
id: layout
anchors { fill: parent; leftMargin: app.margins; rightMargin: app.margins }
HeaderButton {
id: menuButton
objectName: "headerMenuButton"
imageSource: "../images/navigation-menu.svg"
visible: false
onClicked: root.menuPressed();
}
HeaderButton {
id: backButton
objectName: "backButton"
imageSource: "../images/back.svg"
onClicked: root.backPressed();
}
Label {
id: label
Layout.fillWidth: true
Layout.fillHeight: true
verticalAlignment: Text.AlignVCenter
font.pixelSize: app.mediumFont
elide: Text.ElideRight
text: root.text
color: app.headerForegroundColor
}
}
}
Pane {
id: infoPane
Material.elevation: 1
property string text
property bool isError: false
property bool isSticky: false
property bool shown: isSticky || infoPaneTimer.running
visible: height > 0
height: shown ? contentRow.implicitHeight : 0
Behavior on height { NumberAnimation {} }
anchors { left: parent.left; top: toolBar.bottom; right: parent.right }
padding: 0
contentItem: Rectangle {
color: infoPane.isError ? "red" : app.accentColor
implicitHeight: contentRow.implicitHeight
RowLayout {
id: contentRow
anchors { left: parent.left; top: parent.top; right: parent.right; leftMargin: app.margins; rightMargin: app.margins }
Item {
Layout.fillWidth: true
height: app.iconSize
}
Label {
text: infoPane.text
visible: infoPane.alertState
font.pixelSize: app.smallFont
color: "white"
}
ColorIcon {
height: app.iconSize / 2
width: height
visible: true
color: "white"
name: "../images/dialog-warning-symbolic.svg"
}
}
}
}
Timer {
id: infoPaneTimer
interval: 5000
repeat: false
running: false
}
}

View File

@ -28,6 +28,8 @@ SwipeDelegate {
property alias additionalItem: additionalItemContainer.children
property alias busy: busyIndicator.running
signal deleteClicked()
contentItem: RowLayout {
@ -52,6 +54,13 @@ SwipeDelegate {
color: root.iconColor
visible: root.fallbackIcon && (!root.iconName || icon.status === Image.Error)
}
BusyIndicator {
id: busyIndicator
anchors.centerIn: parent
visible: running
running: false
}
}
ColumnLayout {

View File

@ -14,157 +14,189 @@ DeviceListPageBase {
onBackPressed: pageStack.pop()
}
ListView {
Flickable {
anchors.fill: parent
model: root.devicesProxy
contentHeight: contentGrid.implicitHeight
delegate: ItemDelegate {
id: itemDelegate
GridLayout {
id: contentGrid
width: parent.width
columns: Math.ceil(width / 500)
rowSpacing: 0
columnSpacing: 0
property bool inline: width > 500
Repeater {
model: root.devicesProxy
property Device device: devicesProxy.get(index);
property DeviceClass deviceClass: device.deviceClass
delegate: ItemDelegate {
id: itemDelegate
Layout.preferredWidth: contentGrid.width / contentGrid.columns
readonly property StateType playbackStateType: deviceClass.stateTypes.findByName("playbackStatus")
readonly property State playbackState: playbackStateType ? device.states.getState(playbackStateType.id) : null
property bool inline: width > 500
bottomPadding: index === ListView.view.count - 1 ? topPadding : 0
contentItem: Pane {
id: contentItem
Material.elevation: 2
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
property Device device: devicesProxy.get(index);
property DeviceClass deviceClass: device.deviceClass
contentItem: ItemDelegate {
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
contentItem: ColumnLayout {
spacing: 0
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: app.mediumFont + app.margins
color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05)
RowLayout {
anchors { verticalCenter: parent.verticalCenter; left: parent.left; right: parent.right; margins: app.margins }
Label {
Layout.fillWidth: true
text: model.name
elide: Text.ElideRight
}
ColorIcon {
Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height
name: "../images/battery/battery-020.svg"
visible: itemDelegate.deviceClass.interfaces.indexOf("battery") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("batteryCritical").id).value === true
}
ColorIcon {
Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height
name: "../images/dialog-warning-symbolic.svg"
visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false
color: "red"
}
}
readonly property StateType playbackStateType: deviceClass.stateTypes.findByName("playbackStatus")
readonly property State playbackState: playbackStateType ? device.states.getState(playbackStateType.id) : null
}
RowLayout {
ColumnLayout {
id: leftColummn
Layout.margins: app.margins
Label {
Layout.fillWidth: true
text: itemDelegate.playbackState.value === "Stopped" ?
qsTr("No playback")
: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("title").id).value
horizontalAlignment: Text.AlignHCenter
// font.pixelSize: app.largeFont
elide: Text.ElideRight
}
Label {
Layout.fillWidth: true
text: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("artist").id).value
font.pixelSize: app.smallFont
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
Label {
Layout.fillWidth: true
text: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("collection").id).value
horizontalAlignment: Text.AlignHCenter
font.pixelSize: app.smallFont
elide: Text.ElideRight
}
MediaControls {
visible: itemDelegate.deviceClass.interfaces.indexOf("mediacontroller") >= 0
device: itemDelegate.device
}
}
Item {
Layout.preferredHeight: leftColummn.height + app.margins * 2
Layout.preferredWidth: height * .7
readonly property StateType playerTypeStateType: deviceClass.stateTypes.findByName("playerType")
readonly property State playerTypeState: playerTypeStateType ? device.states.getState(playerTypeStateType.id) : null
Item {
id: artworkContainer
anchors.fill: parent
Image {
id: artworkImage
width: artworkImage.sourceSize.width * height / artworkImage.sourceSize.height
anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
readonly property StateType artworkStateType: device ? device.deviceClass.stateTypes.findByName("artwork") : null
readonly property State artworkState: artworkStateType ? device.states.getState(artworkStateType.id) : null
source: artworkState ? artworkState.value : ""
}
}
bottomPadding: index === ListView.view.count - 1 ? topPadding : 0
contentItem: Pane {
id: contentItem
Material.elevation: 2
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
contentItem: ItemDelegate {
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
contentItem: ColumnLayout {
spacing: 0
Rectangle {
id: maskRect
anchors.centerIn: parent
height: parent.width
width: parent.height
gradient: Gradient {
GradientStop { position: 0; color: "transparent" }
GradientStop { position: 1; color: "red" }
}
}
ShaderEffect {
anchors.fill: parent
property variant source: ShaderEffectSource {
sourceItem: artworkContainer
hideSource: true
}
property variant mask: ShaderEffectSource {
sourceItem: maskRect
hideSource: true
}
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform sampler2D mask;
void main(void)
{
highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
highp float alpha = texture2D(mask, vec2(qt_TexCoord0.y, qt_TexCoord0.x)).a;
sourceColor *= alpha;
gl_FragColor = sourceColor;
Layout.fillWidth: true
Layout.preferredHeight: app.mediumFont + app.margins
color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05)
RowLayout {
anchors { verticalCenter: parent.verticalCenter; left: parent.left; right: parent.right; margins: app.margins }
Label {
Layout.fillWidth: true
text: model.name
elide: Text.ElideRight
}
"
ColorIcon {
Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height
name: "../images/battery/battery-020.svg"
visible: itemDelegate.deviceClass.interfaces.indexOf("battery") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("batteryCritical").id).value === true
}
ColorIcon {
Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height
name: "../images/dialog-warning-symbolic.svg"
visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false
color: "red"
}
}
}
RowLayout {
ColumnLayout {
id: leftColummn
Layout.margins: app.margins
Label {
Layout.fillWidth: true
text: itemDelegate.playbackState.value === "Stopped" ?
qsTr("No playback")
: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("title").id).value
horizontalAlignment: Text.AlignHCenter
// font.pixelSize: app.largeFont
elide: Text.ElideRight
}
Label {
Layout.fillWidth: true
text: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("artist").id).value
font.pixelSize: app.smallFont
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
Label {
Layout.fillWidth: true
text: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("collection").id).value
horizontalAlignment: Text.AlignHCenter
font.pixelSize: app.smallFont
elide: Text.ElideRight
}
MediaControls {
visible: itemDelegate.deviceClass.interfaces.indexOf("mediacontroller") >= 0
device: itemDelegate.device
}
}
Item {
Layout.preferredHeight: leftColummn.height + app.margins * 2
Layout.preferredWidth: height * .7
Item {
id: artworkContainer
anchors.fill: parent
Image {
id: artworkImage
width: artworkImage.sourceSize.width * height / artworkImage.sourceSize.height
anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
readonly property StateType artworkStateType: device ? device.deviceClass.stateTypes.findByName("artwork") : null
readonly property State artworkState: artworkStateType ? device.states.getState(artworkStateType.id) : null
source: artworkState ? artworkState.value : ""
}
Rectangle {
visible: artworkImage.status !== Image.Ready || artworkImage.source === ""
anchors.fill: parent
color: "black"
}
ColorIcon {
width: Math.min(parent.width, parent.height) - app.margins * 2
height: width
anchors.centerIn: parent
name: itemDelegate.playerTypeState && itemDelegate.playerTypeState.value === "video" ? "../images/stock_video.svg" : "../images/stock_music.svg"
visible: artworkImage.status !== Image.Ready || artworkImage.source === ""
color: "white"
}
}
Rectangle {
id: maskRect
anchors.centerIn: parent
height: parent.width
width: parent.height
gradient: Gradient {
GradientStop { position: 0; color: "#00FF0000" }
GradientStop { position: 0.2; color: "#15FF0000" }
GradientStop { position: 1; color: "#FFFF0000" }
}
}
ShaderEffect {
anchors.fill: parent
property variant source: ShaderEffectSource {
sourceItem: artworkContainer
hideSource: true
}
property variant mask: ShaderEffectSource {
sourceItem: maskRect
hideSource: true
}
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform sampler2D mask;
void main(void)
{
highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
highp float alpha = texture2D(mask, vec2(qt_TexCoord0.y, qt_TexCoord0.x)).a;
sourceColor *= alpha;
gl_FragColor = sourceColor;
}
"
}
}
}
}
onClicked: {
enterPage(index, false)
}
}
}
onClicked: {
enterPage(index, false)
}
}
}
}
}
}
}

View File

@ -17,9 +17,40 @@ Page {
d.model = engine.deviceManager.browseDevice(root.device.id, root.nodeId);
}
function executeBrowserItem(itemId) {
d.pendingItemId = itemId
d.pendingBrowserItemId = engine.deviceManager.executeBrowserItem(root.device.id, itemId)
}
function executeBrowserItemAction(itemId, actionTypeId, params) {
d.pendingItemId = itemId
d.pendingBrowserItemId = engine.deviceManager.executeBrowserItemAction(root.device.id, itemId, actionTypeId, params)
}
QtObject {
id: d
property BrowserItems model: null
property int pendingBrowserItemId: -1
property string pendingItemId: ""
}
Connections {
target: engine.deviceManager
onExecuteBrowserItemReply: actionExecuted(params)
onExecuteBrowserItemActionReply: actionExecuted(params)
}
function actionExecuted(params) {
print("Execute Action reply:", params, params.id, params["id"], d.pendingBrowserItemId)
if (params.id === d.pendingBrowserItemId) {
d.pendingBrowserItemId = -1;
d.pendingItemId = ""
print("yep finished")
if (params.status !== "success") {
header.showInfo(params.error, true);
} else if (params.params.deviceError !== "DeviceErrorNoError") {
header.showInfo(qsTr("Error: %1").arg(params.params.deviceError), true)
}
}
engine.deviceManager.refreshBrowserItems(d.model)
}
ListView {
@ -36,16 +67,25 @@ Page {
prominentSubText: false
iconName: model.thumbnail
fallbackIcon: "../images/browser/" + model.icon + ".svg"
enabled: model.browsable || model.executable
enabled: !model.disabled
busy: d.pendingItemId === model.id
onClicked: {
print("clicked:", model.id)
if (model.executable) {
engine.deviceManager.executeBrowserItem(root.device.id, model.id)
root.executeBrowserItem(model.id)
} else if (model.browsable) {
pageStack.push(Qt.resolvedUrl("DeviceBrowserPage.qml"), {device: root.device, nodeId: model.id})
}
}
onPressAndHold: {
print("show actions:", model.actionTypeIds)
var popup = actionDialogComponent.createObject(this, {title: model.displayName, itemId: model.id, actionTypeIds: model.actionTypeIds});
popup.open()
// root.device.deviceClass.browserItemActionTypes.getActionType()
}
}
BusyIndicator {
@ -55,4 +95,32 @@ Page {
}
}
Component {
id: actionDialogComponent
MeaDialog {
id: actionDialog
property string itemId
property alias actionTypeIds: actionListView.model
ListView {
id: actionListView
Layout.fillWidth: true
implicitHeight: count * 50
interactive: contentHeight > height
clip: true
delegate: NymeaListItemDelegate {
width: parent.width
text: actionType.displayName
progressive: false
property ActionType actionType: root.device.deviceClass.browserItemActionTypes.getActionType(modelData)
onClicked: {
var params = []
root.executeBrowserItemAction(actionDialog.itemId, actionType.id, params)
actionDialog.close()
}
}
}
}
}
}

View File

@ -24,6 +24,12 @@ DevicePageBase {
}
}
Component.onCompleted: {
if (root.deviceClass.browsable && playbackState.value === "Stopped") {
swipeView.currentIndex = 1;
}
}
function stateValue(name) {
var stateType = root.deviceClass.stateTypes.findByName(name);
if (!stateType) return null
@ -36,8 +42,36 @@ DevicePageBase {
engine.deviceManager.executeAction(device.id, actionTypeId, params)
}
function executeBrowserItem(itemId) {
d.pendingItemId = itemId
d.pendingBrowserItemId = engine.deviceManager.executeBrowserItem(device.id, itemId);
}
readonly property State playbackState: device.states.getState(deviceClass.stateTypes.findByName("playbackStatus").id)
QtObject {
id: d
property int pendingBrowserItemId: -1
property string pendingItemId: ""
}
Connections {
target: engine.deviceManager
onExecuteBrowserItemReply: {
print("Execute reply:", params, params.id, params["id"], d.pendingBrowserItemId)
if (params.id === d.pendingBrowserItemId) {
d.pendingBrowserItemId = -1;
d.pendingItemId = ""
print("yep finished")
if (params.params.deviceError === "DeviceErrorNoError") {
swipeView.currentIndex = 0;
} else {
header.showInfo(qsTr("Error: %").arg(params.params.deviceError), true)
}
}
}
}
SwipeView {
id: swipeView
anchors.fill: parent
@ -133,7 +167,7 @@ DevicePageBase {
onClicked: {
print("clicked:", model.id)
if (model.executable) {
engine.deviceManager.executeBrowserItem(root.device.id, model.id)
root.executeBrowserItem(model.id)
} else if (model.browsable) {
internalPageStack.push(internalBrowserPage, {device: root.device, nodeId: model.id})
}