diff options
| -rw-r--r-- | app/app.pro | 3 | ||||
| -rw-r--r-- | app/pebbledinterface.cpp | 140 | ||||
| -rw-r--r-- | app/pebbledinterface.h | 29 | ||||
| -rw-r--r-- | app/qml/pages/AppConfigPage.qml | 9 | ||||
| -rw-r--r-- | app/qml/pages/InstallAppDialog.qml | 66 | ||||
| -rw-r--r-- | app/qml/pages/WatchPage.qml | 125 |
6 files changed, 347 insertions, 25 deletions
diff --git a/app/app.pro b/app/app.pro index ddf2dba..d375800 100644 --- a/app/app.pro +++ b/app/app.pro @@ -25,4 +25,5 @@ OTHER_FILES += \ qml/images/* \ pebble.desktop \ pebble.png \ - qml/pages/AppConfigPage.qml + qml/pages/AppConfigPage.qml \ + qml/pages/InstallAppDialog.qml diff --git a/app/pebbledinterface.cpp b/app/pebbledinterface.cpp index 0aceaa0..588e24a 100644 --- a/app/pebbledinterface.cpp +++ b/app/pebbledinterface.cpp @@ -24,6 +24,13 @@ PebbledInterface::PebbledInterface(QObject *parent) : this, &PebbledInterface::connectedChanged); connect(watch, &OrgPebbledWatchInterface::AppUuidChanged, this, &PebbledInterface::appUuidChanged); + connect(watch, &OrgPebbledWatchInterface::AppSlotsChanged, + this, &PebbledInterface::refreshAppSlots); + connect(watch, &OrgPebbledWatchInterface::AllAppsChanged, + this, &PebbledInterface::refreshAllApps); + + connect(watch, &OrgPebbledWatchInterface::ConnectedChanged, + this, &PebbledInterface::onWatchConnectedChanged); // simulate connected change on active changed // as the daemon might not had a chance to send 'connectedChanged' @@ -46,6 +53,11 @@ PebbledInterface::PebbledInterface(QObject *parent) : } else { qWarning() << unit.error().message(); } + + if (watch->isValid()) { + refreshAllApps(); + refreshAppSlots(); + } } void PebbledInterface::getUnitProperties() @@ -161,8 +173,27 @@ void PebbledInterface::reconnect() QUrl PebbledInterface::configureApp(const QString &uuid) { qDebug() << Q_FUNC_INFO << uuid; - QString url = watch->StartAppConfiguration(uuid); - return QUrl(url); + QDBusPendingReply<QString> reply = watch->StartAppConfiguration(uuid); + reply.waitForFinished(); + if (reply.isError()) { + qWarning() << "Received error:" << reply.error().message(); + return QUrl(); + } else { + return QUrl(reply.value()); + } +} + +bool PebbledInterface::isAppInstalled(const QString &uuid) +{ + QUuid u(uuid); + + foreach (const QString &s, _appSlots) { + if (QUuid(s) == u) { + return true; + } + } + + return false; } void PebbledInterface::setAppConfiguration(const QString &uuid, const QString &data) @@ -170,3 +201,108 @@ void PebbledInterface::setAppConfiguration(const QString &uuid, const QString &d qDebug() << Q_FUNC_INFO << uuid << data; watch->SendAppConfigurationData(uuid, data); } + +void PebbledInterface::launchApp(const QString &uuid) +{ + qDebug() << Q_FUNC_INFO << uuid; + QDBusPendingReply<> reply = watch->LaunchApp(uuid); + reply.waitForFinished(); + + // TODO Terrible hack; need to give time for the watch to open the app + // A better solution would be to wait until AppUuidChanged is generated. + int sleep_count = 0; + while (watch->appUuid() != uuid && sleep_count < 5) { + QThread::sleep(1); + sleep_count++; + } +} + +void PebbledInterface::uploadApp(const QString &uuid, int slot) +{ + qDebug() << Q_FUNC_INFO << uuid << slot; + QDBusPendingReply<> reply = watch->UploadApp(uuid, slot); + reply.waitForFinished(); +} + +void PebbledInterface::unloadApp(int slot) +{ + qDebug() << Q_FUNC_INFO << slot; + QDBusPendingReply<> reply = watch->UnloadApp(slot); + reply.waitForFinished(); +} + +QStringList PebbledInterface::appSlots() const +{ + return _appSlots; +} + +QVariantList PebbledInterface::allApps() const +{ + return _apps; +} + +QVariantMap PebbledInterface::appInfoByUuid(const QString &uuid) const +{ + int index = _appsByUuid.value(QUuid(uuid), -1); + if (index >= 0) { + return _apps[index].toMap(); + } else { + return QVariantMap(); + } +} + +void PebbledInterface::onWatchConnectedChanged() +{ + qDebug() << Q_FUNC_INFO; + if (watch->connected()) { + refreshAllApps(); + refreshAppSlots(); + } +} + +void PebbledInterface::refreshAppSlots() +{ + qDebug() << "refreshing app slots list"; + _appSlots = watch->appSlots(); + emit appSlotsChanged(); +} + +void PebbledInterface::refreshAllApps() +{ + _apps.clear(); + _appsByUuid.clear(); + + qDebug() << "refreshing all apps list"; + + const QVariantList l = watch->allApps(); + foreach (const QVariant &v, l) { + QVariantMap orig = qdbus_cast<QVariantMap>(v.value<QDBusArgument>()); + QUuid uuid = orig.value("uuid").toUuid(); + if (uuid.isNull()) { + qWarning() << "Invalid app uuid received" << orig; + continue; + } + + QVariantMap m; + m.insert("uuid", uuid.toString()); + m.insert("shortName", orig.value("short-name")); + m.insert("longName", orig.value("long-name")); + + _apps.append(QVariant::fromValue(m)); + } + + std::sort(_apps.begin(), _apps.end(), [](const QVariant &v1, const QVariant &v2) { + const QVariantMap &a = v1.toMap(); + const QVariantMap &b = v2.toMap(); + return a.value("shortName").toString() < b.value("shortName").toString(); + }); + + for (int i = 0; i < _apps.size(); ++i) { + QUuid uuid = _apps[i].toMap().value("uuid").toUuid(); + _appsByUuid.insert(uuid, i); + } + + qDebug() << _appsByUuid.size() << "different app uuids known"; + + emit allAppsChanged(); +} diff --git a/app/pebbledinterface.h b/app/pebbledinterface.h index f506e67..e468505 100644 --- a/app/pebbledinterface.h +++ b/app/pebbledinterface.h @@ -3,6 +3,8 @@ #include <QObject> #include <QUrl> +#include <QHash> +#include <QUuid> #include <QDBusInterface> class OrgPebbledWatchInterface; @@ -17,6 +19,9 @@ class PebbledInterface : public QObject Q_PROPERTY(QString address READ address NOTIFY addressChanged) Q_PROPERTY(QString appUuid READ appUuid NOTIFY appUuidChanged) + Q_PROPERTY(QStringList appSlots READ appSlots NOTIFY appSlotsChanged) + Q_PROPERTY(QVariantList allApps READ allApps NOTIFY allAppsChanged) + public: explicit PebbledInterface(QObject *parent = 0); @@ -27,6 +32,15 @@ public: QString address() const; QString appUuid() const; + QStringList appSlots() const; + QVariantList allApps() const; + + Q_INVOKABLE QVariantMap appInfoByUuid(const QString& uuid) const; + + Q_INVOKABLE QUrl configureApp(const QString &uuid); + + Q_INVOKABLE bool isAppInstalled(const QString &uuid); + signals: void enabledChanged(); void activeChanged(); @@ -34,6 +48,8 @@ signals: void nameChanged(); void addressChanged(); void appUuidChanged(); + void appSlotsChanged(); + void allAppsChanged(); public slots: void setEnabled(bool); @@ -43,18 +59,29 @@ public slots: void disconnect(); void reconnect(); - QUrl configureApp(const QString &uuid); void setAppConfiguration(const QString &uuid, const QString &data); + void launchApp(const QString &uuid); + void uploadApp(const QString &uuid, int slot); + void unloadApp(int slot); + private slots: + void onWatchConnectedChanged(); void getUnitProperties(); void onPropertiesChanged(QString interface, QMap<QString, QVariant> changed, QStringList invalidated); + void refreshAppSlots(); + void refreshAllApps(); private: QDBusInterface *systemd; OrgPebbledWatchInterface *watch; QDBusObjectPath unitPath; QVariantMap unitProperties; + + // Cached properties + QStringList _appSlots; + QVariantList _apps; + QHash<QUuid, int> _appsByUuid; }; #endif // PEBBLEDINTERFACE_H diff --git a/app/qml/pages/AppConfigPage.qml b/app/qml/pages/AppConfigPage.qml index 7b969a3..10fbe05 100644 --- a/app/qml/pages/AppConfigPage.qml +++ b/app/qml/pages/AppConfigPage.qml @@ -7,14 +7,14 @@ Page { id: appConfigPage property alias url: webview.url - property string uuid + property string name SilicaWebView { id: webview anchors.fill: parent header: PageHeader { - title: "Configuring " + uuid + title: "Configuring " + name } onNavigationRequested: { @@ -31,4 +31,9 @@ Page { } } } + + ViewPlaceholder { + enabled: url == "" + text: qsTr("No configuration settings available") + } } diff --git a/app/qml/pages/InstallAppDialog.qml b/app/qml/pages/InstallAppDialog.qml new file mode 100644 index 0000000..3a3c0b1 --- /dev/null +++ b/app/qml/pages/InstallAppDialog.qml @@ -0,0 +1,66 @@ +import QtQuick 2.0 +import QtQml 2.1 +import Sailfish.Silica 1.0 + +Dialog { + id: installAppPage + + property string selectedUuid; + + SilicaListView { + id: appList + anchors.fill: parent + + header: DialogHeader { + title: qsTr("Install app") + defaultAcceptText: qsTr("Install") + } + + VerticalScrollDecorator { flickable: flickable } + + currentIndex: -1 + + delegate: ListItem { + id: appDelegate + contentHeight: Theme.itemSizeSmall + + property string uuid: modelData.uuid + property bool alreadyInstalled: pebbled.isAppInstalled(uuid) + + Image { + id: appImage + anchors { + top: parent.top + left: parent.left + leftMargin: Theme.paddingLarge + } + width: Theme.itemSizeSmall + } + + Label { + id: appName + anchors { + left: appImage.right + leftMargin: Theme.paddingMedium + right: parent.right + rightMargin: Theme.paddiumLarge + verticalCenter: parent.verticalCenter + } + text: modelData.longName + color: appDelegate.highlighted ? Theme.highlightColor : Theme.primaryColor + } + + onClicked: { + appList.currentIndex = index + if (!alreadyInstalled) { + selectedUuid = uuid + accept(); + } + } + } + + model: pebbled.allApps + } + + canAccept: appList.currentIndex >= 0 && !appList.currentItem.alreadyInstalled +} diff --git a/app/qml/pages/WatchPage.qml b/app/qml/pages/WatchPage.qml index 8169507..ce9d636 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -34,7 +34,7 @@ import QtQml 2.1 import Sailfish.Silica 1.0 Page { - id: page + id: watchPage SilicaFlickable { id: flickable @@ -45,9 +45,8 @@ Page { Column { id: column + width: watchPage.width - width: page.width - spacing: Theme.paddingLarge PageHeader { title: pebbled.name } @@ -77,36 +76,124 @@ Page { } } + Item { + height: Theme.paddingMedium + } Label { - text: qsTr("App configuration") + text: qsTr("Installed applications") font.family: Theme.fontFamilyHeading color: Theme.highlightColor anchors.right: parent.right anchors.rightMargin: Theme.paddingMedium } - Button { - text: "Configure current app" - anchors { - left: parent.left - right: parent.right - margins: Theme.paddingLarge - } - onClicked: { - var uuid = pebbled.appUuid; - console.log("going to configureApp " + uuid); - var url = pebbled.configureApp(uuid); - console.log("obtained configure URL " + url); - if (url) { + Repeater { + id: slotsRepeater + model: pebbled.appSlots + + ListItem { + id: slotDelegate + menu: slotMenu + contentHeight: Theme.itemSizeSmall + + property bool isEmptySlot: modelData === "" + property var appInfo: pebbled.appInfoByUuid(modelData) + property bool isKnownApp: appInfo.hasOwnProperty("uuid") + property bool busy: false + + function configure() { + var uuid = modelData; + pebbled.launchApp(uuid); + console.log("going to call configure on app with uuid " + uuid); + var url = pebbled.configureApp(uuid); + console.log("received url: " + url); pageStack.push(Qt.resolvedUrl("AppConfigPage.qml"), { url: url, - uuid: uuid + name: appInfo.longName }); } + + function remove() { + remorseAction(qsTr("Uninstalling"), function() { + busy = true; + pebbled.unloadApp(index); + }); + } + + function install() { + var dialog = pageStack.push(Qt.resolvedUrl("InstallAppDialog.qml")); + dialog.accepted.connect(function() { + var uuid = dialog.selectedUuid; + + if (pebbled.isAppInstalled(uuid)) { + console.warn("uuid already installed"); + return; + } + + var slot = index; + console.log("installing " + uuid + " into " + slot); + busy = true; + pebbled.uploadApp(uuid, slot); + }); + + } + + Image { + id: slotImage + anchors { + top: parent.top + left: parent.left + leftMargin: Theme.paddingLarge + } + width: Theme.itemSizeSmall + } + + BusyIndicator { + id: slotBusy + anchors.centerIn: slotImage + running: slotDelegate.busy + } + + Label { + id: slotName + anchors { + left: slotImage.right + leftMargin: Theme.paddingMedium + right: parent.right + rightMargin: Theme.paddiumLarge + verticalCenter: parent.verticalCenter + } + text: isEmptySlot ? qsTr("(empty slot)") : (isKnownApp ? appInfo.longName : qsTr("(slot in use by unknown app)")) + color: slotDelegate.highlighted ? Theme.highlightColor : Theme.primaryColor + onTextChanged: slotDelegate.busy = false; + } + + Component { + id: slotMenu + ContextMenu { + MenuItem { + text: qsTr("Configure...") + visible: !isEmptySlot && isKnownApp + onClicked: configure(); + } + MenuItem { + text: qsTr("Uninstall") + visible: !isEmptySlot + onClicked: remove(); + } + } + } + + onClicked: { + if (isEmptySlot) { + install(); + } else { + showMenu(); + } + } } } - } } } |
