summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier <dev.git@javispedro.com>2014-12-14 00:45:55 +0100
committerJavier <dev.git@javispedro.com>2014-12-14 00:45:55 +0100
commitdf30ca18eebd2dfec03c589b607d45a5891cf2b2 (patch)
tree2d916ddf017f299759e1c2e41d6861b3ee02be06
parent93ec9b745032b4e9c02756dd0361de3a364b6742 (diff)
add UI to install/remove apps from watch
-rw-r--r--app/app.pro3
-rw-r--r--app/pebbledinterface.cpp140
-rw-r--r--app/pebbledinterface.h29
-rw-r--r--app/qml/pages/AppConfigPage.qml9
-rw-r--r--app/qml/pages/InstallAppDialog.qml66
-rw-r--r--app/qml/pages/WatchPage.qml125
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();
+ }
+ }
}
}
-
}
}
}