From 69822e4dcf541a52e4202d5ff566364fb90e6ec0 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 3 Dec 2014 00:48:19 +0100 Subject: implement UI for JS app configuration --- app/qml/pages/WatchPage.qml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'app/qml/pages/WatchPage.qml') diff --git a/app/qml/pages/WatchPage.qml b/app/qml/pages/WatchPage.qml index 90e5ec9..8169507 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -77,6 +77,36 @@ Page { } } + + Label { + text: qsTr("App configuration") + 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) { + pageStack.push(Qt.resolvedUrl("AppConfigPage.qml"), { + url: url, + uuid: uuid + }); + } + } + } + } } } -- cgit v1.2.3 From df30ca18eebd2dfec03c589b607d45a5891cf2b2 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 00:45:55 +0100 Subject: add UI to install/remove apps from watch --- app/app.pro | 3 +- app/pebbledinterface.cpp | 140 ++++++++++++++++++++++++++++++++++++- app/pebbledinterface.h | 29 +++++++- app/qml/pages/AppConfigPage.qml | 9 ++- app/qml/pages/InstallAppDialog.qml | 66 +++++++++++++++++ app/qml/pages/WatchPage.qml | 125 ++++++++++++++++++++++++++++----- 6 files changed, 347 insertions(+), 25 deletions(-) create mode 100644 app/qml/pages/InstallAppDialog.qml (limited to 'app/qml/pages/WatchPage.qml') 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 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(v.value()); + 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 #include +#include +#include #include 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 changed, QStringList invalidated); + void refreshAppSlots(); + void refreshAllApps(); private: QDBusInterface *systemd; OrgPebbledWatchInterface *watch; QDBusObjectPath unitPath; QVariantMap unitProperties; + + // Cached properties + QStringList _appSlots; + QVariantList _apps; + QHash _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(); + } + } } } - } } } -- cgit v1.2.3 From f40514fe681f5163deb5f579140ef4f7ac77f5a8 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 03:26:46 +0100 Subject: add icons to the slots managament UI --- app/app.pro | 8 ++- app/pebble.cpp | 12 +++- app/pebbleappiconprovider.cpp | 28 ++++++++ app/pebbleappiconprovider.h | 18 +++++ app/pebbledinterface.cpp | 18 ++++- app/pebbledinterface.h | 6 +- app/qml/pages/AppConfigPage.qml | 14 +++- app/qml/pages/InstallAppDialog.qml | 17 +++-- app/qml/pages/WatchPage.qml | 49 ++++++++++--- app/qml/pebble.qml | 4 -- daemon/appinfo.cpp | 25 ++++++- daemon/appinfo.h | 9 ++- daemon/appmanager.cpp | 142 ++++++++++++++++++++++++++++++++++--- daemon/appmanager.h | 2 + daemon/manager.cpp | 5 ++ 15 files changed, 315 insertions(+), 42 deletions(-) create mode 100644 app/pebbleappiconprovider.cpp create mode 100644 app/pebbleappiconprovider.h (limited to 'app/qml/pages/WatchPage.qml') diff --git a/app/app.pro b/app/app.pro index d375800..48fcf68 100644 --- a/app/app.pro +++ b/app/app.pro @@ -3,16 +3,18 @@ TARGET = pebble CONFIG += sailfishapp QT += dbus -QMAKE_CXXFLAGS += -std=c++0x +CONFIG += c++11 DEFINES += APP_VERSION=\\\"$$VERSION\\\" SOURCES += \ pebble.cpp \ - pebbledinterface.cpp + pebbledinterface.cpp \ + pebbleappiconprovider.cpp HEADERS += \ - pebbledinterface.h + pebbledinterface.h \ + pebbleappiconprovider.h DBUS_INTERFACES += ../org.pebbled.Watch.xml diff --git a/app/pebble.cpp b/app/pebble.cpp index 44f1aeb..41da080 100644 --- a/app/pebble.cpp +++ b/app/pebble.cpp @@ -33,16 +33,22 @@ #include #include "pebbledinterface.h" +#include "pebbleappiconprovider.h" int main(int argc, char *argv[]) { - // Register Pebble daemon interface object on QML side - qmlRegisterType("org.pebbled", 0, 1, "PebbledInterface"); - QScopedPointer app(SailfishApp::application(argc, argv)); + qmlRegisterUncreatableType("org.pebbled", 0, 1, "PebbledInterface", + "Please use pebbled context property"); + QScopedPointer view(SailfishApp::createView()); + QScopedPointer pebbled(new PebbledInterface); + QScopedPointer appicons(new PebbleAppIconProvider(pebbled.data())); + view->rootContext()->setContextProperty("APP_VERSION", APP_VERSION); + view->rootContext()->setContextProperty("pebbled", pebbled.data()); + view->engine()->addImageProvider("pebble-app-icon", appicons.data()); view->setSource(SailfishApp::pathTo("qml/pebble.qml")); view->show(); diff --git a/app/pebbleappiconprovider.cpp b/app/pebbleappiconprovider.cpp new file mode 100644 index 0000000..0e694ff --- /dev/null +++ b/app/pebbleappiconprovider.cpp @@ -0,0 +1,28 @@ +#include +#include +#include "pebbleappiconprovider.h" + +PebbleAppIconProvider::PebbleAppIconProvider(PebbledInterface *interface) + : QQuickImageProvider(QQmlImageProviderBase::Image), pebbled(interface) +{ +} + +QImage PebbleAppIconProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) +{ + QUuid uuid(QUrl::fromPercentEncoding(id.toLatin1())); + QImage img = pebbled->menuIconForApp(uuid); + + if (requestedSize.width() > 0 && requestedSize.height() > 0) { + img = img.scaled(requestedSize, Qt::KeepAspectRatio); + } else if (requestedSize.width() > 0) { + img = img.scaledToWidth(requestedSize.width()); + } else if (requestedSize.height() > 0) { + img = img.scaledToHeight(requestedSize.height()); + } + + if (size) { + *size = img.size(); + } + + return img; +} diff --git a/app/pebbleappiconprovider.h b/app/pebbleappiconprovider.h new file mode 100644 index 0000000..c76641a --- /dev/null +++ b/app/pebbleappiconprovider.h @@ -0,0 +1,18 @@ +#ifndef PEBBLEAPPICONPROVIDER_H +#define PEBBLEAPPICONPROVIDER_H + +#include +#include "pebbledinterface.h" + +class PebbleAppIconProvider : public QQuickImageProvider +{ +public: + explicit PebbleAppIconProvider(PebbledInterface *interface); + + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize); + +private: + PebbledInterface *pebbled; +}; + +#endif // PEBBLEAPPICONPROVIDER_H diff --git a/app/pebbledinterface.cpp b/app/pebbledinterface.cpp index 588e24a..c978dd0 100644 --- a/app/pebbledinterface.cpp +++ b/app/pebbledinterface.cpp @@ -183,7 +183,7 @@ QUrl PebbledInterface::configureApp(const QString &uuid) } } -bool PebbledInterface::isAppInstalled(const QString &uuid) +bool PebbledInterface::isAppInstalled(const QString &uuid) const { QUuid u(uuid); @@ -196,6 +196,11 @@ bool PebbledInterface::isAppInstalled(const QString &uuid) return false; } +QImage PebbledInterface::menuIconForApp(const QUuid &uuid) const +{ + return _appMenuIcons.value(uuid); +} + void PebbledInterface::setAppConfiguration(const QString &uuid, const QString &data) { qDebug() << Q_FUNC_INFO << uuid << data; @@ -210,8 +215,11 @@ void PebbledInterface::launchApp(const QString &uuid) // 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. + QUuid u(uuid); + if (u.isNull()) return; int sleep_count = 0; - while (watch->appUuid() != uuid && sleep_count < 5) { + while (QUuid(watch->appUuid()) != u && sleep_count < 5) { + qDebug() << "Waiting for" << u.toString() << "to launch"; QThread::sleep(1); sleep_count++; } @@ -271,6 +279,7 @@ void PebbledInterface::refreshAllApps() { _apps.clear(); _appsByUuid.clear(); + _appMenuIcons.clear(); qDebug() << "refreshing all apps list"; @@ -288,6 +297,11 @@ void PebbledInterface::refreshAllApps() m.insert("shortName", orig.value("short-name")); m.insert("longName", orig.value("long-name")); + QByteArray pngIcon = orig.value("menu-icon").toByteArray(); + if (!pngIcon.isEmpty()) { + _appMenuIcons.insert(uuid, QImage::fromData(pngIcon, "PNG")); + } + _apps.append(QVariant::fromValue(m)); } diff --git a/app/pebbledinterface.h b/app/pebbledinterface.h index e468505..51efa12 100644 --- a/app/pebbledinterface.h +++ b/app/pebbledinterface.h @@ -5,6 +5,7 @@ #include #include #include +#include #include class OrgPebbledWatchInterface; @@ -39,7 +40,9 @@ public: Q_INVOKABLE QUrl configureApp(const QString &uuid); - Q_INVOKABLE bool isAppInstalled(const QString &uuid); + Q_INVOKABLE bool isAppInstalled(const QString &uuid) const; + + QImage menuIconForApp(const QUuid &uuid) const; signals: void enabledChanged(); @@ -82,6 +85,7 @@ private: QStringList _appSlots; QVariantList _apps; QHash _appsByUuid; + QHash _appMenuIcons; }; #endif // PEBBLEDINTERFACE_H diff --git a/app/qml/pages/AppConfigPage.qml b/app/qml/pages/AppConfigPage.qml index 10fbe05..00eb05c 100644 --- a/app/qml/pages/AppConfigPage.qml +++ b/app/qml/pages/AppConfigPage.qml @@ -11,6 +11,7 @@ Page { SilicaWebView { id: webview + visible: url != "" anchors.fill: parent header: PageHeader { @@ -32,8 +33,17 @@ Page { } } - ViewPlaceholder { - enabled: url == "" + Text { + anchors.centerIn: parent + visible: url == "" text: qsTr("No configuration settings available") + width: parent.width - 2*Theme.paddingLarge + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + font { + pixelSize: Theme.fontSizeLarge + family: Theme.fontFamilyHeading + } + color: Theme.highlightColor } } diff --git a/app/qml/pages/InstallAppDialog.qml b/app/qml/pages/InstallAppDialog.qml index 3a3c0b1..79283a6 100644 --- a/app/qml/pages/InstallAppDialog.qml +++ b/app/qml/pages/InstallAppDialog.qml @@ -27,20 +27,29 @@ Dialog { property string uuid: modelData.uuid property bool alreadyInstalled: pebbled.isAppInstalled(uuid) - Image { - id: appImage + Item { + id: appIcon + width: Theme.itemSizeSmall + height: Theme.itemSizeSmall + anchors { top: parent.top left: parent.left leftMargin: Theme.paddingLarge } - width: Theme.itemSizeSmall + + Image { + id: appImage + anchors.centerIn: parent + source: "image://pebble-app-icon/" + uuid; + scale: 2 + } } Label { id: appName anchors { - left: appImage.right + left: appIcon.right leftMargin: Theme.paddingMedium right: parent.right rightMargin: Theme.paddiumLarge diff --git a/app/qml/pages/WatchPage.qml b/app/qml/pages/WatchPage.qml index ce9d636..3a712ab 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -77,7 +77,8 @@ Page { } Item { - height: Theme.paddingMedium + width: parent.width + height: Theme.paddingLarge } Label { @@ -139,26 +140,49 @@ Page { } - Image { - id: slotImage + Item { + id: slotIcon + width: Theme.itemSizeSmall + height: Theme.itemSizeSmall + anchors { top: parent.top left: parent.left leftMargin: Theme.paddingLarge } - width: Theme.itemSizeSmall - } - BusyIndicator { - id: slotBusy - anchors.centerIn: slotImage - running: slotDelegate.busy + Image { + id: slotImage + anchors.centerIn: parent + source: isKnownApp ? "image://pebble-app-icon/" + modelData : "" + scale: 2 + visible: !isEmptySlot && isKnownApp && !slotBusy.running + } + + Rectangle { + width: 30 + height: 30 + anchors.centerIn: parent + scale: 2 + border { + width: 2 + color: slotDelegate.highlighted ? Theme.highlightColor : Theme.primaryColor + } + color: "transparent" + visible: isEmptySlot && !slotBusy.running + } + + BusyIndicator { + id: slotBusy + anchors.centerIn: parent + running: slotDelegate.busy + } } Label { id: slotName anchors { - left: slotImage.right + left: slotIcon.right leftMargin: Theme.paddingMedium right: parent.right rightMargin: Theme.paddiumLarge @@ -172,6 +196,11 @@ Page { Component { id: slotMenu ContextMenu { + MenuItem { + text: qsTr("Install app...") + visible: isEmptySlot + onClicked: install(); + } MenuItem { text: qsTr("Configure...") visible: !isEmptySlot && isKnownApp diff --git a/app/qml/pebble.qml b/app/qml/pebble.qml index da3bfb5..2e26ebe 100644 --- a/app/qml/pebble.qml +++ b/app/qml/pebble.qml @@ -38,8 +38,4 @@ ApplicationWindow { initialPage: Component { ManagerPage { } } cover: Qt.resolvedUrl("cover/CoverPage.qml") - - PebbledInterface { - id: pebbled - } } diff --git a/daemon/appinfo.cpp b/daemon/appinfo.cpp index fd43248..4397abc 100644 --- a/daemon/appinfo.cpp +++ b/daemon/appinfo.cpp @@ -1,5 +1,6 @@ -#include "appinfo.h" #include +#include +#include "appinfo.h" struct AppInfoData : public QSharedData { QUuid uuid; @@ -13,6 +14,7 @@ struct AppInfoData : public QSharedData { AppInfo::Capabilities capabilities; QHash keyInts; QHash keyNames; + QImage menuIcon; QString path; }; @@ -21,6 +23,7 @@ AppInfo::AppInfo() : d(new AppInfoData) d->versionCode = 0; d->watchface = false; d->jskit = false; + d->capabilities = 0; } AppInfo::AppInfo(const AppInfo &rhs) : d(rhs.d) @@ -154,6 +157,26 @@ int AppInfo::valueForAppKey(const QString &key) const return d->keyInts.value(key, -1); } +QImage AppInfo::menuIcon() const +{ + return d->menuIcon; +} + +QByteArray AppInfo::menuIconAsPng() const +{ + QByteArray data; + QBuffer buf(&data); + buf.open(QIODevice::WriteOnly); + d->menuIcon.save(&buf, "PNG"); + buf.close(); + return data; +} + +void AppInfo::setMenuIcon(const QImage &img) +{ + d->menuIcon = img; +} + QString AppInfo::path() const { return d->path; diff --git a/daemon/appinfo.h b/daemon/appinfo.h index 6f97639..3d5c4b4 100644 --- a/daemon/appinfo.h +++ b/daemon/appinfo.h @@ -1,10 +1,10 @@ #ifndef APPINFO_H #define APPINFO_H -#include +#include #include #include -#include +#include class AppInfoData; @@ -28,6 +28,7 @@ public: Q_PROPERTY(bool watchface READ isWatchface WRITE setWatchface) Q_PROPERTY(bool jskit READ isJSKit WRITE setJSKit) Q_PROPERTY(Capabilities capabilities READ capabilities WRITE setCapabilities) + Q_PROPERTY(QImage menuIcon READ menuIcon WRITE setMenuIcon) Q_PROPERTY(QString path READ path WRITE setPath) public: @@ -71,6 +72,10 @@ public: bool hasAppKey(const QString &key) const; int valueForAppKey(const QString &key) const; + QImage menuIcon() const; + QByteArray menuIconAsPng() const; + void setMenuIcon(const QImage &img); + QString path() const; void setPath(const QString &string); diff --git a/daemon/appmanager.cpp b/daemon/appmanager.cpp index 10f2e3e..8745160 100644 --- a/daemon/appmanager.cpp +++ b/daemon/appmanager.cpp @@ -4,6 +4,17 @@ #include #include #include "appmanager.h" +#include "unpacker.h" +#include "stm32crc.h" + +namespace { +struct ResourceEntry { + int index; + quint32 offset; + quint32 length; + quint32 crc; +}; +} AppManager::AppManager(QObject *parent) : QObject(parent), @@ -116,18 +127,55 @@ void AppManager::scanApp(const QString &path) info.setWatchface(watchapp["watchface"].toBool()); info.setJSKit(appDir.exists("pebble-js-app.js")); - const QJsonArray capabilities = root["capabilities"].toArray(); - AppInfo::Capabilities caps = 0; - for (QJsonArray::const_iterator it = capabilities.constBegin(); it != capabilities.constEnd(); ++it) { - QString cap = (*it).toString(); - if (cap == "location") caps |= AppInfo::Location; - if (cap == "configurable") caps |= AppInfo::Configurable; + if (root.contains("capabilities")) { + const QJsonArray capabilities = root["capabilities"].toArray(); + AppInfo::Capabilities caps = 0; + for (auto it = capabilities.constBegin(); it != capabilities.constEnd(); ++it) { + QString cap = (*it).toString(); + if (cap == "location") caps |= AppInfo::Location; + if (cap == "configurable") caps |= AppInfo::Configurable; + } + info.setCapabilities(caps); } - info.setCapabilities(caps); - const QJsonObject appkeys = root["appKeys"].toObject(); - for (QJsonObject::const_iterator it = appkeys.constBegin(); it != appkeys.constEnd(); ++it) { - info.addAppKey(it.key(), it.value().toInt()); + if (root.contains("appKeys")) { + const QJsonObject appkeys = root["appKeys"].toObject(); + for (auto it = appkeys.constBegin(); it != appkeys.constEnd(); ++it) { + info.addAppKey(it.key(), it.value().toInt()); + } + } + + if (root.contains("resources")) { + const QJsonObject resources = root["resources"].toObject(); + const QJsonArray media = resources["media"].toArray(); + int index = 0; + + for (auto it = media.constBegin(); it != media.constEnd(); ++it) { + const QJsonObject res = (*it).toObject(); + const QJsonValue menuIcon = res["menuIcon"]; + + bool is_menu_icon = false; + switch (menuIcon.type()) { + case QJsonValue::Bool: + is_menu_icon = menuIcon.toBool(); + break; + case QJsonValue::String: + is_menu_icon = !menuIcon.toString().isEmpty(); + break; + default: + break; + } + + if (is_menu_icon) { + QByteArray data = extractFromResourcePack(appDir.filePath("app_resources.pbpack"), index); + if (!data.isEmpty()) { + QImage icon = decodeResourceImage(data); + info.setMenuIcon(icon); + } + } + + index++; + } } info.setPath(path); @@ -143,3 +191,77 @@ void AppManager::scanApp(const QString &path) const char *type = info.isWatchface() ? "watchface" : "app"; logger()->debug() << "found installed" << type << info.shortName() << info.versionLabel() << "with uuid" << info.uuid().toString(); } + +QByteArray AppManager::extractFromResourcePack(const QString &file, int wanted_id) const +{ + QFile f(file); + if (!f.open(QIODevice::ReadOnly)) { + logger()->warn() << "cannot open resource file" << f.fileName(); + return QByteArray(); + } + + QByteArray data = f.readAll(); + Unpacker u(data); + + int num_files = u.readLE(); + u.readLE(); // crc for entire file + u.readLE(); // timestamp + + logger()->debug() << "reading" << num_files << "resources from" << file; + + QList table; + + for (int i = 0; i < num_files; i++) { + ResourceEntry e; + e.index = u.readLE(); + e.offset = u.readLE(); + e.length = u.readLE(); + e.crc = u.readLE(); + + if (u.bad()) { + logger()->warn() << "short read on resource file"; + return QByteArray(); + } + + table.append(e); + } + + if (wanted_id >= table.size()) { + logger()->warn() << "specified resource does not exist"; + return QByteArray(); + } + + const ResourceEntry &e = table[wanted_id]; + + int offset = 12 + 256 * 16 + e.offset; + + QByteArray res = data.mid(offset, e.length); + + Stm32Crc crc; + crc.addData(res); + + if (crc.result() != e.crc) { + logger()->warn() << "CRC failure in resource" << e.index << "on file" << file; + return QByteArray(); + } + + return res; +} + +QImage AppManager::decodeResourceImage(const QByteArray &data) const +{ + Unpacker u(data); + int scanline = u.readLE(); + u.skip(sizeof(quint16) + sizeof(quint32)); + int width = u.readLE(); + int height = u.readLE(); + + QImage img(width, height, QImage::Format_MonoLSB); + const uchar *src = reinterpret_cast(&data.constData()[12]); + for (int line = 0; line < height; ++line) { + memcpy(img.scanLine(line), src, qMin(scanline, img.bytesPerLine())); + src += scanline; + } + + return img; +} diff --git a/daemon/appmanager.h b/daemon/appmanager.h index 1725c14..d5e5ba1 100644 --- a/daemon/appmanager.h +++ b/daemon/appmanager.h @@ -30,6 +30,8 @@ signals: private: void scanApp(const QString &path); + QByteArray extractFromResourcePack(const QString &file, int id) const; + QImage decodeResourceImage(const QByteArray &data) const; private: QFileSystemWatcher *_watcher; diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 25908e4..212a1d7 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -380,6 +380,11 @@ QVariantList PebbledProxy::AllApps() const m.insert("company-name", QVariant::fromValue(info.companyName())); m.insert("version-label", QVariant::fromValue(info.versionLabel())); m.insert("is-watchface", QVariant::fromValue(info.isWatchface())); + + if (!info.menuIcon().isNull()) { + m.insert("menu-icon", QVariant::fromValue(info.menuIconAsPng())); + } + l.append(QVariant::fromValue(m)); } -- cgit v1.2.3 From 24a27dcfdd6ce8f3e5a635404e6650081ebd63ca Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 06:40:01 +0100 Subject: convert appconfig into a dialog --- app/app.pro | 4 +-- app/qml/pages/AppConfigDialog.qml | 53 +++++++++++++++++++++++++++++++++++++++ app/qml/pages/AppConfigPage.qml | 49 ------------------------------------ app/qml/pages/WatchPage.qml | 3 ++- 4 files changed, 57 insertions(+), 52 deletions(-) create mode 100644 app/qml/pages/AppConfigDialog.qml delete mode 100644 app/qml/pages/AppConfigPage.qml (limited to 'app/qml/pages/WatchPage.qml') diff --git a/app/app.pro b/app/app.pro index 48fcf68..9cc2d09 100644 --- a/app/app.pro +++ b/app/app.pro @@ -27,5 +27,5 @@ OTHER_FILES += \ qml/images/* \ pebble.desktop \ pebble.png \ - qml/pages/AppConfigPage.qml \ - qml/pages/InstallAppDialog.qml + qml/pages/InstallAppDialog.qml \ + qml/pages/AppConfigDialog.qml diff --git a/app/qml/pages/AppConfigDialog.qml b/app/qml/pages/AppConfigDialog.qml new file mode 100644 index 0000000..65a1f5b --- /dev/null +++ b/app/qml/pages/AppConfigDialog.qml @@ -0,0 +1,53 @@ +import QtQuick 2.0 +import QtQml 2.1 +import QtWebKit 3.0 +import Sailfish.Silica 1.0 + +Dialog { + id: appConfigPage + + property alias url: webview.url + property string uuid + property string name + + SilicaWebView { + id: webview + visible: url != "" + anchors.fill: parent + + header: DialogHeader { + title: "Configuring " + name + } + + onNavigationRequested: { + console.log("appconfig navigation requested to " + request.url); + var url = request.url.toString(); + if (/^pebblejs:\/\/close/.exec(url)) { + var data = decodeURIComponent(url.substring(17)); + console.log("appconfig requesting close; data: " + data); + pebbled.setAppConfiguration(uuid, data); + appConfigPage.canAccept = true; + appConfigPage.accept(); + request.action = WebView.IgnoreRequest; + } else { + request.action = WebView.AcceptRequest; + } + } + } + + Text { + anchors.centerIn: parent + visible: url == "" + text: qsTr("No configuration settings available") + width: parent.width - 2*Theme.paddingLarge + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + font { + pixelSize: Theme.fontSizeLarge + family: Theme.fontFamilyHeading + } + color: Theme.highlightColor + } + + canAccept: false +} diff --git a/app/qml/pages/AppConfigPage.qml b/app/qml/pages/AppConfigPage.qml deleted file mode 100644 index 00eb05c..0000000 --- a/app/qml/pages/AppConfigPage.qml +++ /dev/null @@ -1,49 +0,0 @@ -import QtQuick 2.0 -import QtQml 2.1 -import QtWebKit 3.0 -import Sailfish.Silica 1.0 - -Page { - id: appConfigPage - - property alias url: webview.url - property string name - - SilicaWebView { - id: webview - visible: url != "" - anchors.fill: parent - - header: PageHeader { - title: "Configuring " + name - } - - onNavigationRequested: { - console.log("appconfig navigation requested to " + request.url); - var url = request.url.toString(); - if (/^pebblejs:\/\/close/.exec(url)) { - var data = decodeURIComponent(url.substring(17)); - console.log("appconfig requesting close; data: " + data); - pebbled.setAppConfiguration(uuid, data); - pageStack.pop(); - request.action = WebView.IgnoreRequest; - } else { - request.action = WebView.AcceptRequest; - } - } - } - - Text { - anchors.centerIn: parent - visible: url == "" - text: qsTr("No configuration settings available") - width: parent.width - 2*Theme.paddingLarge - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.Wrap - font { - pixelSize: Theme.fontSizeLarge - family: Theme.fontFamilyHeading - } - color: Theme.highlightColor - } -} diff --git a/app/qml/pages/WatchPage.qml b/app/qml/pages/WatchPage.qml index 3a712ab..9096df6 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -109,8 +109,9 @@ Page { 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"), { + pageStack.push(Qt.resolvedUrl("AppConfigDialog.qml"), { url: url, + uuid: uuid, name: appInfo.longName }); } -- cgit v1.2.3 From 7c48bbe17251fef1d3045ac8b5b6fe8058b4fb10 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Dec 2014 17:19:46 +0100 Subject: add i18n support to UI app also 'es' translation --- app/app.pro | 10 +- app/qml/pages/WatchPage.qml | 4 +- app/translations/pebble-es.ts | 254 ++++++++++++++++++++++++++++++++++++++++++ app/translations/pebble.ts | 253 +++++++++++++++++++++++++++++++++++++++++ rpm/pebble.spec | 1 + rpm/pebble.yaml | 1 + 6 files changed, 518 insertions(+), 5 deletions(-) create mode 100644 app/translations/pebble-es.ts create mode 100644 app/translations/pebble.ts (limited to 'app/qml/pages/WatchPage.qml') diff --git a/app/app.pro b/app/app.pro index 9cc2d09..ca03ab1 100644 --- a/app/app.pro +++ b/app/app.pro @@ -23,9 +23,13 @@ OTHER_FILES += \ qml/pages/ManagerPage.qml \ qml/pages/WatchPage.qml \ qml/pages/AboutPage.qml \ + qml/pages/InstallAppDialog.qml \ + qml/pages/AppConfigDialog.qml \ qml/pebble.qml \ qml/images/* \ + translations/*.ts \ pebble.desktop \ - pebble.png \ - qml/pages/InstallAppDialog.qml \ - qml/pages/AppConfigDialog.qml + pebble.png + +CONFIG += sailfishapp_i18n +TRANSLATIONS += translations/pebble-es.ts diff --git a/app/qml/pages/WatchPage.qml b/app/qml/pages/WatchPage.qml index 9096df6..2d69306 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -60,7 +60,7 @@ Page { Button { - text: "Ping" + text: qsTr("Ping") width: parent.width / 2 onClicked: { pebbled.ping(66) @@ -68,7 +68,7 @@ Page { } Button { - text: "Sync Time" + text: qsTr("Sync Time") width: parent.width / 2 onClicked: { pebbled.time() diff --git a/app/translations/pebble-es.ts b/app/translations/pebble-es.ts new file mode 100644 index 0000000..7332e4c --- /dev/null +++ b/app/translations/pebble-es.ts @@ -0,0 +1,254 @@ + + + + + AboutPage + + + Version + Versión + + + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + + Bugs? + ¿Errores? + + + + AppConfigDialog + + + No configuration settings available + No hay opciones disponibles para configurar + + + + CoverPage + + + connected + conectado + + + + disconnected + desconectado + + + + InstallAppDialog + + + Install app + Instalar app + + + + Install + Instalar + + + + ManagerPage + + + About + Acerca de + + + + Pebble Manager + + + + + Waiting for watch... +If it can't be found please check it's available and paired in Bluetooth settings. + Buscando el reloj +Si esto tarda mucho, comprueba que el reloj esté emparejado correctamente. + + + + Service + Servicio + + + + Enabled + Habilitado + + + + Automatic startup + Inicio automático + + + + Manual startup + Inicio manual + + + + Active + Activo + + + + Running + Ejecutándose + + + + Dead + Detenido + + + + Connection + Conexión + + + + Connected + Conectado + + + + Disconnected + Desconectado + + + + Settings + Configuración + + + + Forward phone calls + Transferir llamadas + + + + Silent when connected + Modo silencio automático + + + + Sets phone profile to "silent" when Pebble is connected + Activa el modo silencio cuando se conecte un Pebble + + + + Transliterate messages + Transliterar mensajes + + + + Messages are transliterated to ASCII before sending to Pebble + Codifica los mensajes entrates a ASCII antes de enviarlos a Pebble + + + + Notifications + Notificaciones + + + + Messaging + Mensajería + + + + SMS and IM + SMS y chat + + + + Missed call + Llamadas perdidas + + + + Emails + Correos electrónicos + + + + Mitakuuluu + + + + + Twitter + Twitter + + + + Facebook + Facebook + + + + Other notifications + Resto de notificaciones + + + + All notifications + Todas las notificaciones + + + + WatchPage + + + Ping + Ping + + + + Sync Time + Ajustar hora + + + + Installed applications + Aplicaciones instaladas + + + + Uninstalling + Desinstalando + + + + (empty slot) + (hueco libre) + + + + (slot in use by unknown app) + (hueco en uso) + + + + Install app... + Instalar app... + + + + Configure... + Configurar... + + + + Uninstall + Desinstalar + + + diff --git a/app/translations/pebble.ts b/app/translations/pebble.ts new file mode 100644 index 0000000..3cc8b6d --- /dev/null +++ b/app/translations/pebble.ts @@ -0,0 +1,253 @@ + + + + + AboutPage + + + Version + + + + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + + Bugs? + + + + + AppConfigDialog + + + No configuration settings available + + + + + CoverPage + + + connected + + + + + disconnected + + + + + InstallAppDialog + + + Install app + + + + + Install + + + + + ManagerPage + + + About + + + + + Pebble Manager + + + + + Waiting for watch... +If it can't be found please check it's available and paired in Bluetooth settings. + + + + + Service + + + + + Enabled + + + + + Automatic startup + + + + + Manual startup + + + + + Active + + + + + Running + + + + + Dead + + + + + Connection + + + + + Connected + + + + + Disconnected + + + + + Settings + + + + + Forward phone calls + + + + + Silent when connected + + + + + Sets phone profile to "silent" when Pebble is connected + + + + + Transliterate messages + + + + + Messages are transliterated to ASCII before sending to Pebble + + + + + Notifications + + + + + Messaging + + + + + SMS and IM + + + + + Missed call + + + + + Emails + + + + + Mitakuuluu + + + + + Twitter + + + + + Facebook + + + + + Other notifications + + + + + All notifications + + + + + WatchPage + + + Ping + + + + + Sync Time + + + + + Installed applications + + + + + Uninstalling + + + + + (empty slot) + + + + + (slot in use by unknown app) + + + + + Install app... + + + + + Configure... + + + + + Uninstall + + + + diff --git a/rpm/pebble.spec b/rpm/pebble.spec index 8779710..9fb9575 100644 --- a/rpm/pebble.spec +++ b/rpm/pebble.spec @@ -81,6 +81,7 @@ systemctl --user daemon-reload %{_bindir} %{_datadir}/%{name}/qml %{_datadir}/%{name}/js +%{_datadir}/%{name}/translations %{_datadir}/applications/%{name}.desktop %{_datadir}/icons/hicolor/86x86/apps/%{name}.png %{_libdir}/systemd/user/%{name}d.service diff --git a/rpm/pebble.yaml b/rpm/pebble.yaml index 80a5d20..c788c27 100644 --- a/rpm/pebble.yaml +++ b/rpm/pebble.yaml @@ -32,6 +32,7 @@ Files: - '%{_bindir}' - '%{_datadir}/%{name}/qml' - '%{_datadir}/%{name}/js' +- '%{_datadir}/%{name}/translations' - '%{_datadir}/applications/%{name}.desktop' - '%{_datadir}/icons/hicolor/86x86/apps/%{name}.png' - '%{_libdir}/systemd/user/%{name}d.service' -- cgit v1.2.3 From ed43269c2adebed7baf4e2452f998d7e60c797e6 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 21 Dec 2014 21:04:35 +0100 Subject: fix typos --- app/qml/pages/InstallAppDialog.qml | 2 +- app/qml/pages/WatchPage.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app/qml/pages/WatchPage.qml') diff --git a/app/qml/pages/InstallAppDialog.qml b/app/qml/pages/InstallAppDialog.qml index 79283a6..fa96c28 100644 --- a/app/qml/pages/InstallAppDialog.qml +++ b/app/qml/pages/InstallAppDialog.qml @@ -52,7 +52,7 @@ Dialog { left: appIcon.right leftMargin: Theme.paddingMedium right: parent.right - rightMargin: Theme.paddiumLarge + rightMargin: Theme.paddingLarge verticalCenter: parent.verticalCenter } text: modelData.longName diff --git a/app/qml/pages/WatchPage.qml b/app/qml/pages/WatchPage.qml index 2d69306..43c2b99 100644 --- a/app/qml/pages/WatchPage.qml +++ b/app/qml/pages/WatchPage.qml @@ -186,7 +186,7 @@ Page { left: slotIcon.right leftMargin: Theme.paddingMedium right: parent.right - rightMargin: Theme.paddiumLarge + rightMargin: Theme.paddingLarge verticalCenter: parent.verticalCenter } text: isEmptySlot ? qsTr("(empty slot)") : (isKnownApp ? appInfo.longName : qsTr("(slot in use by unknown app)")) -- cgit v1.2.3